Azure Functions - Functional Redirection
A few months ago, we decided to look into additional options that exist for command and control (C2), specifically what we can use for "redirectors". We set two specific restrictions on what we needed to look for:
- Don't stand up another Linux system for redirection
- Don't use another CDN for domain fronting
Outside of the above restrictions, our goal was to look for another option that we could leverage on assessments for C2. We quickly discovered and chose to focus on Azure Functions!
Azure Functions are the equivalent of AWS Lambda functions (which @_xpn_ also blogged about for its own C2 usage). It's essentially "server-less computing" where you can provide the code that you want to run, and a trigger that invokes your code. There's no overhead where you need to manage a system/server that runs your code for you; all you need to do is push your Azure Function codebase to Azure and they will handle its execution and access to computing resources.
As a quick aside, if you are looking to develop your own Azure Functions, it couldn't be when using VS Code. When tied into your Azure account, you can create new functions and deploy updates to your function with the click of a button. This is all done incredibly easy with the Azure Functions extension.
Azure Background
Azure functions have the ability to use multiple languages to execute code, including:
- PowerShell Code
- .NET Core
- Python
- Node.js
- Java
When you build an Azure function, you have to specify what will trigger your function and make it run. There are multiple options, but an easy one to start with is to use a HTTP request to trigger it.
When building a trigger with a HTTP request, you can specify the level of access required to invoke the function. The main levels and what they mean are:
- Admin - This requires a host key to be passed with the request for authorization. Think of this as an API key for all sub-functions within the overall function "container". A host key can be used to access and trigger any sub-function.
- Function - Within each function "container" can be multiple sub-functions (for GET requests, POST requests, etc.). A functional key is a key created within each sub-function. This is also similar to an API key which is created for accessing a specific sub-function.
- Anonymous - Exactly what it sounds like. No key is needed to trigger the function code. Just a web request to the specific URL will invoke the code.
Finally, when building your Function, you also get to specify the subdomain that your function will use. Make sure to choose wisely!
Redirection with Azure Functions
When building out a redirector with Azure Functions, you will need to pair your function code closely with your malleable profile. You're likely going to have a minimum of three sub-functions. One for handling GET requests, one for POST requests, and one for staging (if using staged payloads).
Let's take a look at some sample code:
If you look at the GET code, it's incredibly simple in this POC. All that it does is receives the incoming web request, captures all the headers, and makes a new request to the location of the Cobalt Strike team server. Once it receives the response from the team server, it then returns its contents to the original requesting system.
If you look at the POST code, it's nearly identical to the GET request code. The only real difference is on line 13 the contents of the post request are stored in the "post_data" variable. The POST contents are then sent to the team server (line 14). Once the function receives a response back, it then forwards the response to the original requesting system.
Once you have developed your code, you can push it to Azure with VS Code. Once pushed, you should get output similar to the following which shows the trigger URLs for each function.
If you'll notice in the above URLs, you'll see that "api" is included within the URL. This is the default value. You can change that value by modifying the host.json file included within your Azure Function.
The only bug we've encountered is when deploying the function code with VS Code, it will still show "api" within the trigger URLs. However, you can always verify the actual URL you are supposed to use by logging into the Azure portal and looking at your function URL.
Malleable Profile
The last step is to make your malleable profile's URLs (for GET, POST, and the stage block) requests match up with your function's URLs.
There are two things you should notice with the above image. First, the URI for the GET and POST code blocks do match up with the URI that is used for the Azure Function. Second, all identifying data for each Beacon is contained within the request's headers. This is why your Azure Function code captures the headers for the incoming requests, to ensure that the unique Beacon identifier does get passed along to the Cobalt Strike team server.
Finally, when setting up your Cobalt Strike listener, the only real data you need to provide is the subdomain that you specified when creating your Azure Function.
At this point, you should be able to generate a payload and test to ensure that you do receive a beacon!
For proof of concept example, be sure to check out our repo - https://github.com/FortyNorthSecurity/FunctionalC2
We hope that this blog post helps to give an idea about using Azure Functions for C2, and if you have any questions feel free to Contact Us!