3 minute read

MITM can be pretty easy with Mitmproxy and Python

Introduction

First of all, I would like to link wiki MITM definition. If you are interested in my simple explanation, MITM is a hacker attack, in which there is a client, a server, and that man from the title in the middle. Man in the middle changes the original connection of the user and receives full access to his traffic with the ability to add or remove any stuff in requests and responses.

Mitmproxy — is your swiss-army knife for debugging, testing, privacy measurements, and penetration testing. This is about HTTP and HTPPS requests and touches on not only mobile (iOS/Android), but also OSX, Windows, and Linux apps. Especially nice that it is FREE.

In this note, I won’t go through the advantages of mitmproxy over other similar tools (Charles, scat!), but trust me, it would vanquish any tool out there almost single-handedly.

Preview

Install

brew install mitmproxy

A broad variety of UI

Mitmproxy has three interfaces for every taste:

  1. Cli

     mitmproxy
    
  2. Web (I personally prefer this one)

     mitmweb
    
  3. Log dump

     mitmdump
    

Usage

Preview
  1. Connect to the same Wi-Fi on computer and device

  2. Start mitmproxy on computer

  3. Open Wi-Fi settings on the device and set up a proxy with laptop IP and mitmproxy port (8080 by default)

  4. Open mitm.it on device browser and choose your platform

  5. Then install the downloaded certificate and trust it

Now you can sniff your traffic via mitmproxy.

More information about installing the certificates

Ignore hosts

A useful option when we don’t have to sniff traffic from some hosts, but if we sniff them we can receive a lot of problems. For example, if we don’t ignore:

  • android.clients.google.com, we will have problems with signing into the Play Store
Preview
  • init.itunes.apple.com and itunes.apple.com, we will have problems with signing into the App Store
Preview
  • ppq.apple.com, we will get this pop-up for some apps
Preview

So, let’s ignore these hosts. In order to do it we should set up a little regex at the startup of mitmproxy:

mitmweb --ignore-hosts '^(?:(?!android.clients.google.com|appldnld.apple.com|mesu.apple.com|ppq.apple.com).)*$'

What Python brings?

Python is the game-changer and the killer feature at the same time.

The main things to consider for the quick start are:

  • method request, if we want to modify our requests and/or create new responses
  • method response, if we want to modify server responses
from mitmproxy import http

def request(flow: http.HTTPFlow) -> None:
   print("here will be request modification via flow.request and its params")

def response(flow: http.HTTPFlow) -> None:
   print("here will be response modification via flow.response and its params")

We’re lucky guys ‘cause using these two methods we can control the whole traffic.

Attention! The following examples are really useful but don’t claim to be exhaustive, but only describe some of the possibilities of interaction with the mitmproxy API. Something heavier, and generally anything you want you can find in the official examples.

  • kill requests (e.g.: kill iOS requests for update):
from mitmproxy import http

block_hosts = [ "appldnld.apple.com", "mesu.apple.com" ]

def request(flow: http.HTTPFlow) -> None:
  if any(host in flow.request.host for host in block_hosts):
    flow.kill
  • change query/headers of requests/responses:
from mitmproxy import http

def request(flow: http.HTTPFlow) -> None:
  flow.request.query["mitm"] = "proxy"
  flow.request.headers["newheader"] = "foo"

def response(flow: http.HTTPFlow) -> None:
  flow.response.query["proxy"] = "mitm"
  flow.response.headers["newheader"] = "bar"
  • change response code (e.g.: return 503 for offline emulation):
from mitmproxy import http

def response(flow: http.HTTPFlow) -> None:
  flow.response.status_code = 503
  • emulate slow connection:
from mitmproxy import http
import time

def response(flow: http.HTTPFlow) -> None:
  time.sleep(5.0)
  • redirect requests:
from mitmproxy import http

def request(flow: http.HTTPFlow) -> None:
  flow.request.host = "127.0.0.1"
  flow.request.scheme = "http"
  flow.request.port = 4567
  • create own responses:
from mitmproxy import http

def request(flow: http.HTTPFlow) -> None:
  flow.response = http.Response.make(
    204,
    '{"foo":"bar"}',
    {"Content-Type": "application/json"}
  )

Don’t take seriously

What if we want to flip all the images in the server responses?

import io
from PIL import Image
from mitmproxy import http

def response(flow: http.HTTPFlow) -> None:
  if flow.response.headers.get("content-type", "").startswith("image"):
    s = io.BytesIO(flow.response.content)
    img = Image.open(s).rotate(180)
    s2 = io.BytesIO()
    img.save(s2, "png")
    flow.response.content = s2.getvalue()
    flow.response.headers["content-type"] = "image/png"

Don't take seriously

Conclusion

MITM attacks in real life can be a bit more complicated due to requirement of physical access to the device (hi https). The main thing that we should take from MITM is an analysis of ours, let me highlight, not another’s, but ours traffic. An interesting discovery may be the number of requests with our data flowing to other servers from our own devices and our favourite apps.

If you had any questions or clarifications after reading the note about the MITM or mitmproxy, I’ll be happy to answer them.

So, wish you happy Mondays (:

Updated: