Aus Linux-Magazin 05/2019

Das Tool Mitmproxy hilft Webentwicklern bei der Fehlersuche

© Wavebreak Media Ltd, 123RF

Welche Daten zwischen Browser und Server auf der Leitung hin und her sausen, das interessiert nicht nur Spione, sondern auch debuggende Entwickler. Mike Schilli zeigt, wie diese das Tool Mitmproxy mit Python-Skripten auf individuelle Bedürfnisse zurechtschneidern können.

Klappt es mal nicht so ganz beim Entwickeln einer Webapplikation, stellt sich sofort die Frage, welche Daten Browser und Webserver eigentlich austauschen. Hierzu eignen sich Tools zum Schnüffeln im Netzwerk wie Wireshark, aber auch Proxys, die zwischen Client und Server klemmen, dabei Anfragen und Antworten durchleiten und nebenbei fleißig mitschreiben.

Mitmproxy (Man-in-the-Middle-Proxy) ist der Platzhirsch in dieser Kategorie, denn er macht das Unmögliche möglich, da er selbst verschlüsselte HTTPS-Requests mitprotokolliert. Aber zunächst zum einfachsten, unverschlüsselten Fall, für den sich meine noch mit blankem unverschlüsseltem HTTP arbeitende olle Webseite http://perlmeister.com eignet.

Abbildung 1 zeigt, wie der Browser den HTML-Text der Seite sowie einige Bildchen und Javascript-Schnipsel vom Server einholt. Der Terminal-UI von »mitmproxy«, der als Binary [2] zum Download bereitsteht, zeigt links vor dem aktuell ausgewählten und »Flow« genannten Request einen Doppelpfeil. Drückt der User die Enter-Taste, kommen, wie in Abbildung 2 zu sehen ist, die detaillierten Request-Daten hoch.

Abbildung 1: Der Proxy-Server protokolliert die HTTP-Requests des Browsers beim Aufruf einer Seite.

Abbildung 1: Der Proxy-Server protokolliert die HTTP-Requests des Browsers beim Aufruf einer Seite.


Abbildung 2: Auf Tastendruck [Enter] zeigt der Proxy detaillierte Request-/Response-Daten.

Abbildung 2: Auf Tastendruck [Enter] zeigt der Proxy detaillierte Request-/Response-Daten.

Mit den Cursor- oder dem »vi«-typischen [h]+[j]+[k]+[l] fährt der User dann nach rechts oder links und springt zwischen »Request«, »Response« und »Details« hin und her. Zurück zur nächsthöheren Ebene geht es stets mit [Q]+. Auch die Auswahl eines bestimmten Requests auf der höchsten Navigationsebene geht über die Cursortasten (oder [J]+ für runter und [K]+ für rauf). Mit [D]+ löscht er den mit »>>« bezeichneten Flow, und Hämmern auf [D] fegt den Bildschirm leer.

Damit der Proxy anfängt zwischen Client und Server zu lauschen, bietet »mitmproxy« mehrere Ansätze. Die einfachste Lösung ist die Konfiguration auf der Seite des Clients, der typischerweise Optionen für HTTP- oder Socks-Proxys bereithält. Der Aufruf »mitmproxy« startet den Proxy und färbt das laufende Terminalfenster schwarz, die Konsole ist nun betriebsbereit.

Wer jetzt allerdings versucht Chromium auf Ubuntu zum Überstülpen eines Proxys zu überreden, offiziell unter »Settings | Advanced/Security | Open Proxy Configuration«, erlebt sein blaues Wunder, denn die Desktop-Applikation weigert sich mit einer Fehlermeldung (Abbildung 3). Zum Glück akzeptiert das Programm aber die Kommandozeilenoption

Abbildung 3: Auf Ubuntu verweigert Chromium die Proxy-Einstellung im Settings-Menü.

Abbildung 3: Auf Ubuntu verweigert Chromium die Proxy-Einstellung im Settings-Menü.

$ chromium-browser --proxy-server="localhost:8080"

und auf diesem Host (»localhost«) und diesem Port (8080) lauscht Mitmproxy standardmäßig. Wichtig ist, dass Chromium als Einzelinstanz auf dem Ubuntu-Desktop läuft. Wer vorher schon eine laufen hatte, rauft sich die Haare, warum es nicht funktioniert. Ruft der User nach erfolgreicher Konfiguration dann URLs im Browser ab, erscheinen die Daten im Proxy-UI zur weiteren Analyse.

