Schwachstellensuche mit Fuzzing

Seite 2: Weniger ist mehr

Inhaltsverzeichnis

Eine wesentliche Strategie beim Fuzzing ist daher, die Menge der zu testenden Eingabedaten - auch Eingaberaum genannt - möglichst weit zu reduzieren. Je kleiner der Eingaberaum ist, desto größer ist die Wahrscheinlichkeit, in akzeptabler Zeit einen Programmabsturz herbeizuführen.

Effizientes Fuzzing setzt demnach eine gründliche Analyse des verwendeten Datenformats beziehungsweise Protokolls voraus. Der Tester bekommt dadurch häufig auch eine Vorstellung, wo Programmfehler zu erwarten sind und welche Felder in den Eingabedaten und Protokollen für das Fuzzing interessant sind. An die dazu nötigen Informationen gelangt man beispielsweise durch Dokumentation, Quellcode, Disassemblierung oder Protokollieren des Netzwerkverkehrs. Dies ist auch der Grund für die Vielzahl der hoch spezialisierten Fuzzing-Tools wie beispielsweise CSS-Die für die Stylesheet-Unterstützung von Browsern, AXman für das ActiveX-API des IE oder IRCfuzz für Chatprogramme (siehe Tabelle). Der Artikel "Die Axt im Walde" auf heise Security befasst sich näher mit ausgewählten Tools und ihrem praktischen Einsatz.

Im Raum der möglichen Eingabedaten genau einen der wenigen Fehlerzustände zu treffen, ist bei reinen Blindschüssen eher unwahrscheinlich und damit sehr langwierig.

Diese Überlegungen führen direkt zu einer weiteren Extremvariante des Fuzzing: der Datenmutation. Grundidee dabei ist, bekannte gültige Eingabedaten an zufälligen Stellen zu verändern. Dadurch ist sichergestellt, dass die mutierten Eingabedaten in den meisten Arbeitsschritten des Programms Fehler provozieren können. Diese Methode funktioniert in der Regel sogar ohne eingehende Beschäftigung mit dem Datenformat.

Die Veränderung genügend vieler gültiger Eingabedaten kann schnell zum Erfolg führen. Fehler durch völlig mutierte Daten sind mit diesem Verfahren jedoch kaum zu entdecken.

Allerdings ist auch bei der Datenmutation ein gezieltes Vorgehen sinnvoll. Die wahllose Mutation von Bilddaten etwa produziert fast ausschließlich Pixelfehler, die nur selten zum Programmabsturz führen. Für Mutationen, die die Länge der Daten verändern, ist wiederum die Analyse des Datenformates etwa zur Korrektur von Längenangaben nötig. Im Allgemeinen ist diese Methode jedoch nahezu blind für Fehler, die erst durch sehr stark mutierte Daten ans Licht kommen. Praxisnahe Ansätze verfolgen daher einen Mittelweg zwischen wahllosem Fuzzing und reiner Datenmutation.

In einem wegweisenden Paper beschreibt der Sicherheitsexperte Dave Aitel eine universelle Analysemethode für Netzwerkprotokolle: die blockbasierte Protokollanalyse [4]. Sie macht sich zunutze, dass die meisten gängigen Web-Protokolle letztlich als Folge von Blöcken aus Längenangaben und Datenfeldern übertragen werden, selbst wenn sie wie beispielsweise CSS in HTML in HTTP ineinander verschachtelt sind.

Die Grundidee der blockbasierten Protokollanalyse: Auch ineinander verschachtelte Datenstrukturen sind als lineare Folge von Bytes darstellbar.

Man schreibt dazu ein Programm, das bei jedem Durchlauf mit Zufall angereicherte Pakete im Protokollformat ausspuckt. Die einzelnen Datenfelder des Protokolls werden durch Funktionsaufrufe ersetzt, die passende Inhalte wie Integer-Zahlen, Zeichenketten oder Konstanten ausgeben. Die Strukturierung der Datenfunktionen durch Aufrufe von Blockfunktionen macht es beispielsweise möglich, Längenangaben innerhalb der Blöcke mit Hilfe eindeutiger Bezeichner automatisch anzupassen.

Bei der blockbasierten Protokollanalyse gibt es zwei gegensätzliche Herangehensweisen. Die arbeitsintensivere, aber erfolgreichere Methode ist die vollständige Nachbildung eines gut analysierten Protokolls durch die passenden Funktionsaufrufe. Dadurch erreicht man Fuzzing mit einem sehr sorgfältig ausgewählten Eingaberaum. Die wesentlich schneller umsetzbare Variante ist, sich aus einem mitgeschnittenen Datenpaket lediglich einzelne vielversprechende Blöcke und Datenfelder herauszupicken und durch die nötigen Funktionsaufrufe zu ersetzen. Der Rest des Paketes wird einfach als konstanter Wert übernommen. Dies entspricht der Datenmutation in ausgewählten Feldern eines Paketes.

In der Praxis beginnt ein Tester in der Regel mit der zweiten Methode, ohne sich intensiv mit den Protokollspezifika auseinandergesetzt zu haben. Als besonders leicht identifizierbare und ergiebige Felder haben sich beispielsweise Längenangaben erwiesen, deren Fuzzing mit vergleichsweise hoher Wahrscheinlichkeit zu Pufferüberläufen führt. Durch zusätzliche Analysen von Quellcode und Binärprogramm kann sich der Tester Feld für Feld in Richtung einer vollständigen Funktionaldarstellung des Protokolls vorarbeiten, wie sie sich bei der ersten Methode ergeben hätte. Kritische Programmfehler finden sich aber meist schon lange vorher.

Auf dieser Grundlage hat Aitel das Fuzzing-Framework Spike in der Programmiersprache C entwickelt. Es stellt Funktionen für verschiedene Datentypen und Blockstrukturen sowie diverse Netzwerkfunktionen bereit. Für das Fuzzing der Datenfelder zieht es sowohl Zufallswerte als auch bestimmte Zeichenketten und Zahlenwerte heran, die typische Abstürze etwa durch unzureichende Filterung oder Integer-Überläufe provozieren. Diverse Protokollnachbauten liegen dem Framework ebenfalls bei, etwa für MS-RPC, CIFS, IMAP und PPTP.

Die blockbasierte Protokollanalyse ist jedoch unabhängig von Spike. Sie liefert auch eine Grundlage für vollständig selbst entwickelte Fuzzing-Tools in anderen Programmiersprachen. Ihre Prinzipien lassen sich auf andere Bereiche als Netzwerkprotokolle verallgemeinern, beispielsweise für das Fuzzing von Dateiformaten, und auch andere Fuzzing-Frameworks machen sich die Methode zunutze.