Cyber Security Technical Blog

Cobalt Strike OpSec

Written by Jerry Odegaard | Sep 18, 2024 1:34:18 AM

Cobalt Strike OpSec & Other Misadventures Of Pentesting

I’m old enough to know better. Since 2004 I’ve been in various offensive roles in infosec, so OpSec isn’t a new thing for me. In consulting, one regularly performs security scans and hosts some form of Command & Control (C2). Certain Cloud providers are more amenable to this activity than others. However, regardless of the offensive role, one will periodically receive notifications from Cloud providers about potentially malicious activity originating from your account. Most major Cloud providers explicitly forbid penetration testing without authorization; however, a few will simply close out a complaint ticket when you inform them that it’s expected activity for authorized penetration testing, even without going through the provider’s authorization process.

C2 Infrastructure Automation

Instead of making this another generic OpSec blog, we’ll be talking about some very specific issues when using Cobalt Strike. Once upon a time, in my former life, I was on a Red Team with a lot of smart people. One of our smart guys rolled out our first round of C2 infrastructure automation, and for our precise purposes (e.g. weekly foothold simulations) it was extremely well implemented. Not only did our guy implement infra-automation, but fully staged our campaign on our C2 server. Here’s the short list of the awesome features he implemented:

  • Terraform provisioning of VM instances and DNS configuration
    • Support for several C2 platforms
    • Malleable C2 profile randomization
  • Instance configuration with Salt
    • System updates
    • LetsEncrypt TLS certs
    • Handle all the additional configuration steps
  • Automated reverse proxy configuration
    • Redirection of C2 URIs to our C2 server
    • Redirection of all other traffic to a reputation domain
    • Proxy server hosted configuration script
  • Campaign configuration via Aggressor Script
    • Validation that the campaign configuration was completed correctly
  • Well thought through documentation and usage instructions

All of these features made for a very OpSec-safe and organized C2 infrastructure. 

Red Team Pentesting

After my former life ended, I wanted to replicate a portion of this functionality and tune it for this purpose. Since our Red Team Engagements are scoped and defined differently than in my former life, many of our analysts were using C2 platforms for the first time in production. Because of this, we may need to quickly adapt how we’re using a single C2 server and so we took a more basic approach:

  • Terraform provisioning of VM instances, DNS configuration
    • Support for Cobalt Strike
  • Terraform instances configuration
    • System updates, configuration
    • TLS certificate setup
    • Teamserver configuration with a static Malleable C2 Profile
  • Campaign Configuration via Aggressor Script
    • Stage the campaign
    • Configure custom random URL for pulling shellcodes for payload configuration
  • Usage documentation with best practices recommendations

We definitely took a less robust approach, especially when it comes to reverse proxy configuration. One benefit we have from this is that our team members can quickly host and delete a file without having to reconfigure the proxy to pass a request to reach it. The downside is that, unless manually configured to restrict access, all HTTP/S requests will hit our Teamserver. This is the easiest way to get a complaint filed with your Cloud provider!

Cobalt Strike & Legacy Staging

Before we get further, let’s quickly define terminology for those who are unfamiliar with Cobalt Strike:

Stager [shellcode]: A small blob of shellcode that when executed will download a larger blob of shellcode

Stage [shellcode]: A much larger blob of shellcode containing the core of an agent. This is the formal term used for where a large portion of Cobalt Strike Beacon code lives and includes built-in functionality and tasking capability. In the case of Meterpreter, its Stage would contain the bulk of Meterpreter functionality, minus modules like kiwi and incognito.

It was between the 2.x and 3.x versions of Cobalt Strike where they removed Metasploit integration. However, they didn’t remove Metasploit compatibility. If you’ve ever taken the time to look at the shellcode for staging reverse HTTP/S Meterpreter payloads and compared them to Cobalt Strike’s staging shellcode, you’ll see they are extremely similar. This was by design so that you can use a Metasploit exploit to achieve a Cobalt Strike beacon on a host (and vice versa). 

