In my last post, I documented how my I set up my Linksys router to serve as a firewall for my home IoT lab. This was one of two outstanding issues that I described in my first post about my lab. The remaining unaddressed issue was to set up HTTPS interception.
With a proper firewall in place, my initial lab setup is almost complete. The only thing remaining on my initial goals is setting up TLS/HTTPS interception, which will involve generating a CA, configuring the lab tablet to trust it, configuring
sslspliton the Kali laptop and possibly setting up iptables rules on the router to intercept outbound traffic on port 443. I don’t think that will take too long to configure, so I might end up doing it when I first encounter a device that requires that capability.
In this post I’ll go into how I set up HTTP(S) interception in a way that supports both listening and tampering MITM.
My lab has two primary infrastructure components, a WiFi router that handles NAT, firewalling and DHCP, and a laptop that runs a DNS server for tampering with DNS. In order to be able to read and modify HTTP and HTTPS connections, I needed to add servers for both protocols. In fact I landed on running two servers, Apache and sslsplit, both listening on port 80 and port 443, on separate IP addresses in order to get a more feature rich interception.
The traffic flow is that either the DNS server (via a spoofed record) or the router (via an iptables rule) redirects a client to sslsplit. sslsplit forges a certificate on a local CA if the request is HTTPS, and then forwards the connection to Apache. Apache then desides to either tamper with the request or transparently proxy it to the original destination.
Apache runs on
192.168.2.3, ports 80 and 443. The configuration is basically the default Apache/PHP Ubuntu configuration,
with a few global options changed to enable the right modules:
/var/www/html, there’s a
.htaccess file that contains
the actual logic for how Apache will handle the request. It’s convenient to put this in a
.htaccess file, because these get
automatically reloaded when modified without needing to restart Apache.
.htaccess decides to either proxy the request to the original host or pass the request to a PHP script
based on a series of RewriteRules.
To perform the proxying, is actually quite simple:
RewriteRule at the bottom matches all requests, proxies them (
P), doesn’t match subrequests (
NS), prevents any
other redirect rules from running (
L), and passes through the query string (
There are two exceptions that are not proxied. The first is any request that already has the
Via header set. Because
ProxyVia is on, this will prevent Apache from entering infinite loops if it ever tries to go proxy a request to itself.
The second exception is disallowing any requests to
192.168.0.0/16. This is just to prevent any other weird request patterns,
or cases where this allow requests to bypass some of the router’s firewall rules because they’re getting made by the laptop.
It’s also easy to add new exceptions for specific hosts to prevent them from getting proxied, and instead be served a tampered response.
For requests that don’t get proxied, they get matched by this rule:
This causes any non-proxied requests to get handled by
So, to return a different result for a request all that needs to happen is to add a new
RewriteCond for the target, and then
add the relevant logic to
mitm.php for handling the site. I think this is pretty flexible, because the PHP script could then
chose to further proxy the request and modify it or do anything else it wants.
With Apache configured to be a transparent forward proxy that could tamper with requests, the other piece of the puzzle was to configure an SSL CA and a server to generate leaf certificates on-the-fly:
Creating a CA can be a real pain with the openssl command line tools, so I decided to use
minica instead. Creating a new CA was as easy as:
This spits out a private key and a certificate for both the CA and for the example.com leaf. In this case, we only need the
cakey.key files for passing to
sslsplit is a lot like Moxie’s
sslstrip, but with a
lot more features. Basically, it supports generating certificates signed by the CA in response to new connections, and also
supports a logging the connection contents as PCAP files.
sslsplit included in Kali had some sort of issue with openssl, a simple Dockerfile to build it from
source instead. The invocation I settled on to actually run the tool is:
These flags run
sslsplit in docker with access to the host network. The
sslsplit daemon listens on port 80 and 443 on
192.168.2.4 and proxies both to
192.168.2.3, which is where the Apache proxy is running. It also dumps a pcap file of the
proxied content, and saves a log of the TLS session keys which can be used by Wireshark too.
For HTTP traffic,
sslsplit does some simple processing like removing HSTS headers and also disables a bunch of features like
compression and chunked encoding to make the log more readable. For HTTPS it does the same processing for inner request, and
also handles the TLS handshake. In this case, it clones the snake-oil cert from Apache, and adds any name from the TLS SNI
extension into the SAN field of the cert to make sure that it matches the requested name. For requests that don’t have SNI,
they only get the default cert names, unfortunately, but hopefully this is relatively rare.
With sslsplit and Apache up and running, the only remaining task is to direct traffic into the MITM server. There are two ways that I’ve been doing this – either configuring the DNS server to return the IP address for sslsplit directly, or set up iptables rules on the router to slurp up all traffic for port 80 and 443. I’m not sure which is more useful yet, but having both options is nice. This makes the full flow:
- Direct target traffic to
192.168.2.4using DNS or iptables.
- Optionally, stop if the goal is just to decrypt the traffic.
- Add a
/var/www/html/.htaccessto not proxy the traffic.
- Add relevant logic to
/var/www/html/mitm.phpto return desired results.
I also was able to load the generated CA into the Android table that I’m using for testing, which means that it always trusts the MITM, which is pretty convenient. I’m hoping that most IoT devices don’t do much certificate verification or can be configured to trust other certificates too.
In the end this ended up not being less than a hundred lines of configuration, but connecting all the pipes together ended up being pretty difficult and required some iteration. I’m pretty happy with the flexibility of the resulting system and the relatively fine grained control it provides over the traffic flows.