Wie man Heartbleed hätte finden können

Posted on Posted in Hacker News

Vor einem Jahr machte der Heartbleed-Bug in OpenSSL Schlagzeilen – doch solche Bugs lassen sich mit Hilfe von Fuzzing-Technologien aufspüren. Wir haben das mit den Tools American Fuzzy Lop und Address Sanitizer nachvollzogen und den Heartbleed-Bug neu entdeckt

sp_113345-97928-i_rc

Vor einem Jahr, am siebten April 2014, veröffentlichten die Entwickler von OpenSSL Details zu einem Fehler in ihrer Software. Der Bug, der den Namen Heartbleed erhielt, führte zu einer intensiven Debatte über die Sicherheit wichtiger freier Softwareprojekte. Vielfach wurde dabei die Frage gestellt, warum dieser Fehler nicht früher gefunden wurde – über zwei Jahre befand sich Heartbleed im OpenSSL-Code.

Hätte Fuzzing Heartbleed gefunden?

Eine relativ simple Möglichkeit, Fehler in Software zu finden, ist das sogenannte Fuzzing. Die Idee dabei: Eine Software wird immer wieder mit Eingabedaten aufgerufen, die kleine Fehler enthalten. Stürzt das Programm ab, ist oft ein Fehler gefunden – in vielen Fällen deuten Abstürze auf fehlerhafte Speicherzugriffe hin, die zu Sicherheitslücken führen. Der IT-Sicherheitsspezialist David A. Wheeler hat sich nach Heartbleed ausführlich damit beschäftigt, ob und wie Fuzzing Heartbleed hätte finden können. Der Autor dieses Artikels konnte diese Frage nun in der Praxis beantworten.

Selbstverständlich ist es einfach, einen Fehler zu finden, wenn klar ist, wonach gesucht werden muss. Deshalb war bei diesem Experiment wichtig, dass keine spezifischen Informationen über Heartbleed genutzt werden sollten. So handelte es sich um einen Fehler in der TLS-Erweiterung Heartbeat – die kannte zum damaligen Zeitpunkt aber kaum jemand.

Fehlerhafter Lesezugriff im Speicher

Bei Heartbleed handelt es sich um einen Buffer Overflow beim Lesezugriff auf den Speicher. Solche Pufferüberläufe sind sehr häufig und auch schon seit Jahrzehnten bekannt. Als der Chaos Computer Club vor 30 Jahren das BTX-System der damaligen Deutschen Post hackte, nutzten die CCC-Mitglieder einen Bug, der Heartbleed erstaunlich ähnlich ist – zumindest wenn man ihrer Version der Geschichte glaubt.

Ein einfaches Beispiel für einen Buffer Overflow: Ein Programm nutzt einen Speicherbereich, der zehn Bytes lang ist. Versucht das Programm nun, aus diesem Speicherbereich 20 Bytes zu lesen, greift die Software auf den Speicher zu, der sich daneben befindet. Was dort gerade liegt, ist nicht genau vorhersehbar und hängt von vielen Details ab.

Um ein Programm erfolgreich fuzzen zu können, müssen die entsprechenden Fehler natürlich erkennbar sein, etwa durch einen Programmabsturz. Doch während schreibende Buffer Overflows meistens ein Programm zum Absturz bringen, läuft Software bei lesenden Buffer Overflows oft einfach weiter.

Es gibt eine Reihe von Tools, die eine bessere Erkennung von ungültigen Speicherzugriffen ermöglichen. Ein bekanntes Tool dieser Art ist Valgrind. Allerdings hat es einen Nachteil: Es ist extrem langsam, und die Ausführung eines Programms mit Hilfe von Valgrind dauert etwa zwanzigmal so lang wie sonst. Da man beim Fuzzing ein Programm millionenfach aufruft, ist Valgrind hier impraktikabel.

Address Sanitizer und American Fuzzy Lop

Ein für diesen Zweck besser geeignetes Tool ist Address Sanitizer. Dabei handelt es sich um eine Erweiterung des Compilers. Address Sanitizer ist seit einigen Jahren in den beiden wichtigsten freien C-Compilern GCC und LLVM integriert. Um es zu aktivieren, muss man ein Programm mit dem Parameter -fsanitize=address kompilieren. Auch Address Sanitizer macht ein Programm langsamer, allerdings im Durchschnitt nur um den Faktor 1,8 und ist damit für Fuzzing gut geeignet.

Ein Fuzzing-Tool, das in letzter Zeit Furore machte, ist American Fuzzy Lop. Entwickelt wurde es von Michal Zalewski vom Google-Sicherheitsteam, der auch unter seinem Nickname Lcamtuf bekannt ist. Mit Hilfe von American Fuzzy Lop wurden schon unzählige Sicherheitslücken in diversen Programmen gefunden.