Below you can see on the left a Cobalt Strike Stager shellcode, and on the right a Meterpreter Stager shellcode. They use common techniques to kick off execution, and there are certainly deviations in their shellcodes, but the further down you go the more obvious it is that Cobalt Strike’s Stager is based on Metasploit’s HTTP/S Meterpreter Stager.

Staging A Cobalt Strike Beacon Within Metasploit

Since we know Cobalt Strike should support Metasploit’s staging, we simply drop in a Teamserver’s hostname and port into a Metasploit exploit that used a windows/meterpreter/reverse_https (or x64 version) payload to load a beacon upon execution. There are plenty of blogs and videos on Cobalt Strike’s website, so we’ll cut to the chase and just build a simple Windows binary with msfvenom to demonstrate:

Once we run this payload, we’ll see a callback on our Teamserver:

This is exactly what we’d expect. The last request in Cobalt Strike’s Web Log isn’t a URL we configured, though. If you’ve ever used windows/meterprever/reverse_https before, you’re familiar with these long URLs used during staging. But what exactly is Cobalt Strike providing back to the request for that URL? Disappointingly, it is a raw Stage, without any of the PNG/JPEG mockup that we configured in our C2 profile:

The example Cobalt Strike HTTPS Listener above was a vanilla configuration, with the additions of Malleable C2 profile configuration. If you’re a Cobalt Strike veteran, you might ask “Why not use the host_stage parameter in your C2 profile and set it to false?” This configuration parameter should disable ALL staging on a Teamserver. Thing is, we do this already, and it doesn’t seem to be working. And additionally, we DO want to use Staged payloads (this is where we use a Stager payload first to kick off the download and execution of the Stage) for a variety of reasons.

Instead, I went down a rabbit hole and when I came back up found an incredibly simple solution to “fix” this. That is, we can completely disable Metasploit compatibility on the Teamserver without losing the ability to have Staged payloads, and as a result, prevent raw Stages from ever getting downloaded.

The Problem Statement

We see sandboxed callbacks to our C2 server, likely caused by security service vendors sweeping our Cloud provider looking for Cobalt Strike deployments. We want to stop this to avoid identification of our C2 infrastructure, and we need to do this without setting IP whitelisting (which is unreliable in the remote work world we now live in). Sandboxed callbacks to our C2 server look a lot like this:

And we can check our embarrassing proxy logs to see requests like these (all the visible requests were successful Stage downloads):

Understanding The Cobalt Strike Problem

So we’ve seen the above proxy logs with successful Stage downloads that follow some pattern of /XXXX, and we also saw a Stage download with our msfvenom generated payload quite a bit longer:

/vYOKIw8rwqnZN9g2uibbwQnPIt5CkO-N3gXaKe7juHrq8ky5IYPxp-LVyhROarSADMXlg3h8CcediwMxN-YE_i4q3fd6alf1OYEjDu_J16p2_ykD9xNIKLoyvWwWd3crCMe4/ 

What the heck are these, or what do they correspond to? Well, if we Google hard enough we’ll find articles like this guy: https://isc.sans.edu/diary/Finding+Metasploit+%26+Cobalt+Strike+URLs/27204

When a Metasploit server running a windows/meterpreter/reverse_https receives a request, it passes the request URI through an 8-bit checksum. If that 8-bit checksum equals specific values corresponding to the payload configuration (e.g. Windows, Python, Java, etc), then a Stage will be sent back that matches the requesting payload. You can check this in Python with a few very simple commands:

So, it seems that if a URI hits out Teamserver with an 8-bit checksum of 92 (or 93 for x64), then the Teamserver will emit a raw Stage, just like Metasploit. You may see 4-character URIs hitting your Teamserver and try to explicitly block them at your reverse proxy, but if you haven’t configured your reverse proxy to ONLY pass explicitly allowed URIs to your Teamserver then you’re going to get caught without fixing this. You can easily have 5-character (or greater) URIs that will match 92 or 93 after the 8-bit checksum.

First Attempt At A Cobalt Strike Solution

