In this article, we’re going to peel back the covers of TLS encryption in transport and monitor encrypted application traffic in real time, in cleartext, with mitmproxy! We’ll look at in-situ monitoring, and decrypting PCAPs.
Mitmproxy
Mitmproxy is a free and open source tool that serves as a TLS terminating proxy, allowing the inspection of traffic without the TLS encryption layer. This is a great tool for debugging as a developer or system administrator, as well as a mainstay in the arsenal of bug bounty hunters and penetration testers. You can’t tell what’s going on exactly inside an TLS-encrypted stream without something like this.
It takes a little fiddling, because generally applications aren’t going to want to trust any old CA or certificate you find on the street. Mitmproxy generates its own self-signed certificates for TLS inspection, so this is going to be a problem we have to tackle.
In steps podman! Running containers give us network namespace isolation and control. As long as we can run our application that needs to be inspected in a container, we’ll be set to proxy its traffic through mitmproxy. We’ll be leveraging one of mitmproxy’s newer features for this: wireguard deployment.
Requirements
To make this work, you’re going to need:
- A linux machine with podman installed
- The wireguard kernel module installed on said machine, to make available to containers
- A basic understanding of container networking
I’ll be doing this on a Fedora host, but this should work well on any modern OS with podman.
The basic structure will be three containers:
- A container running mitmproxy and spawning a wireguard server
- A container to connect to the wireguard server, that we’ll use for its network namespace
- A container running the application that you intend on man-in-the-middling
As a preview, the crux of this operation will be a specific flag on your third, application container: --network container:wireguard, forcing all traffic for the application container to transit through the wireguard tunnel, and to mitmproxy.
The mitmproxy container
Mitmproxy builds and publishes official OCI-compliant containers through its Github registry. These are also available on Dockerhub, but I prefer GHCR.io wherever possible, to sidestep the silly pull limits Docker imposes.
These images are available at: https://ghcr.io/mitmproxy/mitmproxy.
Setup the container
We’ll be doing this with ad-hoc podman run commands, but you could easily script this or employee a container orchestrator. I am choosing this method for the sake of explanation and clarity though.
First pull the image so we don’t have to wait for it at execution time.
sudo podman pull ghcr.io/mitmproxy/mitmproxy:latestNow we need to craft the command to spawn and run our container. The mitmproxy documentation is where I got the information to base this on.
We’ll first create a network namespace:
sudo podman network create mitmproxyThen we’ll create and run our container:
mkdir $HOME/mitmproxy
sudo podman run --rm --replace --name mitmproxy -it -v $HOME/mitmproxy:/home/mitmproxy/.mitmproxy:z --network mitmproxy -p 8080:8080 -p 8081:8081 -e SSLKEYLOGFILE=/home/mitmproxy/.mitmproxy/keylog.txt ghcr.io/mitmproxy/mitmproxy:latest mitmweb --web-host 0.0.0.0We’re exposing ports 8080 and 8081 here so that if we want to use mitmproxy as a conventional proxy rather than a wireguard server, we can (port 8080), and that the web interface is accessible (port 8081). Notably, we’re not exposing port 51820/UDP (the wireguard port), because that is never going to be needed outside the container network namespace. Our client will connect inside the podman network mitmproxy.
Tip
You could instead run mitmdump as your command if you don’t prefer using the web interface. The web interface method as I’ve chosen just gives you the opportunity to spawn other proxy listeners as well, not just wireguard. However, you must call one of the mitmproxy commands, as calling bash as your entrypoint command will prevent mitmproxy from writing it’s CA certificate chain to disk, for some reason.
Notice that we included the environment variable SSLKEYLOGFILE. This command is telling mitmproxy to save negotiated session master keys to a file and then to start the wireguard server. We’ll use these session master keys later to decrypt our TLS traffic.
Note
TLS session keys are different every time you start a “conversation” with an endpoint, even if the TLS certificates are the same. This is because asymmetric keying (public/private keypairs) are computationally expensive. Rather than using that for the entire session, we use something called key exchange (think Diffie-Hellman Exchange (DHE)), which leverages asymmetric encryption to securely exchange a symmetric key (where the key can both encrypt and decrypt data). This symmetric key is what is used for the session, and we’re we’re logging to our key log file. Because it’s dynamic, we need to track what is is each time it’s negotiated.
You should see console output with a token to access your webserver. Copy this URL, and make sure you change the 0.0.0.0 IP to localhost for use in your browser.