Verschlüsselung knacken

So weit, so einfach. Doch was passiert, falls der Browser einen HTTPS-Request absetzt? Das Protokoll wehrt doch entwurfsgemäß Lauschangriffe von Mittelsmännern ab, und in der Tat meldet ein mit »mitmproxy« konfigurierter Chromium-Browser sofort die laufende Attacke (Abbildung 4).

Abbildung 4: Lauscht Mitmproxy auf einer HTTPS-Verbindung, merkt der Browser dies am gefälschten Zertifikat.

Abbildung 4: Lauscht Mitmproxy auf einer HTTPS-Verbindung, merkt der Browser dies am gefälschten Zertifikat.

Klickt der User aber unter »Advanced« den Link »Proceed (unsafe)«, öffnet Chromium die Schranke und lässt ihn auf eigene Verantwortung weitermachen, worauf Mitmproxy die Verbindungsdaten eifrig mitprotokolliert. Aber, mal dumm gefragt, wie ist das überhaupt möglich, wo doch Client und Server eine verschlüsselte Verbindung aufbauen?

Der revolutionäre Trick von Mitmproxy besteht darin, dass er sich einfach gegenüber dem Client als Server und gegenüber dem Server als Client ausgibt (Abbildung 5). Statt der wirklichen Server-Antwort schickt er dem Client ein gefälschtes Zertifikat. Das führt dazu, dass der Client, wenn er das Zertifikat nicht prüft oder zulässt, dass der User trotz Warnung weitermachen will, eine verschlüsselte Verbindung mit dem Proxy und nicht mit dem Server aufbaut.

Abbildung 5: Mitmproxy hängt sich zwischen Client und Server und präsentiert sich dem Client als Server und dem Server als Client.

Abbildung 5: Mitmproxy hängt sich zwischen Client und Server und präsentiert sich dem Client als Server und dem Server als Client.

Der Proxy kann deswegen die Daten natürlich problemlos entschlüsseln, schreibt sie mit (Abbildung 6) und verschlüsselt sie anschließend wieder zur Weiterleitung an den Server, dem gegenüber er sich als Client ausgibt. Die klassische Man-in-the-Middle-Attacke also.

Abbildung 6: Scheinbar mühelos lauscht Mitmproxy auf verschlüsselten HTTPS-Verbindungen mit.

Abbildung 6: Scheinbar mühelos lauscht Mitmproxy auf verschlüsselten HTTPS-Verbindungen mit.

Härtefälle

Erlaubt der Client auch auf Nachfrage keine Kommunikation über offensichtlich gehackte Kanäle, schiebt ihm der Nutzer ebenso ein gefälschtes Certificate-Authority-(CA)-Zertifikat unter, das das Tool gleich mitliefert. Viele Clients, zum Beispiel Mobiltelefone, erlauben keine Proxy-Konfiguration oder ignorieren sie schlicht. Für diese Fälle bietet Mitmproxy die Möglichkeit des transparenten Proxy-Modus, bei dem die Daten über Netzwerk-Tools wie »IPtables« an den Proxy statt an den Server geleitet werden. Auf [3] finden sich auch Lösungen für diese hartnäckigeren Fälle.

Selbst geschriebene Programme in Go verlassen sich auf den CA-Store des Systems. Dieser kommt auf Ubuntu im Paket »ca-certificates« daher und enthält das Fälscher-CA-Zertifikat von »mitmproxy« von Haus aus natürlich nicht. Als Beispiel setzt Listing 1 einen HTTPS-Request ab, um die TLS-gesicherte Webseite eines bekannten Online-Riesen einzuholen. Bei laufendem Mitmproxy zeigt sich folgende Fehlermeldung, falls der Client wie gezeigt mittels »HTTP_PROXY« auf den Proxyserver eingenordet ist:

Listing 1

client.go

01 package main
02
03 import (
04   "fmt"
05   "io/ioutil"
06   "net/http"
07 )
08
09 func main() {
10   resp, err := http.Get("https://amazon.com")
11   if err != nil {
12     panic(err)
13   }
14   defer resp.Body.Close()
15   body, err := ioutil.ReadAll(resp.Body)
16   fmt.Println(string(body))
17 }

