Wiki / Software

mitmproxy

Interactive TLS-intercepting proxy. Sits between a device and its cloud backend to inspect, replay and rewrite encrypted API traffic, scriptable for automation.

mitmproxy is an interactive HTTPS proxy that sits between a device and its cloud backend, letting you inspect, replay and rewrite TLS-encrypted API traffic.

What it is

mitmproxy terminates TLS using its own certificate authority: the client trusts the mitmproxy CA, so mitmproxy can decrypt the connection, show you the plaintext, and re-encrypt onward to the real server. It ships in three faces of one engine, the terminal UI (mitmproxy), the scriptable headless dumper (mitmdump), and a web UI (mitmweb), plus a Python addon API for programmatic request/response manipulation. For a smart device that talks REST or MQTT-over-WebSocket to a vendor cloud, it makes that hidden conversation readable and editable.

Why it matters

Most of a modern IoT product's logic lives in the app-to-cloud API, not on the device. That is where the authentication tokens, the device-provisioning secrets, the firmware-update URLs and the "is this user allowed to unlock the door" checks happen. mitmproxy exposes that channel: you can watch the exact JSON a smart lock sends to open, replay it, tamper a device ID to reach another user's hardware, or strip a flag that gates a paid feature. It turns an opaque encrypted API into an interactive test surface.

Typical workflow

# Start the interactive proxy (default listen port 8080)
mitmproxy --listen-port 8080

# Point the device or phone's Wi-Fi proxy at your host:8080,
# then install the mitmproxy CA on the client from http://mit.it (or mitm.it)

# Headless: record every flow to disk while a tamper script rewrites requests
mitmdump -s tamper.py -w session.flows

# Replay a saved session back at the server
mitmdump -nr session.flows

A minimal tamper addon:

# tamper.py
def request(flow):
    if flow.request.pretty_host.endswith("api.vendor.com"):
        flow.request.headers["X-Debug"] = "1"

def response(flow):
    # flip a gated feature flag in the JSON body
    if b'"premium":false' in flow.response.content:
        flow.response.content = flow.response.content.replace(
            b'"premium":false', b'"premium":true')

Getting the traffic to flow through it

  • Apps that honour system proxy (most phone apps) just need the Wi-Fi proxy set to your host and the CA installed.
  • Devices that ignore proxy settings (most IoT gadgets) need the traffic forced through you: an iptables REDIRECT in transparent mode, a rogue DNS entry, or a rogue AP the device joins.
  • --ignore-hosts passes non-HTTP or undecodable connections straight through, so a stubborn binary protocol does not stall the whole capture.

Pitfalls

  • Certificate pinning is the big wall: a pinned app rejects the mitmproxy CA outright. You bypass it with Frida (runtime unpinning) or a patched APK, not with mitmproxy alone.
  • Non-HTTP TLS (raw MQTT/AMQP over TLS, custom binary protocols) is not HTTP, so the HTTP views are empty; use transparent mode and read the raw TCP, or fall back to wireshark with the key log.
  • Installing a CA lowers the client's security. Do it only on your own lab devices, and remove it afterwards.
  • HSTS and cert-transparency can make browsers refuse the intercepted site even when the CA is trusted; devices usually care less than browsers.

What it pairs with

mitmproxy owns the decrypted application layer. nmap finds the cloud endpoints, wireshark shows the packets it cannot decrypt (and consumes mitmproxy's key material for the ones it can), and Frida handles the pinning bypass that mitmproxy needs but does not provide. Together they cover the full device-to-cloud path.

Further reading