Das Tool fügt dem zu testenden Programm spezielle Instruktionen hinzu, die es erlauben, Codepfade zu erkennen. Wenn American Fuzzy Lop bei einer bestimmten Eingabe einen neuen Codepfad erkennt, wird diese Eingabedatei als Startpunkt für das weitere Fuzzing genutzt.

Das Tool ist allerdings bisher nicht in der Lage, direkt Netzwerkeingaben zu fuzzen; es arbeitet lediglich auf Dateibasis. OpenSSL besitzt ein Kommandozeilentool, mit dem es etwa möglich ist, den Parser für Zertifikate und einige andere Dinge zu fuzzen, aber Heartbleed ist ein Fehler beim TLS-Handshake. Um hier mit einem Fuzzer anzugreifen, ist etwas mehr Arbeit nötig.

OpenSSL mit sich selbst reden lassen

Der Autor dieses Artikels hat hierfür ein kleines Tool erstellt, das zwei Instanzen von OpenSSL miteinander einen Handshake durchführen lässt. Dabei findet keine echte Netzwerkkommunikation statt, die Handshake-Pakete werden lediglich über den Arbeitsspeicher hin- und hergereicht. Das Tool speichert dabei alle Handshake-Pakete als Dateien ab, ein normaler Handshake führt zu vier Datenpaketen. Zusätzlich besteht die Möglichkeit, auf der Kommandozeile Dateien zu übergeben und dadurch jeweils einen Teil des Handshakes zu ersetzen. Damit ist es möglich, den Handshake zu fuzzen.

Will man American Fuzzy Lop und Address Sanitizer in Kombination benutzen, gibt es ein paar Hürden. Address Sanitizer nutzt für seine Funktionalität einen mehrere Terabytes großen, virtuellen Speicherbereich. Da es sich nur um virtuellen Speicher handelt, ist dies kein direktes Problem. American Fuzzy Lop limitiert jedoch den virtuellen Speicher, den ein Programm nutzen darf. Es gibt verschiedene Ansätze, wie sich dieses Problem angehen lässt, sie haben aber alle Nachteile. In unserem Fall haben wir eine sehr einfache Lösung gewählt: Wir haben das Speicherlimit von American Fuzzy Lop deaktiviert (Parameter -m -1).

Das ist nicht ganz ungefährlich: In seltenen Fällen kann es dazu kommen, dass ein Fuzzing-Input dazu führt, dass ein Programm den gesamten Speicher eines Systems aufbraucht. Das kann auf dem System zu Programmabstürzen führen; in der Praxis kommt dies allerdings selten vor. Es sollten auf dem entsprechenden System nicht gleichzeitig wichtige Arbeiten durchgeführt werden.

Fuzzing des TLS-Handshakes

Damit kann der Fuzzing-Vorgang beginnen. Die letzte für Heartbleed anfällige OpenSSL-Version ist 1.0.1f. Diese muss mit Hilfe von American Fuzzy Lop kompiliert werden; dafür müssem in der Makefile die Aufrufe von gcc durch afl-gcc ersetzt werden. Um Address Sanitizer zu aktivieren, muss vorher die Umgebungsvariable AFL_USE_ASAN auf 1 gesetzt werden (AFL_USE_ASAN=1; export AFL_USE_ASAN).

Anschließend wird das Test-Tool selftls mit American Fuzzy Lop kompiliert und statisch gegen OpenSSL gelinkt. Der OpenSSL-Compile erzeugt hierfür zwei Dateien libssl.a und libcrypto.a, die wir dafür nutzen:

afl-gcc selftls.c -o selftls libssl.a libcrypto.a -ldl

Ein einfacher Aufruf des Test-Tools führt dazu, dass sechs Dateien mit den Namen packet-1 bis packet-6 erstellt werden. Nur die Pakete eins bis vier enthalten Daten, die letzten beiden sind leer. Nur der initiale Teil des Handshakes soll gefuzzt werden. Durch den Aufruf von ./selftls 1 packet-1 kann ein Handshake mit dem ausgegebenen Paket simuliert werden. Die Datei packet-1 wird nun in einem Verzeichnis abgelegt, in unserem Beispiel das Verzeichnis in. Nun kann der eigentliche Fuzzing-Vorgang gestartet werden:

afl-fuzz -i in -o out -m -1 -t 5000 ./selftls 1 @@

