Über unsMediaKontaktImpressum
Sergej Dechand 08. Februar 2022

Schafft modernes Fuzzing bewährte Testing-Methoden ab?

Automatisiertes Fuzz Testing macht es Entwicklern besonders leicht, sichere und robuste Software zu entwickeln. Doch was steckt dahinter?

Je früher man im Entwicklungsprozess eine Schwachstelle findet, desto einfacher ist es, sie zu beheben. Dabei ist das Ziel natürlich, Schwachstellen zu finden, bevor die Software in die Produktion geht. Immer mehr Entwickler setzen dafür auf automatisiertes Fuzz Testing. Das ist eine Methode, bei der die zu testende Applikation mit zufällig generierten Testeingaben ausgeführt wird. Die zu testende Anwendung wird einige dieser Eingaben verarbeiten können und andere abweisen. In einigen Fällen können diese Eingaben jedoch zu Fehlern führen, die für den Entwickler aufschlussreich sind.

Tech-Riesen wie Google haben das Potenzial von feedback-basiertem Fuzzing bereits früh erkannt. Alleine im Chrome-Browser hat Google schon über 29 000 Bugs mit Fuzzing gefunden. Inzwischen ist Fuzzing massentauglich und wird von immer mehr Entwicklerteams in das Testing-Repertoire aufgenommen. Denn im Vergleich zu anderen Sicherheitsmaßnahmen, wie der statischen Codeanalyse, erzielt Fuzzing mit wenig Aufwand deutlich zuverlässigere Testergebnisse, ohne lästige falsch-positive Ergebnisse.

Mit kaum einem anderen Testverfahren lassen sich so viele Bugs aufspüren, wie mit Fuzzing.

  • Google findet mit Fuzzing 29.000 Bugs in Google Chrome [1]
  • Google findet mit Fuzzing 30.000 Bugs in weiteren Open-Source-Anwendungen (OSS-Fuzz) [2]
  • Microsoft findet mit Fuzzing 1.800 Bugs in Microsoft Office [3]
  • Internationale Forscher finden mit Fuzzing 1.000 Bugs im Linux-Kernel [4]

Immer mehr Industriestandards setzen Fuzzing voraus

In sicherheitsrelevanten Bereichen sind Softwarehersteller schon heute dazu verpflichtet, automatisierte Software- und Sicherheitstests durchzuführen. Viele Industriestandards und ISO-Normen empfehlen daher, auch Fuzz Testing in den Entwicklungsprozess zu integrieren. Insbesondere dort, wo die Qualität und Sicherheit von Software über Leib und Leben entscheidet.

Ein gutes Beispiel dafür ist ISO/SAE 21434 und UNECE WP.29, die sich mit der Sicherheit von Automobil-Software befassen. Doch auch in weiteren Branchen, wie zum Beispiel der Luftfahrt, dem Energiesektor, dem Finanzsektor und dem Gesundheitswesen, ist Fuzzing als modernes Testverfahren schon weit verbreitet.

Welche ISO-Normen und Security-Standards empfehlen Fuzz Testing [5]:

  • ISO/SAE 21434 - Straßenfahrzeuge - Cybersecurity
  • UNECE WP.29 - Harmonisierung der Regelungen für Kraftfahrzeuge
  • ISO 26262 - Straßenfahrzeuge - Funktionale Sicherheit
  • ISA/IEC 62443-4-1 - Sichere Anforderungen an den Lebenszyklus der Produktentwicklung
  • UL2900-1 und UL2900-2-1 - Gesundheits- und Wellness-Systeme - Software-Cybersicherheit für vernetzbare Produkte

Standards, die von Fuzzing profitieren (z. B. weil Pentesting erforderlich ist) [6]:

  • ISO/IEC/IEEE 29119 - Software- und Systemtechnik - Softwaretests
  • ISO/IEC 12207 - System- und Softwaretechnik - Software-Lebenszyklus-Prozesse
  • ISO 27001 - Informationstechnologie - Sicherheitstechniken - Informationssicherheits-Managementsysteme​
  • ISO 22301 - Sicherheit und Resilienz - Business-Continuity-Managementsysteme
  • IT-Grundschutz (Germany) - Auf Grundlage von ISO 27001​
  • Und weitere

Allgemeine Empfehlungen für den Einsatz von Fuzzing

  • Bitkom-Leitfaden - "Zur Sicherheit von softwarebasierten Produkten" [7]

Was ist feedback-basiertes Fuzzing?