After navigating to the web interface, you should see a page like this:

Navigate over to the Capture tab on the left, and scroll down to the Wireguard section. Check Run Wireguard Server and you should see a wireguard configuration pop up. Copy this, as we’re going to use it in our second container.

The wireguard client container
Next we’ll setup a very plain, vanilla podman container that is going to connect to our mitmproxy wireguard server and that’s it. This isn’t where the heavy lifting comes in, we just need this container for its network resources later.
To make this simple, I’ll be using the Linuxserver.io wireguard image. If you haven’t heard of them, here’s my shout-out. They’re a fantastic organization that packages commonly used applications into Alpine-based containers that allow UID and GID mapping, and all share a common base. They all share an Alpine Linux base, which makes them small and lightweight, and if you use multiple LSIO images, the layers are reusable, saving you disk space on down the road. As an avid self-hoster, I adore Linuxserver.io.
Setup the container
Again, we’ll pre-stage our image pull:
sudo podman pull lscr.io/linuxserver/wireguard:latestNow we’re going to setup that wireguard config file that mitmproxy output for us. I am choosing to put this in its own directory so the containers have no chance of clobbering eachother’s files.
mkdir $HOME/mitmclientThen create your config file at $HOME/mitmclient/wireguard.conf
[Interface]
PrivateKey = HMcr0vtmL0CY8DtcPQsDLwm8jHEBQFxlX/oNqiO3FSw=
Address = 10.0.0.1/32
DNS = 10.0.0.53
[Peer]
PublicKey = zhy/BK3T1IvU32VKqPbgT8SVr6r8n9jzawMui2V1bis=
AllowedIPs = 0.0.0.0/1, 128.0.0.0/1
Endpoint = 10.89.0.5:51820Warning
Note that I’ve changed the AllowedIPs key here. I unfortunately ran into an issue where the container would fail to start the wireguard client with an AllowedIPs of 0.0.0.0/0. I’ve broken it up into two chunks here which are mathematically the equivalent of 0.0.0.0/0 and will route everything, but also keep the container happy.
Now we’re ready to spawn our client container!
sudo podman run --rm --name wireguard --network mitmproxy --cap-add=NET_ADMIN --cap-add=SYS_MODULE -e PUID=1000 -e PGID=1000 -e TZ=Etc/UTC --sysctl="net.ipv4.conf.all.src_valid_mark=1" -v $HOME/mitmclient:/config/wg_confs:z -p 3000:3000 -p 3001:3001 lscr.io/linuxserver/wireguard:latestThis should get your client up and running, and that’s all we need it for.
Tip
See how two ports are forwarded in this container, even though we don’t need them here? Since we’ll be using this container for its network namespace, we need to forward ports here that we want accessible in our third, application container. We’ll see this in action momentarily. Just know that if you choose a different container to inspect, forward your necessary ports here.
The application container
Okay, time for our crown jewels. We’ll setup another application container that we’re going to direct all traffic through. This is where we get the goods.
I’m choosing the Linuxserver.io firefox image here, as we’ll just be demonstrating some web traffic interception. You can, of course, switch this for whatever base image suits you and fits your needs, so long as you can install custom certificates on it, and get your desired application to run.
For this container, the only data we’ll need is the certificates created by the first mitmproxy container. We’ll also want a method of saving artifacts and retrieving them on the host machine as well though.
Setup the container
Again, we’ll pull the image first:
sudo podman pull lscr.io/linuxserver/firefoxThen create the directory we’ll use to save our artifacts:
mkdir $HOME/investigationThen finally, run the container:
sudo podman run --rm --name application -it -v $HOME/mitmproxy:/mitmproxy:z,ro -v $HOME/investigation:/investigation --network container:wireguard -e PUID=1000 -e PGID=1000 -e TZ=Est/UTC lscr.io/linuxserver/firefoxThe thing that makes all of this work is the --network container:wireguard argument. DO NOT omit that. This will use podman network namespaces, to force all network traffic for our client out the wireguard setup, forcing it to hit mitmproxy before going anywhere else.
This should get you up and running with firefox accessible from your host machine at http://127.0.0.1:3000. A browser in a browser. Neat. Now, if you were to visit any https site, you’d get a certificate error. This is because mitmproxy is intercepting and decrypting all TLS traffic, then re-encrypting it with its own certificate and serving it to you. We need to install and trust its certificate.
Firefox is a special case because it depends on its own certificate store, but you can do the same thing for other applications that depend on the OS certificate store (most applications). You do this by putting your cert into /usr/local/share/ca-certificates/ and then running update-ca-certificates
Tip
PRO TIP: If you’re on debian or similar and notice this is not working, it’s because Debian expects a .crt file, which while similar, is not the same as a .cer file. We can lean on ol’ trusty openssl to convert it for us though.
Try openssl x509 -inform PEM -in ./yourCert.cer -out ./yourCert.crt to output a .crt version to us in the ca-certificates folder.