Mit -i und -o werden das Ein- und das Ausgabeverzeichnis definiert, -m -1 schaltet das Speicherlimit ab und -t 5000 erhöht den Timeout, da ein TLS-Handshake vergleichsweise langsam abläuft. Beim Programmaufruf von selftls wird das @@ von American Fuzzy Lop durch die jeweilige Eingabedatei ersetzt.

Nach sechs Stunden ein Treffer

In unseren Tests lieferte American Fuzzy Lop circa sechs Stunden später das erste Ergebnis: Ein manueller Aufruf von selftls mit der Ausgabedatei ergibt einen Stack Trace und weitere Informationen von Address Sanitizer – ein Buffer Overflow, verursacht durch einen fehlerhaften Aufruf von memcpy() in der Funktion tls1_process-heartbeat(). Es ist der Heartbleed-Bug.

Einige erwähnenswerte Dinge sind beim Testen aufgefallen: Nach der Entdeckung von Heartbleed wurde mehrfach erwähnt, dass OpenSSL ein eigenes Speichermanagement nutzt, das angeblich dazu führte, dass Schutztechnologien ausgehebelt würden. Insbesondere eine E-Mail von OpenBSD-Entwickler Theo de Raadt wurde viel zitiert, in der dieser schrieb, dass OpenSSL“Umgebungstechnologien für Exploit-Schutzmechanismen” habe. Das OpenSSL-eigene Speichermanagement lässt sich beim Kompilieren durch den Parameterno-buf-freelist deaktivieren.

Zunächst sind wir davon ausgegangen, dass dies notwendig ist, damit Address Sanitizer korrekt funktioniert. Es stellte sich aber heraus, dass auch ohne diese Option Address Sanitizer den Bug erkennt. Zwar besitzt OpenSSL ein eigenes Speichermanagement, aber trotzdem wird offenbar für jeden zugewiesenen Puffer ein eigener Speicherbereich alloziert. Die Details des OpenSSL-Speichermanagements sind in einem Blogbeitrag von Chris Rohlf erläutert.

Randomisierung abzuschalten war nicht notwendig

American Fuzzy Lop wird beim Fuzzen des Test-Tools mit einer roten Zahl vor variablen Programmaufrufen warnen. Das bedeutet, dass das Programm sich manchmal bei identischen Eingabedaten unterschiedlich verhält. Der Grund dafür ist, dass der TLS-Handshake bei der Erstellung des sogenannten Master Secrets und in der RSA-Funktion Zufallszahlen nutzt. Um dies zu verhindern und den Handshake deterministisch ablaufen zu lassen, kann die Zufallszahlenfunktion von OpenSSL gepatcht werden und permanent lediglich den Wert Eins ausgeben. In unseren Tests benötigte American Fuzzy Lop etwa gleich lang zum Finden von Heartbleed, unabhängig davon, ob der Zufallszahlengenerator deaktiviert wurde oder nicht. Trotzdem erscheint es generell sinnvoll, zum Fuzzen von kryptographischen Anwendungen den Zufallszahlengenerator zu deaktivieren und keine echten Zufallszahlen ausgeben zu lassen.

Erwähnenswert ist außerdem, dass American Fuzzy Lop ein relativ neues Tool ist. Zum Zeitpunkt als Heartbleed gefunden wurde, war es zwar bereits verfügbar, allerdings kannte es kaum jemand. Die frühen Versionen waren zudem deutlich komplexer zu bedienen und hatten einige Fallstricke, insbesondere die Zusammenarbeit mit Address Sanitizer funktionierte nicht problemlos. Mit der damaligen Version hätte sich Heartbleed also nicht so einfach finden lassen.

Lernen für die Zukunft

Einen direkten Nutzen hat unser Experiment natürlich nicht, denn Heartbleed wurde längst gefunden und umfangreich analysiert, der OpenSSL-Code wurde gefixt und die meisten Systeme sind heute nicht mehr verwundbar. Allerdings lässt sich aus dem Experiment einiges lernen. Mit ähnlichen Strategien sollte es möglich sein, in Zukunft Heartbleed-ähnliche Bugs in anderer Software zu finden. Address Sanitizer und American Fuzzy Lop sind sehr mächtige Tools, die jeder Softwareentwickler kennen und nutzen sollte.

Eine ausführlichere englischsprachige Beschreibung des Fuzzing-Experiments hat der Autor in seinem Blog veröffentlicht. Außerdem hat er im vergangenen Jahr das Fuzzing Project mit dem Ziel gestartet, mehr Bugs in freier Software mittels Fuzzing zu finden. Dort gibt es auch ein Einsteiger-Tutorial.

Quelle: Golem.de

Facebooktwittergoogle_plus