Im ersten Teil der Blogserie wurde 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. Das Ziel dieses Artikels ist es, den Fuzzer Boofuzz vorzustellen, der auf dem „Sulley Fuzzing Framework“ aufbaut und viele Möglichkeiten für Gray-Box-Ansätze bietet, wie wir sie oft in unseren Analysen vorfinden.

Vor allem die folgenden vier Eigenschaften machen den Fuzzer zu einem mächtigen Werkzeug:

  • Die Fuzzing-Eingaben werden automatisch generiert. Pro Parameter werden hier tausende Eingaben versucht (die teilweise auf dem korrekten Wert des untersuchten Parameters beruhen). Wenn man dies mit der manuellen Suche nach sinnvollen Fuzzing-Eingaben vergleicht, ist hier eine weitaus größere Abdeckung möglich.
  • Es werden verschiedene Möglichkeiten angeboten, um die Fehlerentdeckung einerseits automatisiert zu übernehmen, aber auch, um diese komplett selbst zu implementieren. So können zusätzliche Debugging-Kanäle selbst eingebaut werden, um die Entdeckungsrate zu verbessern und im Fall von Abstürzen direkt detaillierte Debug-Ausgaben zu erhalten.
  • Es ist zudem möglich, nach der Erkennung von Fehlern das Fuzzing-Ziel neu zu starten. Dies muss zwar manuell implementiert werden, jedoch nehmen Fuzzingtests für gewöhnlich mehrere Tage in Anspruch. Wenn es also möglich ist, das Ziel automatisiert neu zu starten, kann dies ohne den Eingriff eines Analysten geschehen. Es muss lediglich der Bericht am Ende des Fuzzings interpretiert werden.
  • Boofuzz speichert alle Ein- und Ausgaben für eine Fuzzing-Analyse und bietet ein übersichtliches Webinterface, in welchem die relevanten Informationen gebündelt betrachtet werden können.

Ein vorgefertigtes Beispiel des Projekts zeigt auf, wie einfach es ist, simple Fuzzingtests auf Protokollebene mit Boofuzz einzurichten. Der folgende Code-Ausschnitt ist Teil eines Templates für TFTP-Fuzzing:

Das TFTP-Protokoll ist trivial aufgebaut. Das Code-Schnipsel nimmt als Grundlage für den Fuzzing-Test die folgende Download-Anfrage:

Dabei sind die im Bytestring enthaltenen Zeichen \x00 und \x01 Steuerzeichen und in ASCII keinem Zeichen zugeordnet. Im Python-Code werden diese mit der Funktion s_static() übergeben. Das bedeutet, dass Boofuzz keine Fuzzing-Tests gegen diese durchführt, sondern nur die beiden Zeichenketten filename und netascii, die mit s_string() definiert wurden, untersucht werden.

Startet man diesen Fuzzing-Test, stellt Boofuzz ein nützliches Webinterface zur Verfügung, welches den Status und Informationen zum aktuell laufenden Test anzeigt:

(Quelle: NSIDE)

Im Screenshot wird ersichtlich, dass aktuell der Fuzzing-Test 1836 von 9780 (global) bzw. von 3912 (im aktuellen Test RRQ, den der Codeausschnitt definiert) ausgeführt wird. Nach dem aktuellen Status ist es möglich, einzelne Tests zu betrachten. Konkret wird in dem Fall der Test mit Nummer 18 angezeigt, bei dem für den Parameter filename offenbar %n Format-String-Identifier gesendet wurden. Wäre die TFTP-Server-Anwendung also anfällig für eine Format-String-Schwachstelle im Parameter filename, so hätte dieser Testfall den Server bereits zum Absturz gebracht.

Leser, die mit dem TFTP- und speziell mit dem UDP-Protokoll vertraut sind, werden an dieser Stelle zu Recht einwenden, dass der Fuzzing-Test womöglich gar keine Abstürze detektieren kann (UDP ist zustandslos und der Fuzzer kann gar nicht feststellen, ob am anderen Ende eine Anwendung lauscht). Das ist korrekt. Für vollautomatisierte Tests müssten die Abstürze detektiert und die Anwendung neu gestartet werden. Ein Tester/Angreifer müsste noch Funktionsrückrufe implementieren, die diese Schritte automatisiert übernehmen. Im Code-Schnipsel können solche Funktionen bei der Initialisierung der Session übergeben werden.

Funktionsrückruf zur Erkennung von Abstürzen

Das Fuzzing-Skript in seiner aktuellen Ausprägung greift einen TFTP-Server auf dem lokalen System (127.0.0.1) an. Der einfachste Weg, einen Absturz zu detektieren, wäre somit, nach jedem Testfall zu überprüfen, ob der Server noch ausgeführt wird. Dazu könnte man schlicht das Linux-Utility ps nutzen: ps aux |grep tftp und in der Ausgabe überprüfen, ob der Server noch läuft. Sollte dies nicht mehr der Fall sein, könnten noch Logdateien untersucht werden, ob Informationen zum Absturz verfügbar sind. Diese können an Boofuzz übergeben werden, um sie direkt mit dem aktuellen Testfall abzuspeichern.

Funktionsrückruf zum Neustarten nach Abstürzen

Boofuzz registriert Abstürze beispielsweise, indem die zuvor beschriebene Funktion einen Absturz meldet. Sobald das geschieht, wird vor dem nächsten Test die Funktion zum Neustarten des Servers ausgeführt. Im aktuellen Fall wäre dies wieder sehr leicht gelöst – indem der lokale TFTP-Server wieder gestartet wird.

Es lässt sich abschließend feststellen, dass es mit Boofuzz einfach möglich ist, Protokolle, bei denen kein Zustand (also ohne beispielsweise Authentifizierung) beachtet werden muss, schnell abzubilden. Die drei Teilaufgaben „Definieren des Protokolls“, „Funktionsrückruf zum Detektieren von Abstürzen“ und „Funktionsrückruf zum Neustarten“ genügen in diesen Fällen, um eine Anwendung recht erschöpfend gegen Angriffe mit unerwarteten Eingaben zu testen. Im nächsten Artikel dieser Serie wird ein konkretes Beispiel beschrieben, wie es möglich ist, die drei Teilaufgaben im Embedded-Webserver einer O2 HomeBox 6441 zu erledigen. Im letzten Teil wird dann beschrieben, wie die Teilaufgaben implementiert wurden, um den Webserver zu fuzzen.