Okay, let’s add our certificate. Navigate into your firefox browser’s settings, and head down to Privacy & Security and look for the certificates section. Click View Certificates
Hit the Import button, and navigate over to your bindmount where the certificates directory is mounted in podman, in my case: /mitmproxy. Select the ca-certificate pem file. You could use the ca pem file too, but that contains the private keys and we simply don’t need them here.


After you have your CA certificate imported and trusted, you should be able to browse to TLS endpoints without issue, for example: StackOverflow.

Then, if you jump back over to your mitmweb session, and go to the Flows tab, you should see traffic being intercepted, in cleartext!

This is all well and good, and you now have enough knowledge under your belt to explore further and make the best of this. I have one more cool trick for you though!
The PCAP
Remember when we setup that environment variable: SSLKEYLOGFILE? Yeah, that was to get us a text record of all the negotiated plain master session keys for HTTPS traffic. The contents should look something like this…
SERVER_TRAFFIC_SECRET_0 55a354be6536f619c84b8d4421ed46cdef96f87ff5d0cf76995d361beba7f2a2 4d9a88ea18d446a605a6690f16f7877e1ad2468e041de3eb921481a8279d233e53d3043c92a3eafebb5d3f08e343a4c0
CLIENT_HANDSHAKE_TRAFFIC_SECRET 55a354be6536f619c84b8d4421ed46cdef96f87ff5d0cf76995d361beba7f2a2 0f3edff983ec9d559432c73f2286c822d2315865be5776ede2d599606eed34be68467ac0e11ce65411c943a62442befe
CLIENT_TRAFFIC_SECRET_0 55a354be6536f619c84b8d4421ed46cdef96f87ff5d0cf76995d361beba7f2a2 8ac2c7dd2eb1a12b6d974bf5835987ccd0e8585b5a7b7a9b96b0ffcd80fe25abe71e49c0364cbf3443ab29ca7e49c452
SERVER_HANDSHAKE_TRAFFIC_SECRET fe7394bf895700ba2a0034054f1c71bbcc4b9ef51d940fe9b3f654912bb42f95 3a8df70fb2b283095a36d360dfb2a5ea3b86975d6dd4f31f66685727ea88355730fae24b2a021ec146c1e76e61bd833a
CLIENT_HANDSHAKE_TRAFFIC_SECRET fe7394bf895700ba2a0034054f1c71bbcc4b9ef51d940fe9b3f654912bb42f95 1f653d2da45552a3fd9af9a6b5a309e428ba69c323e883aa1a1ba7965cf4b1c74e5a8efe5d961685b6a8da76e9740268
EXPORTER_SECRET fe7394bf895700ba2a0034054f1c71bbcc4b9ef51d940fe9b3f654912bb42f95 49d7f6493430cd8a92a55f9d50ae4401459b20946698137b1a28f010e202bb608ad887edb33ec2fb8c6d5c32a0b5944f
SERVER_TRAFFIC_SECRET_0 fe7394bf895700ba2a0034054f1c71bbcc4b9ef51d940fe9b3f654912bb42f95 c9168f3181ab11daec61190f5704df63d05539b89b32649106d19df92618209212f9e751fc3359572a3ae53e8bc9bdec
CLIENT_TRAFFIC_SECRET_0 fe7394bf895700ba2a0034054f1c71bbcc4b9ef51d940fe9b3f654912bb42f95 08afeda1710405d5ebee0d4db66d1943707650417b5264bcedddf660ffe0b8b513715ef2af0c3d05adef5948967693ceThis is what the results of numerous DHE key exchanges look like. I’ve included this in here in the case that rather than a simple web browser like firefox, you want to run and inspect the TLS flows of another application that isn’t just straight HTTP requests. You can totally do that, just write yourself a custom Containerfile to build the application and install something like tcpdump. Don’t forget to add your custom certificates! After collecting your pcap, you can load the SSLKEYLOGFILE into Wireshark and it will decrypt the TLS streams in situ!
Here’s a quick example Containerfile to build the app container…
FROM docker.io/debian:bookworm
RUN apt-get update && apt-get install -y tcpdump openssl ca-certificates iproute2 curl wget lynx && apt clean
COPY ./mitmproxy-ca* /usr/local/share/ca-certificates/
WORKDIR /usr/local/share/ca-certificates
RUN openssl x509 -inform PEM -in mitmproxy-ca-cert.cer -out mitmproxy-ca-cert.crt && update-ca-certificates podman run --rm --name app2 -it --network container:wireguard --cap-add=NET_ADMIN --cap-add=NET_RAW -v $HOME/investigation:/investigation:z application:local bashNote the two additional capabilities added: NET_ADMIN and NET_RAW. These are necessary to make tcpdump work correctly inside the container.
Next, examine the interfaces (Hint: they should be exactly the same as your wireguard container).