Fuzz Testing ist hilfreich, um Stabilitätsprobleme und Sicherheitslücken mit wenig Aufwand bereits sehr früh im Entwicklungsprozess aufzuspüren. Das Grundprinzip von Fuzz Testing besteht vor allem darin, Software durch automatisch generierte Testeingaben zum Absturz zu bringen. Die zu testende Software wird dabei von einem sogenannten "Fuzzer" ausgeführt und mit intelligent generierten Eingaben getestet. Dabei werden auch Eingaben abgedeckt, auf die Menschen nicht kommen, sodass sie bei manuellen Tests in der Regel nicht bedacht werden. Zum Beispiel lange oder seltene Zeichenkombinationen für Logins, JSON- oder XML-Dokumente oder andere mögliche Eingaben.

Durch das Instrumentieren des Quellcodes sammeln moderne Fuzzer Informationen über die Struktur der Anwendung. Sie analysieren und markieren dann den Pfad, der eine Eingabe durch das Programm durchläuft. Im späteren Verlauf kann der Fuzzer dieses Wissen nutzen, um seine Testeingaben entsprechend anzupassen, und vor allem, um solche Eingaben zu testen, die mit großer Wahrscheinlichkeit zu Fehlern und Abstürzen der Anwendung führen.

Zudem erhalten moderne Fuzzer Feedback, welche Funktionen von den jeweiligen Eingaben erreicht werden. Mithilfe dieser Information und genetischer Mutationsalgorithmen entwickeln die Fuzzer ihre Eingaben fortlaufend weiter. Wenn der Fuzzer zum Beispiel eine Eingabe findet, die zum Absturz der Anwendung führt, würde er im nächsten Testdurchlauf vielleicht 10.000 ähnliche Eingaben testen, die mit einer gewissen Wahrscheinlichkeit ebenfalls zum Absturz führen könnten. Auf diese Weise ist der Fuzzer in der Lage, Testeingaben intelligent anzupassen, um mit jedem Testdurchlauf noch tiefer in den Quellcode einzudringen.

Wie Fuzzing einen Ethereum-Shutdown verhindern konnte

Fuzz Testing findet Bugs, die andere Testverfahren nicht aufdecken können. So konnte im Source-Code des Ethereum-Netzwerks nur wenige Wochen nachdem dort automatisiertes Fuzz Testing integriert wurde, eine schwerwiegende DoS-Sicherheitslücke behoben werden. In den falschen Händen hätte diese Schwachstelle (CVE-2020-28362) dazu führen können, dass das gesamte Ethereum-Netzwerk heruntergefahren würde. Obwohl es sich um ein speichersicheres Golang-Modul handelt, welches bereits umfangreichen Sicherheitstests unterzogen wurde, konnte diese Schwachstelle erst durch Fuzz Testing gefunden werden [8].

7 Gründe, warum Fuzzing Entwicklern das Leben erleichtert

