Red Team infrastructure is a detail-heavy subject. Take the case of domain fronting through a CDN like CloudFront. You have to setup the CloudFront distribution, have a valid SSL configuration, and configure your profile properly. If any of these items is wrong, your C2 will not work.
Many folks take “configure your profile properly” for granted. A profile that passes a c2lint check isn’t guaranteed to work in all circumstances you throw at it. A lot of things can go wrong. For example, the CDN may rewrite your HTTP requests in ways that break your Malleable C2 profile. That’s what this blog post is about…
One of my favored ways to quickly setup an SSL-enabled Cobalt Strike is with Alex Rymdeko-harvey‘s HTTPsC2DoneRight.sh script. This script asks a few questions, requests a LetsEncrypt certificate, and sets up a modified Malleable C2 profile that uses this certificate.
The profile used by Alex’s script is amazon.profile. I’ve noticed that many red teamers opt to use this profile with their first experiments to use CloudFront as a redirector for Cobalt Strike.
An Amazon Web Service and an Amazon-like profile, what could go wrong? A lot of things can go wrong. 🙂 If you’ve tried this setup, you may have found a console message [on the team server] that looks like this:
This message means Cobalt Strike could not recover information from an HTTP transaction. Some aspect of the HTTP transaction differs from the assumptions provided by your Malleable C2 profile.
Assumption is the key word. Malleable C2 gives operators a lot of power to change what Cobalt Strike’s HTTP communication looks like. Malleable C2 is an example of a declarative programming language. You, the operator, specify what Beacon’s communication should look like. Cobalt Strike figures out how to make that happen. Specifically, Cobalt Strike compiles your specification into two different programs. One program transforms data and stores it in a transaction. The other program recovers data from a transaction and reverses the transformations made to the data. These programs are patched into the Beacon payload and its controller. If the assumptions of these derived programs break, your communication breaks.
When the above happens, it’s helpful to know how to read the output above and use it to troubleshoot your communication.
Our error message states that Malleable C2 could not recover data from the http-get -> client -> metadata transaction. Beacon provides metadata to tell Cobalt Strike about the session. This RSA-encrypted blob includes situational awareness information (e.g., username, PID, hostname, etc.) and a unique key for the session. Without metadata, Cobalt Strike knows nothing about the session. It’s important that this part of the transaction works.
To resolve this issue, it’s our job to figure out how the expectations of the Malleable C2 profile differ from the reality of the HTTP request received by Cobalt Strike.
Where should we look? The right place is to look at the parts of the HTTP transaction relevant to our failed transaction.
Here’s what the .http-get.client.metadata block looks like in the (slightly modified) amazon profile I used with CloudFront:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | http-get { set uri "/s/ref=nb_sb_noss_1/167-3294888-0262949/field-keywords=books" ; client { header "Accept" "*/*" ; metadata { base64; prepend "session-token=" ; prepend "skin=noskin;" ; append "csm-hit=s-24KU11BB82RZSYGJ3BDK|1419899012996" ; header "Cookie" ; } } |
The metadata block base64 encodes the metadata, prepends two strings to it, appends another string, and it stores the entire thing in the HTTP Cookie header. OK.
And, here’s a screenshot of the preview generated by c2lint:
In troubleshooting this specific issue, we should ask: does the HTTP Cookie header received by Cobalt Strike differ from the HTTP Cookie header Malleable C2 expects. If so, how and why?
Let’s look at the preview Cookie and the received Cookie side-by-side:
Here, the issue becomes a little more obvious. The Cookie received by Cobalt Strike has the same information, but the parts of the Cookie are in a different order. This is a change CloudFront made to our HTTP transaction. This breaks our profile.
Malleable C2 does not know the semantics of the Cookie field. To recover data from this transaction, it knows it must recover the value of the Cookie header, remove some appended strings, remove some prepended strings, and base64 decode what’s left. Any unexpected change (e.g., spaces removed, new strings, re-arranged strings, etc.) breaks this process.
What’s the solution? In this case, we could update the structure of our HTTP Cookie to match what CloudFront will transform it to. This would work just fine. We could also store our metadata in something other than the Cookie header. Either approach is OK.
Note: This metadata block will fail a c2lint check because of the space between ; and skin=noskin. c2lint’s advice is sound, but risk averse. Here, the space is necessary to match what we will receive after passing an HTTP transaction through CloudFront.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | http-get { set uri "/s/ref=nb_sb_noss_1/167-3294888-0262949/field-keywords=books" ; client { header "Accept" "*/*" ; metadata { base64; prepend "session-token=" ; append "csm-hit=s-24KU11BB82RZSYGJ3BDK|1419899012996" ; append "; skin=noskin" ; header "Cookie" ; } } |
This blog post focuses on CloudFront because I’ve answered questions about this circumstance in the past. Profile assumptions can fail for other reasons. For example, if you change a profile mid-engagement, expect to see issues like this one. If your HTTP communication fails, due to broken profile assumptions, these steps can help you diagnose the issue and correct your profile.