Skip to content

C2 HTTP(S) Redirectors with HAProxy

Published: at 07:10 PM (5 min read)

Table of Contents

Open Table of Contents

Why redirectors?

(Because we practice safe red teaming)

C2 redirectors serve three main purposes:

What is HAProxy?

High Availability Proxy (HAProxy) is an open-source reverse-proxy tool used on Linux systems. It’s meant to improve performance and availability of web servers.

But it offers another option besides .htaccess files for routing web traffic to C2 servers.

HAProxy contains two main components:

HAProxy can have multiple frontends and backends, if you want to listen on multiple ports and redirect traffic to multiple hosts.

What does your C2 traffic look like?

In order to redirect traffic, we need to know what kind of traffic we want to redirect in the first place. For web-based C2 listeners, that usually comes in the form of URIs, request headers, type of request (GET, POST), and more.

My infra:

I configured my payload to use these settings (note that the callback host is set to the IP of the redirector):

HAProxy configuration file setup

Before we set up HAProxy, we must first install HAProxy.

sudo apt install haproxy

Configure SSL certificate stuff as well:

sudo apt install ssl-cert
cp /etc/ssl/certs/ssl-cert-snakeoil.pem /etc/haproxy
cp /etc/ssl/private/ssl-cert-snakeoil.key /etc/haproxy/ssl-cert-snakeoil.pem.key # haproxy is weird

To configure the frontend, we will set the mode to http and bind to ports 80 and 443. Our main C2 traffic will be using HTTPS, but we want our redirector to automatically redirect all HTTP traffic on port 80 to HTTPS as well.

frontend redirector
  mode http
  bind *:80
  bind *:443 ssl crt /etc/haproxy/ssl-cert-snakeoil.pem
  http-request redirect scheme https unless { ssl_fc }

Next up we define our ACLs. Our beacon callbacks are configured to use a specific user agent, as well as hit the /pages and /info endpoints on the C2 server. We can create ACLs for those:

  # format: acl <name> <parameter to match> <flags> <values>
  # -i = case insensitive
  # -m = match type

  # match User-Agent request header to a specific value
  acl ua req.fhdr(User-Agent) -i "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.100 Safari/537.36"

  # match URL paths that begin with /pages or /info
  acl urls path -i -m beg /pages /info

Now we define the action to take if ACLs match.

  # format: use_backend <backend name> if <condition>
  use_backend c2 if ua urls

We set a default backend as a catch-all for non-matching traffic:

  # format: default_backend <backend-name>
  default_backend catchall

Now let’s set our backends. We have 2 backends: one for the C2 server and the other for all other traffic.

# backend for C2 traffic
backend c2
  mode http
  server mythic 10.2.99.99:443 ssl verify none

# backend going to a locally hosted Apache2 server
backend catchall
  mode http
  server bruh 127.0.0.1:8080

For troubleshooting purposes, let’s enable logging.

# add on the very top
global
  log /dev/log local0
  log /dev/log local1 notice

# add in frontend section
  log global
  option httplog

HAProxy full config file

Here’s the full config file:

global
  log /dev/log local0
  log /dev/log local1 notice

frontend redirector
  mode http
  bind *:80
  bind *:443 ssl crt /etc/haproxy/ssl-cert-snakeoil.pem
  http-request redirect scheme https unless { ssl_fc }

  # Enable logging
  log global
  option httplog

  # Define ACLs
  acl ua req.fhdr(User-Agent) -i "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.100 Safari/537.36"
  acl urls path -i -m beg /pages /info

  # Forwrd to C2 if conditions are met
  use_backend c2 if ua urls

  # If no ACL matches, redirect to a different landing page
  default_backend catchall

backend c2
  mode http
  server mythic 10.2.99.99:443 ssl verify none

backend catchall
  mode http
  server bruh 127.0.0.1:8080

Setting up the catch-all backend web server

Set up Apache2 to listen on non-default port (e.g. 8080) and host a static webpage on there.

Install Apache2 if not installed already:

sudo apt install apache2

Change Listen 80 to Listen 8080 in /etc/apache2/ports.conf file:

# If you just change the port or add more ports here, you will likely also
# have to change the VirtualHost statement in
# /etc/apache2/sites-enabled/000-default.conf

Listen 8080

<IfModule ssl_module>
        Listen 443
</IfModule>

<IfModule mod_gnutls.c>
        Listen 443
</IfModule>

Change <VirtualHost *:80> to <VirtualHost *:8080> in /etc/apache2/sites-available/000-default.conf:

<VirtualHost *:8080>
        # The ServerName directive sets the request scheme, hostname and port that
        # the server uses to identify itself. This is used when creating
        # redirection URLs. In the context of virtual hosts, the ServerName
        # specifies what hostname must appear in the request's Host: header to
        # match this virtual host. For the default virtual host (this file) this
        # value is not decisive as it is used as a last resort host regardless.
        # However, you must set it for any further virtual host explicitly.
        #ServerName www.example.com

Restart Apache2:

sudo systemctl restart apache2

Apache2 should now be listening on port 8080.

Running HAProxy

Typically I like to run it in a tmux session.

haproxy -f redirector.conf

Troubleshooting

If you aren’t seeing any traffic come back, check your HAProxy logs. Make sure that rsyslog is installed first:

sudo apt install rsyslog

You can then tail /var/log/haproxy.log.

The below screenshot is a snippet of the generated log file. The first line is C2 traffic incorrectly being redirected to a default Apache2 landing page, and the second line is after fixing a few things, where the C2 traffic is being correctly routed to the C2 server.