In den letzten Jahren hat Fuzzing Einzug in die verschiedensten Bereiche der Sicherheitsbranche gefunden. Im Folgenden möchte ich darauf eingehen, warum Fuzzing gerade unter Entwicklern auf offene Arme stößt:

  1. Messbare Code Coverage: Moderne Fuzzing-Plattformen ermöglichen es Entwicklern, die Codeabdeckung von Sicherheitstests zu messen. In der Vergangenheit war ein großer Nachteil dynamischer Softwaretestings die Tatsache, dass es fast unmöglich war, zu sehen, was die Tests erreicht haben. Es konnte sein, dass so gut wie kein Code abgedeckt wurde und die Tests somit nutzlos waren, ohne dass die Entwickler dies bemerken konnten. Bei modernem Fuzzing wird mittels Quellcode-Instrumentierung genau gemessen, welche Pfade und Programmzustände (States) von den Eingaben durchlaufen werden. Somit können Entwickler sicher sein, welche Teile ihrer Software schon getestet wurden.
  2. Hohe Code Coverage und intelligente Testfall-Generierung: Durch die Instrumentierung ist es modernen Fuzzern ebenfalls möglich, Testfälle zu generieren, die deutlich höhere Code Coverage erzielen, als bei herkömmlichen dynamische Testverfahren. Dort wurden Inputs zufällig oder mithilfe von potenziellen Angriffsmustern generiert. Mit diesem Verfahren war es jedoch schwierig, eine hohe Codeabdeckung zu erreichen und in die Tiefen eines Systems vorzudringen. Moderne Fuzzer nutzen das Feedback, das sie von dem instrumentierten Code bekommen, um intelligent mit genetischen Algorithmen Testfälle zu generieren, die vollautomatisch Inputdaten generieren. Damit können diese Fuzzer tief in Programme eindringen und mit wenig Aufwand hohe Code Coverage erzielen.
  3. Keine False Positives: Entwickler können mit anderen Testverfahren, wie zum Beispiel der statischen Codeanalyse, zwar eine sehr hohe Codeabdeckung erreichen, allerdings werden dabei immer enorm viele falsch-positive Testergebnisse generiert. Das liegt daran, dass die statische Codeanalyse Approximationen und Heuristiken nutzt, um potentielle Fehler zu finden. Da der Code dabei nicht ausgeführt wird, ist es dem Programm unmöglich zu garantieren, dass es sich um einen echten Bug handelt und ob und wie man den Fehler triggern kann. Um diese falschen Alarme von den tatsächlichen Bugs zu unterscheiden, ist ein hohes Maß an manuellem Aufwand von Entwicklern nötig.
    Ein erheblicher Vorteil von feedback-basiertem Fuzzing ist, dass für jede Fehlermeldung tatsächlich eine Eingabe als Nachweis und Ursache für den Absturz der Anwendung mitgeliefert wird. Es handelt sich also fast immer um echte Fehler ohne falsch-positive Testergebnisse. Dadurch können Entwickler die relevanten Bugs deutlich schneller erkennen und unschädlich machen.
  4. Einfaches Debugging durch nachweisbare Inputs: Mit Unit-Tests oder statischer Code-Analyse kann Debugging eine zeitintensive und nervenaufreibende Tätigkeit sein. Software-Fehler müssen manuell lokalisiert und nachvollzogen werden, bevor Schwachstellen behoben werden können. Hierfür ist oft viel Expertise notwendig, denn es ist oft nicht klar, wo die Wurzel des Problems ist. Bei der statischen Code-Analyse bleibt auch immer die Sorge, dass man seine Zeit an eine falsch-positive Warnung verschwendet.
    Die hervorragende Eigenschaft von Fuzzern ist, dass es zu jeder Warnung den passenden Input gibt. Entwickler können diesen Input in den Debugger ihres Vertrauens laden und durch das Programm gehen und den Fehler direkt nachvollziehen.
  5. Weniger manueller Aufwand: Bis vor nicht allzu langer Zeit war modernes Fuzz Testing eine Technologie, die ohne das Fachwissen von auf Fuzzing spezialisierten Sicherheitsexperten nicht nutzbar war. Open Source Fuzzer wie AFL oder LibFuzzer waren schon sehr früh sehr gut darin, Sicherheitslücken aufzuspüren. Allerdings benötigten sie dafür lange ein enormes Maß an manueller Konfiguration, Expertenwissen und Detailwissen über die zu testende Anwendung.
    Die Dokumentation und Anwendbarkeit von modernen Fuzzern ist inzwischen jedoch so weit fortgeschritten, dass Fuzzingwissen oft nicht mehr notwendig ist. Aufgrund des hohen Automatisierungsgrades ist es mittlerweile möglich, Fuzz Tests in wenigen Klicks aufzusetzen. Es gibt zudem inzwischen auch professionelle Lösungen für Fuzzing-as-a-Service.
  6. Kontinuierliche Sicherheitstests: Entwickler können Fuzzing in Ihre CI/CD integrieren, um kontinuierliche Sicherheitstests durchzuführen. Moderne Fuzzing-Lösungen bieten dafür verschiedene Integrationen an, zum Beispiel für Jenkins, GitHub, oder Docker. Mithilfe dieser Integrationen werden mit jedem Commit automatisierte Sicherheitstests durchgeführt, bevor dieser mit der Codebase zusammengeführt wird. Somit entsteht ein zyklischer Testing-Prozess, bei dem es Sicherheitsfehler deutlich seltener unbemerkt in die späten Phasen des Entwicklungsprozesses schaffen.
    Ohne kontinuierliche Testzyklen werden diese Sicherheitslücken häufig erst Monate später entdeckt, wenn sich kein Entwickler mehr so richtig erinnert, wie er den Code damals gebaut hat. Sich wieder in seinen alten Code einzudecken, kostet Zeit und Nerven. Viel leichter ist es stattdessen, Bugs unmittelbar – bereits in der Entwicklung – zu beheben. Fuzz Tests sind dabei eine große Hilfe. Da immer erkennbar ist, welche Eingabe zu einem Bug geführt hat, lassen sich Bugs mit wenigen Klicks reproduzieren und beheben. Anstelle von großen und aufwändigen Abnahmetests, können Entwickler ihren Code somit sofort und mit jedem Release testen.
  7. Fuzzing findet Bugs: Der Hauptgrund, warum Fuzzing unter Entwicklern immer beliebter wird, ist schlicht und einfach: Fuzzing findet verdammt viele Bugs! Das zuvor erwähnte Ethereum-Beispiel ist kein Einzelfall. Ähnliche gravierende Sicherheitslücken wurden bei Mozilla Firefox, Linux-Kernel und Microsoft Azure gefunden. Diese Resultate zeigen, dass die selbstlernenden Algorithmen moderner Fuzzing-Software es schaffen, Sicherheitslücken dort zu finden, wo andere Testverfahren versagen.