This is one of the edge cases in Cobalt Strike I hadn’t figured out yet, so I investigated a band-aid approach. We use Apache for our reverse proxies and found that we can perform some filtering with mod_ext_filter: https://httpd.apache.org/docs/2.4/mod/mod_ext_filter.html

With mod_ext_filter you can programmatically perform transformations to the response from your Teamserver before it leaves your reverse proxy. This is great, and it worked like a charm! Except it’s not the best solution. 

For the sake of posterity, and for those who might ever need it for a different purpose, is a snippet from my site configuration that I used:

<IfModule mod_ext_filter.c>
        ExtFilterDefine BlockStageRequests outtype=text/html mode=output cmd="/usr/bin/python /var/www/validation/validate.py"
        <Location />
                SetOutputFilter BlockStageRequests
                ExtFilterOptions LogStderr
        </Location>
</IfModule>

And here’s the corresponding script file validate.py:

#!/usr/bin/python3
import sys
import re
 
rsp_data = sys.stdin.read()
 
# x86 Stage bytes
x86_bytes = "\xfc\xe8\x1d\x00\x00\x00\xbe\xb8"
 
# x64 Stage bytes
x64_bytes = "\xfc\x48\x83\xe4\xf0\xeb\x33\x5d"
 
# HTML Redirect:
html_redirect = """<html>
<head></head>
<body><meta http-equiv="refresh" content="0; URL=https://www.google.com/" />
</body>
</html>"""
 
if (re.match(x86_bytes, rsp_data)):
    print (html_redirect)
elif (re.match(x64_bytes, rsp_data)):
    print (html_redirect)
else:
    sys.stdout.write(rsp_data)

Best Attempt At A Cobalt Strike Solution

Using mod_ext_filter definitely did the trick, but once it was working – I wasn’t satisfied. Without knowing how or why Cobalt Strike would still provide raw Stages, I was unconvinced it would be a permanent solution. As mentioned earlier, the correct solution is incredibly simple. Let’s take a quick look at our Sites tab:

There are a couple of URIs I’ve seen there for forever: stager and stager64. Interestingly enough, if you have direct access to the Teamserver’s HTTP/S ports, you can request those URIs (without a leading forward-slash) and pull Stages. Behind an Apache reverse proxy it’s not possible. As it turns out, those correspond to raw Stages. Guess what happens if you delete them!

Not only does the removal of stager and stager64 remove Metasploit compatibility, but it also doesn’t affect Stages configured via Malleable C2 profiles. It’s actually the exact solution we needed!

Let’s verify. First, select and delete both stager and stager64:

And then, let’s make a request to a known URI pattern that will yield a Stage:

Next, for good measure, let’s see what our Teamserver’s web log has to say:

EXCELLENT! Now let’s wait a few hours and check our reverse proxy logs to see what they look like:

Look at all those 404’s! Muhaha! 😈

Now, the last thing we needed to do was to add to the Aggressor Script that’s responsible for campaign configuration – just two lines to make this happen every single time we deploy a new Teamserver:

site_kill(443, "stager");
site_kill(443, "stager64");

Only the best solutions for the Cyber Advisors pentesting team!

Cobalt Strike OpSec For Red Teams Conclusion

There’s plenty of chatter on the internet about OpSec for Red Teams and organizations that conduct Red Team Engagements. Understanding our tools and how they can give away our presence is vital to avoiding getting caught, and in particular avoiding complaints lodged against you to your Cloud provider. Make sure you follow your Cloud provider’s rules, get permission when needed, and be ready to ask for forgiveness when you don’t. As for your Cobalt Strike infrastructure, if you’re not configuring reverse proxies to explicitly pass URIs to your Teamservers, make sure that you take the above steps to remove unnecessary functionality and improve OpSec for your infrastructure.

MORE FROM OUR TECHNICAL BLOG

Cyber Advisors specializes in providing fully customizable cyber security solutions & services. Our knowledgeable, highly skilled, talented security experts are here to help design, deliver, implement, manage, monitor, put your defenses to the test, & strengthen your systems - so you don’t have to.

Read more from our technical experts...