$ go build client.go
$ HTTP_PROXY=localhost:8080 ./client
panic: Get https://usarundbrief.com: x509:
certificate signed by unknown authority

Statt Amazons Zertifikat hat Mitmproxy dem Client ein selbst unterschriebenes untergejubelt, was der Client bemängelt. Damit der standhafte Client das Zertifikat dennoch (nur zu Testzwecken natürlich) akzeptiert, kopiert es die Befehlsfolge in Abbildung 7 in die Sammlung des Ubuntu-Systems. Der nachfolgende Aufruf von

Abbildung 7: Diese Befehlsfolge jubelt dem Ubuntu-System das Fälscher-CA-Zertifikat unter.

Abbildung 7: Diese Befehlsfolge jubelt dem Ubuntu-System das Fälscher-CA-Zertifikat unter.

$ HTTP_PROXY=localhost:8080 ./client

zeigt klaglos die TLS-gesicherte Webseite von Amazon, während Mitmproxy als Mittelsmann mitprotokolliert.

Übrigens nutzt Chrome auf Ubuntu nicht den System-Store für vertrauenswürdige Certificate Authorities (CAs), sondern bringt seine eigene Sammlung mit. Um diese um das Fälscherzertifikat zu erweitern, bedarf es einiger Kniffe, die Mitmproxy erklärt, wenn der User im Browser die magische Domain »mitm.it« bei konfiguriertem Proxy ansteuert und das gewünschte Betriebssystem auswählt (Abbildung 8). Firefox bietet ebenfalls ein Konfigurationsmenü an, um ein neues Zertifikat einzuhängen, und zwar unter »Options |Privacy & Security | View Certificates |Authorities | Import«.

Abbildung 8: Für Instruktionen, um Linux ein Mitmproxy-konformes CA-Zertifikat unterzujubeln, ist hier »Other« zu drücken.

Abbildung 8: Für Instruktionen, um Linux ein Mitmproxy-konformes CA-Zertifikat unterzujubeln, ist hier »Other« zu drücken.

Hausgemachte Erweiterung

Mehr noch, als wahrer Tausendsassa akzeptiert Mitmproxy auch selbst geschriebene Python-Skripte als Erweiterungen. Als Beispiel zeigt Listing 2 ein Skript im von Mitmproxy verlangten Python 3, das die URLs aller eintreffenden Antworten aufschnappt und samt der Länge der zugehörigen Webantwort in Bytes in der Datei »dump.log« ablegt.

Listing 2

URLDumper.py

01 #!/usr/bin/env python3
02 from mitmproxy import ctx
03 import re
04
05 class URLDumper:
06   def __init__(self):
07     ctx.log.warn( "URLDumper ready" )
08
09   def request(self, flow):
10     ctx.log.warn( "URLDumper request" )
11     flow.request.anticache()
12
13   def append_to_dump(self, url, content):
14     with open("dump.log", "a") as f:
15       f.write("%s (%d bytes)\n" %
16               (url, len(content)))
17
18   def response(self, flow):
19     url = flow.request.url
20     self.append_to_dump(url,
21             flow.response.content)
22
23 addons = [
24     URLDumper()
25 ]

Hierzu importiert es in Zeile 2 für spätere Aufrufe der Konsolen-Logging-Funktion das Kontext-Objekt »ctx« aus dem Paket »mitmproxy«, welches das Skript automatisch findet, sobald »mitmproxy« es einbindet, ohne dass der User es irgendwo installieren müsste. Der Proxy kann sogar alle ausgehenden Webanfragen so umschreiben, dass kein Caching stattfindet.

Dabei springt der Proxy vor jedem Request die Methode »request()« in Zeile 9 an, die mit der Variablen »flow« ein Objekt auf den aktuellen Webvorgang mitbekommt und damit die Methode »anticache()« des Request-Objekts aufruft, die Header wie »If-modified-since« einfach verwirft. Trotzdem findet in Browsern manchmal ein Caching statt, weil der Browser nicht beim Server fragt, ob sich eine Abbildung geändert hat, aber ein [Shift]-»Reload« zwingt ihn dazu.