Then finally, we’re ready to start our pcap. You’re welcome, of course, to implement whatever BPF filters you want here, or circular file rotation, etc. tcpdump has a lot of neat tricks up its sleeve.
tcpdump -n -vv -i wireguard -w /investigation/output.pcap
Okay, assuming you’ve done the above or somehow captured a PCAP of your investigative traffic through mitmproxy, let’s get on to the good part: Wireshark.
Load up Wireshark and navigate to the Settings windows, then Protocols -> TLS. We’ll be loading up whichever file you chose as your SSLKEYLOGFILE environment variable value into the (Pre)-Master-Secret log filename field here.

Now you’re ready to load up your pcap and check out the TLS goodies!

If you can figure out what I was doing in this container here from the screencap, you’re on the right track.
Conclusion
The content that I actually examined here is quite boring. Obviously, when using this for pentesting, bug bounty hunting, or even just reverse engineering your own network apps for fun, you’ll be seeing things more exciting that Firefox captive portal requests and curl requests to google. Hopefully, at least. The notion here was to provide a toolkit for how to get into this, rather than showing the crown jewels myself.
Containers provide use a very easy method of doing this type of work on the spot without any significant reconfiguration of an analyst system. If you set up exposed ports appropriately, you can even connect other host devices directly to your wireguard tunnel, which opens up a slew of other possibilities, like running Android or iOS apps through the mitmproxy tunnel.
Let me know what you find and what you’d like to see, as well as what issues you run into! (Someone is going to be thwarted by certificate pinning, guaranteed…). I’d be happy to write more about in the future.
Support
If you’ve enjoyed this, consider helping support the infrastructure to run it.
Buy me a coffee or three with the button below.
Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.