Welche Bugs findet Fuzzing?

Viele herkömmliche Testverfahren sind darauf ausgerichtet, positive Tests durchzuführen. Bei positiven Tests wird davon ausgegangen, dass nur valide und relevante Eingaben getätigt werden. Mit modernem Fuzzing kann man vor allem negative Tests durchführen, was bedeutet, dass man die zu testende Software mit unerwarteten oder invaliden Inputs füttert. Beispielsweise erfassen negative Tests, wie ein Programm damit umgeht, wenn man in ein numerisches Eingabefeld Buchstaben eingibt. Modernes Fuzzing ist daher besonders wichtig für Anwendungen, die Benutzereingaben oder externe Daten verarbeiten.

Aktuell ist Fuzzing in verschiedenen Sprachen einsetzbar, wie zum Beispiel C/C++, Java, Kotlin, Python und Go. In C/C++ konnte mithilfe von feedback-basiertem Fuzzing eine Vielzahl von Fehlerklassen, inklusive Data Validation Issues und Concurrency Issues, aufgedeckt werden.

Fuzzing ist extrem effektiv darin, Memory-Corruptions in C/C++ zu finden, doch auch in speichersicheren Sprachen, wie zum Beispiel Java, kann Fuzzing eine Vielzahl von Sicherheitslücken aufdecken. Bei diesen Sprachen treten allerdings andere Bug-Klassen in den Vordergrund, die vor allem für die Sicherheit von Web-Anwendungen und APIs relevant sind. Beispiele dafür sind Logic Issues, Audit/Logging Errors, Cookie Issues und die gängigen OWASP Top 10 Bugs [9].

Mit Fuzzing gefundene Schwachstellenklassen

Security Issues in C/C++: Memory Buffer Errors, Data Validation Issues, Pointer issues, Numeric Errors, Concurrency Issues, Bad Coding Practices
 
Security Issues in Java: OWASP Bugs, Data Validation Issues, Denail of Services, Infinite Loops, Uncaught Exeptions, Cookie Issues, Injection Vulnerabilities, Broken Access Control, Vulnerable and Outdated Components, Remote Code Executions (z. B. log4j)

An Fuzzing führt kein Weg vorbei

Modernes Fuzz Testing ist herkömmlichen, statischen und dynamischen Testing-Methoden in vielen Bereichen deutlich überlegen. Kontinuierliche Fuzz Tests ermöglichen einen höheren Automatisierungsgrad, finden mehr Bugs und generieren dabei weniger Fehlalarme. Pentests (egal ob manuell oder automatisiert) sind zwar sehr effektiv, finden Sicherheitslücken allerdings erst gegen Ende des Entwicklungsprozesses, was sowohl den Aufwand erhöht als auch die Entwicklung verlangsamt und Entwickler und Tester in Konflikt bringt.

Das Ziel von Sicherheitstests sollte sein, mit möglichst wenig Aufwand sichere Software zu schaffen.

Das Ziel von Sicherheitstests sollte es jedoch sein, als Team mit möglichst wenig Aufwand sichere Software zu schaffen. Dazu eignet sich eine Kombination von Fuzz Testing und herkömmlichen Testing-Methoden am besten. Zum Beispiel kann man feedback-basiertes Fuzzing gemeinsam mit einer Pentesting Suite wie OWASP ZAP oder Burp anwenden. In kommerziellen Lösungen sind diese häufig integriert und können durch Fuzzing-Tools automatisch erweitert werden. Das Feedback, welches der Fuzzer sammelt, wird dabei genutzt, um Testeingaben systematisch zu steuern und Rückschlüsse über deren Erfolg zu ziehen.

Fuzzing ermöglicht es allen Entwicklern, sichere und robuste Software schnell zu entwickeln und diese mit jedem Commit auf Sicherheitslücken zu testen.

Wird Fuzzing zukünftig Teil jedes Entwicklungsprozesses sein?