Bei jeder eintrudelnden Antwort springt der Proxy die Methode »response()« in Zeile 18 an. Dort extrahiert Listing 2 erst die URL der Anfrage, holt dann mit dem Attribut »content« den Inhalt der eingeholten Web-Ressource als String ein und gibt beides zum Mitprotokollieren an die Methode »append_to_dump()« ab Zeile 13. Sie öffnet die Datei »dump.log« im Append-Modus und hängt die URL sowie die Länge des Content-Strings in lesbarem Format an. Der Aufruf

$ mitmproxy --mode regular -s URLDumper.py\  console_eventlog_verbosity="warn"

gibt dem Proxy das neue Python-Skript mit und zeigt in der Fußzeile des Proxyfensters kurz nach dem Start die Meldung »URLDumper ready«. Diese dient als eine Bestätigung, dass die Initialisierungsfunktion der Erweiterung erfolgreich durchgelaufen ist.

Der Proxy-Prozess überwacht übrigens das Python-Skript. Ändert es sich zur Laufzeit, merkt der aufmerksame Proxy das und initialisiert das Skript praktischerweise umgehend neu. Nach einer Browser-Session, die »localhost:8080« als Proxy eingestellt hatte und die Startseite des Linux-Magazin besuchte, fanden sich die in Abbildung 9 gezeigten Einträge in »dump.log«.

Abbildung 9: Mit der Erweiterung in <a href="#artRef-l2">Listing 2</a> hat der Proxy in &raquo;dump.log&laquo; mitprotokolliert, welche URLs der Browser beim Besuch auf Linux-Magazin Online angefahren hat.

Abbildung 9: Mit der Erweiterung in Listing 2 hat der Proxy in »dump.log« mitprotokolliert, welche URLs der Browser beim Besuch auf Linux-Magazin Online angefahren hat.

Wer Fehler sucht …

Tritt im Python-Skript ein Fehler auf, entweder beim Kompilieren oder zur Laufzeit, zeigt das Konsolenfenster eine ominöse Nachricht an, nämlich dass Details dazu “im Eventlog” lägen. Einiges Stöbern im Netz brachte die Antwort zutage: Der User tippt dazu

:console.view.eventlog

in das Konsolenfenster. In der Folge wechselt das Fenster zu der gesuchten Fehlermeldung. Diese verrät dem Anwender, welche der Zeilen Python konkret zu bemeckern hatte.

Eine weitere Killeranwendung von Mitmproxy ist das Mitprotokollieren von Batterien von Request-Sequenzen, um sie später zu Testzwecken wieder auf den Server loszulassen. Die Manualseiten – sowohl zur Bedienung der Konsole als auch die Möglichkeiten des API – findet der Hilfesuchende auf http://mitmproxy.org. Ihr Inhalt sieht etwas lieblos zusammengeschustert aus, aber der geduldige Forscher findet meist, was er braucht.

Oh, bevor ich es vergesse: Wer auf seinem System nach dem Testen mit »mitmproxy« keine klaffende Security-Lücke hinterlassen möchte, sollte das zu Testzwecken hinzugefügte CA-Zertifikat nach Abschluss der Tests wieder entfernen. Sicher ist sicher. (uba)

Online PLUS

Im Screencast demonstriert Michael Schilli das Beispiel: https://www.linux-magazin.de/videos/

Der Autor

Michael Schilli arbeitet als Software-Engineer in der San Francisco Bay Area in Kalifornien. In seiner seit 1997 laufenden Kolumne forscht er jeden Monat nach praktischen Anwendungen verschiedener Programmiersprachen. Unter mailto:mschilli@perlmeister.com beantwortet er gerne Fragen.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 4 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
1 Kommentar
Älteste
Neuste Beste Bewertung
Philipp Hahn
7 Jahre her

Bzgl Abbildung 7: Es besteht kein Grund, ein Verzeichnis im Bereich des Paket-Managers selber anzulegen. EInfach asl Benutzer ‘root’ das Zertifikat im PEM-Format in ‘/usr/local/share/ca-certificates/’ mit der Endung ‘.crt’ ablegen und anschließend ‘update-ca-certificates’ aufrufen.

Nach oben