This is the first of a series of blog posts exploring a number of modern tech stacks which I’ve found to dramatically improve quality of life for an operator when creating C2 infrastructure regardless of the C2 platform used.
In this post we’ll be trying to ease the pain deploying/configuring reverse proxies which should be in front of your C2 platform. If you’re not familiar with why you’d want to do this in the first place check out this blog which covers the basics.
Traditionally, most people used either NGINX or Apache1 mod_rewrite. This always bothered me for a number of reasons:
Apache mod_rewrite is pretty boomer
Configuring NGINX or Apache mod_rewrite is beyond a PITA.
They’re overkill. Typically, I’m not expecting an insane amount of traffic hitting my C2 server. (Unless you’re the SolarWinds APT2 I guess?). The amount of configuration required to set these up isn’t worth the payoff.
NGINX/Apache don’t help at all with acquiring a valid TLS certificate via ZeroSSL or LetsEncrypt cause they’re old as fuck. Because of this, you need to configure an entire other stack to get a valid certificate and then point Apache/NGINX to it. (I was a fan of acme.sh before I saw the light)
Let me introduce you to Caddy, if you haven’t heard of it you should stop living like a caveman and check it out. It’s by far the easiest “enterprise-ready” web server to setup and get running. More importantly it has secure defaults out-of-the-box and can also be used as a reverse proxy. I recommend checking out the common patterns and the Caddyfile structure to get started. Side note, it has an API which “… makes it possible to automate [Caddy] in a programmable fashion…”. There’s definitely a lot of possibilities the API opens up from a C2 infrastructure automation perspective however that’s not the focus of the post.
Let’s cut to the chase, I want you to visualize the suffering it usually takes to get NGINX/Apache configured properly and then take a look at this:
{ | |
# This instructs Caddy to hit the LetsEncrypt staging endpoint, in production you should remove this. | |
acme_ca https://acme-staging-v02.api.letsencrypt.org/directory | |
} | |
(proxy_upstream) { | |
# Enable access logging to STDOUT | |
log | |
# This is our list of naughty client User Agents that we don't want accessing our C2 | |
@ua_denylist { | |
header User-Agent curl* | |
} | |
# This is our list of naughty IPs that we want to prevent from accessing our C2 | |
@ip_denylist { | |
remote_ip 8.8.8.8/32 | |
} | |
header { | |
-Server | |
+X-Robots-Tag "noindex, nofollow, nosnippet, noarchive" | |
+X-Content-Type-Options "nosniff" | |
} | |
# Respond with a 403 if the client has a User Agent defined in our naughty list | |
# Lot more you can do with this (e.g. redirect to seperate domain), check the docs | |
respond @ua_denylist "Forbidden" 403 { | |
close | |
} | |
# Respond with a 403 if the client has an IP defined in our naughty list | |
# Lot more you can do with this (e.g. redirect to seperate domain), check the docs | |
respond @ip_denylist "Forbidden" 403 { | |
close | |
} | |
# Reverse proxy to our host "c2platform" on port 80 | |
# Caddy automatically adds a X-Forwarded-For header which is super useful for Cobalt Strike | |
reverse_proxy c2platform:80 { | |
header_up Host {upstream_hostport} | |
header_up X-Forwarded-Host {host} | |
header_up X-Forwarded-Port {port} | |
} | |
} | |
www.legitdomain.com { | |
# Use the proxy_upstream code snippet (defined above) | |
import proxy_upstream | |
} | |
legitdomain.com { | |
# Use the proxy_upstream code snippet (defined above) | |
import proxy_upstream | |
} |
Install Caddy, put that in a file named Caddyfile
and then in the same directory run the following command: caddy run
At runtime, Caddy will create a valid TLS certificate for the domains you specified and will start reverse proxy’ing traffic to your C2 back-end. It also supports URL rewrites in case you were wondering.