Langfristig sollten Automatisierung und Usability dazu führen, dass Entwickler auch ohne Sicherheitsexperten die Sicherheit ihrer Software gewährleisten können. SaaS-Fuzzing wird dies ermöglichen, indem effektive Sicherheitstests automatisiert generiert und durchgeführt werden.
 
Modernes Fuzz Testing ist bereits in vielen Branchen etabliert. Insbesondere die neuen Fuzzing-SaaS-Lösungen werden dazu führen, dass sich die Technologie auf weitere Branchen und Programmiersprachen ausbreiten wird. Wobei es immer Bereiche geben wird, die auf lokale (On-Premise-)Lösungen setzen werden.

Angreifern immer einen Schritt voraus

Auch Angreifer nutzen Fuzzing, um sich unbefugten Zugriff zu Softwaresystemen zu verschaffen. Zukünftig sollte daher jede Applikation (mindestens) einem Fuzz Test unterzogen werden, um Angreifern einen Schritt voraus zu bleiben.

Fuzzing in der Praxis: CVEs aufdecken in wenigen Minuten

Im Folgenden werde ich anhand eines Anwendungsfalles aufzeigen, wie Fuzzing funktioniert. Dazu möchte ich darauf eingehen, wie mithilfe des Open-Source-JVM-Fuzzing-Tools "Jazzer" eine schwerwiegende Sicherheitslücke (CVE-2021-23899) im JSON-Sanitizer gefunden werden konnten. Bei dem JSON-Sanitizer handelt es sich um ein Open-Source-Projekt, das bereits mit den verschiedensten Methoden getestet wurde.

Der JSON-Sanitizer ist eine beliebte Java-Bibliothek, die von Google entwickelt wurde und von OWASP gepflegt wird. Der JSON-Sanitizer dient dazu, beliebige Bytes und Strings in gültige JSON-Formate zu konvertieren. Mithilfe des JSON-Sanitizers können Entwickler sicherstellen, dass Ausgaben keine Substrings enthalten, die Skripte durcheinanderbringen oder sogar XSS-Bugs (Cross-Site-Scripting) verursachen könnten. Sicherheitslücken im JSON-Sanitizer können weitreichende Folgen haben, da dieser in vielen weiteren Anwendungen verwendet wird. Demnach sollten Inputs, die den JSON-Sanitizer durchlaufen, sicher in den Scriptblock eingebettet werden können, wie in der orangfarbenen Box in Abb. 2.

Um den JSON-Sanitizer zu testen wurde in diesem Beispiel ein property-based Fuzzing-Ansatz verwendet. Dabei wird ein String durch den JSON-Sanitizer geschickt und sichergestellt, dass das konvertierte, "sichere" JSON den spezifizierten Substring nicht enthält. Wenn es das täte, wäre die Anwendung anfällig für XSS-Bugs.

Was ist ein Fuzz Target?

Ein Fuzz Target ist ein Eingangpunkt einer zu testenden Anwendung, der während eines Testprozesses die Testeingaben entgegen nimmt. Dies kann die Main-Methode eines Programms sein oder eine Funktion einer API, die aufgerufen werden kann.

XSS-Bug im JSON-Sanitizer (CVE-2021-23899)

Mit Hilfe dieses Fuzz Targets konnte eine ernstzunehmende Schwachstelle gefunden werden. Durch das "Escapen" des ersten Buchstabens eines HTML-Tags in der JSON-Zeichenfolge kann dieser den Sanitizer durchlaufen und den ursprünglichen Tag ausgeben. Es liegt in der Natur von HTML, dass dieses abschließende Skript-Tag den Scriptblock schließt – selbst, wenn es in einem JavaScript-String literal enthalten ist. Wenn anschließend ein neuer Scriptblock geöffnet wird, wird eine Warnung gemeldet und man erhält den XSS-Exploit.

Da der JSON-Sanitizer ein beliebtes Open-Source-Tool ist, wurde er bereits mit vielen verschiedenen Methoden getestet. Dennoch konnte der oben beschriebene XSS-Bug, obwohl er zugegebenermaßen ziemlich leicht zu triggern ist, erst durch Fuzzing entdeckt werden.

Lange Rede, kurzer Sinn: Fuzz Testing hat es ermöglicht, mit sehr geringem Aufwand eine schwerwiegende Sicherheitslücke in einem beliebten und weit verbreitetem Java-Projekt aufzudecken.

Autor

Sergej Dechand

Sergej Dechand kann auf langjährige Forschungserfahrung im Bereich Usable Security am Fraunhofer FKIE und der Universität Bonn zurückblicken.
>> Weiterlesen
Das könnte Sie auch interessieren
Kommentare (0)

Neuen Kommentar schreiben