Im ersten Teil der Blogserie haben wir bereits erläutert, was die grundsätzliche Zielsetzung von Fuzzing ist, welche Qualitätsunterschiede hinsichtlich der Abdeckung möglich sind und auf welche Einschränkungen und Hürden wir in unseren Assessments oft stoßen. Im zweiten Teil wurde der Fuzzer Boofuzz vorgestellt und die Anforderungen für einfache erschöpfende Tests erklärt. Der dritte Artikel widmete sich einem konkreten Gerät, wie Boofuzz zum Fuzzen und automatisierten Detektieren von Schwachstellen von Embedded-Webservern verwendet werden kann. Dieser Artikel beschreibt nun die Umsetzung der im dritten Artikel gelösten Problemstellungen und die finale Implementierung.

Im dritten Artikel wurden drei Problemstellungen gelöst, diese werden nun als Funktion in Python abgebildet.

1. Funktion: Starten des Webservers über die Telnet-Schnittstelle

s ist hierbei ein üblicher python socket. Die Funktion until() ist eine Hilfsfunktion, die auf dem Socket liest, bis der übergebene byte-string empfangen wurde.

2. Funktion: Neustarten des Webservers, nachdem ein Absturz detektiert wurde

Die initiale Übertragung des Zeichens \x03 entspricht hierbei der üblichen Zeichenkombination STRG+C zum Abbruch des aktuellen Programms und zum Zurücksetzen der Eingabezeile.

3. Funktion: Detektierung eines Absturzes und Einholen des Stack-Traces

Diese Funktion wird nach jedem Fuzzing-Versuch aufgerufen. Sie liest die Ausgaben des Webservers und untersucht, ob die Stichwörter „SIGSEGV“ oder „SIGBUS“ auftreten. Falls sie auftreten, wird einmal STRG+C gesendet und der Stack-Trace wird durch die Ausführung von dmesg eingeholt und durch die Funktion fuzz_data_logger.log_fail direkt an den aktuellen Testfall angeheftet.

Die Funktionen zur Detektierung und zum Neustart werden dabei direkt der Session übergeben, die Funktion zum initialen Verbindungsaufbau muss getrennt aufgerufen werden:

Zur Instrumentierung der HTTP-Fuzzing-Tests bietet Boofuzz bereits diverse Templates, die Standardfunktionalität wie GET-/POST-Requests und Tests gegen Spezialheader abdecken (Link in das Repository). Beispielsweise implementiert die Datei http_header.py Tests gegen 37 Header, welche teils häufig, aber teilweise bereits sehr speziell sind.

Die Definition des Tests gegen den Header Accept-Encoding sieht beispielsweise so aus:

Angreifer/Tester können einfach beliebig viele dieser Header-Tests in ihr Fuzzing-Script einbauen. Es ist auch möglich, alle 37 direkt aus der Datei durch das import Statement einzubinden:

Nach der Definition der Session können beliebig viele (wir testen generell alle) dieser Header-Tests hinzugefügt werden:

Durch die granulare Zerlegung der einzelnen Aufgaben bleibt der Test somit übersichtlich und der Fuzzer überprüft alle 37 Header innerhalb einiger Stunden.

Durch die anderen öffentlichen Templates http_get.py und http_post.py lassen sich Webserver bereits erstaunlich gut abdecken. In unserem Assessment lösen wir noch zwei weitere Probleme, die allerdings den Rahmen dieser Serie sprengen würden und bei jedem Gerät individuell gelöst werden müssen. Der geneigte Leser sollte solche Probleme allerdings auch innerhalb einiger Stunden/Tage lösen können:

  1. Bisher werden nur allgemeine Requests und Header getestet. Für erschöpfende Tests müssen die speziellen Header und Parameternamen des konkreten Webservers und Gerätes identifiziert und im Fuzzing-Script abgebildet werden. Dabei gibt es mehrere Ansätze:
  • Es ist möglich, den Webserver mit einem Crawler automatisch zu durchlaufen, um Requests zu erkennen.
  • In vielen Fällen ist das Webverzeichnis auf dem Gerät gespeichert und Requests lassen sich durch Parsen des Webroot des Webservers identifizieren.
  • Eine manuelle Methode wäre auch beispielsweise, einmal jede Funktionalität des Webservers mit einem Proxy anzusurfen und aus den Ausgaben des Proxies die Parameter zu ermitteln.
  1. Viele der Funktionen (gerade solche, die intern an andere Programme weitergeleitet werden) benötigen Authentisierung. Je nach Gerät ist dies mehr oder weniger einfach zu automatisieren. Bei vielen Geräten genügt es bereits, ab und zu Nutzername und Passwort zu senden, da die Authentisierung (leider) nur IP-basiert ist. Ein eher schwierigeres Beispiel ist hingegen die Automatisierung bei einer aktuellen FritzBox, wo das Passwort einerseits nicht im Klartext (sondern als pbkdf2-hash durch challenge/response) übertragen wird und andererseits das aktuelle Cookie in jedem Request enthalten sein muss.

Im Falle der FritzBox konnte das Problem durch etwas Graben im JavaScript-Code des Gerätes in einer am Ende übersichtlichen Funktion gelöst werden:

Bei den Fuzzing-Requests wird das so gewonnene Token als POST-Parameter sid übergeben. Es genügt also, an der entsprechenden Stelle der Definition folgenden Parameter einzufügen:

Wenn auf diesem Wege der Fuzzer erst einmal jede verfügbare Funktion des Webservers authentisiert ansprechen kann, sollte jeder verfügbare Parameter rudimentär überprüft werden können. Üblicherweise dauert das 3-7 Tage, je nach Funktionsumfang und Geschwindigkeit des Geräts. Auch wenn klassische Buffer-Overflows eher der Vergangenheit angehören, ist es doch verblüffend, wie oft diese auf Embedded-Geräten noch auffindbar sind. Wie zu Beginn erwähnt, wurde auf diesem Wege eine Schwachstelle in der O2 HomeBox 6441 durch NSIDE entdeckt und die Ausnutzung beschrieben