Fuzzing in Java

In diesem Artikel geht es um das Testing im Bereich Security. Die Idee zu diesem Bericht stammt aus dem Vortrag von Fabian Meumertzheim an der Java User Group Schweiz zum Thema “Fuzzing Java with Jazzer”. Erfahre im Blogpost von Jean-Claude, wie die Testing Methode Sicherheitslücken aufdeckt!

Fuzzing ist eine dynamische Testing Methode, um Sicherheitslücken in der Software aufzudecken. Bei einem Fuzz-Test wird ein Programm mit einem ungültigen, unerwarteten oder zufälligen Input ausgeführt. Das Ziel dabei ist, die Anwendung zum Absturz zu bringen. Fuzzing wird schon länger bei Unternehmen wie Microsoft und Google eingesetzt, um zum Beispiel Security Bugs in den Browser-Implementierungen zu finden.

Historisch – Blackbox Testing

Früher wurde das (zu testende) Programm als Blackbox betrachtet und der Test-Input wurde rein zufällig generiert. Dies ist zeitintensiv und nicht sehr effektiv.

Modernes Fuzzing – Feedback-Based Fuzzing

Ein moderner Fuzzer analysiert den zu testenden Code und generiert daraus automatisch Testfälle (Testdaten), die direkt auf das Programm angewendet werden. Zudem erstellt er dabei eine Code-Abdeckung der ausgeführten Tests. Damit weiss der Fuzzer, mit welchem Input er welche Teile des Codes durchlaufen hat und kann die Erstellung der weiteren Testfälle steuern. Es werden dabei (ohne irgendwelche Start-Testdaten) automatisch Tausende von Testfällen erstellt.

Jazzer

Wir wollen uns Fuzzing nun an einem Beispiel mit Jazzer anschauen. Jazzer ist ein moderner Fuzzer für die Java Virtual Machine (JVM). Er arbeitet direkt auf dem Byte Code und unterstützt via Byte Code Instrumentierung “Feedback-Based Fuzzing”. Jazzer ist zudem Open Source!

Es gibt 3 verschiedene Möglichkeiten, Jazzer zu installieren:

  • Jazzer kann direkt vom Source Code von GitHub installiert werden. Dies ist auf GitHub dokumentiert und funktioniert einwandfrei. Die Voraussetzung dafür ist jedoch die Installation von Bazel, welches Jazzer als Build Tool verwendet.
  • Jazzer kann mittels Binares installiert werden. Links dazu gibt es auch auf GitHub.
  • Jazzer kann mittels Docker installiert werden.

Wir schauen uns nun die Installation mit Docker an. Bei dieser Variante müssen wir nichts anderes als Docker installieren. Als Test-Applikationen nehmen wir jsoup in einer alten Version (1.13.1), die Sicherheitslücken hat. jsoup ist eine Library, die Input HTML parst. Damit ist jsoup optimal für einen Testversuch mit Jazzer geeignet.

Es gibt auf DockerHub bereits Jazzer Images. Eines davon ist das Jazzer-autofuzz , was das direkte Fuzzing einer Library von Maven Central erlaubt. Es wird ein Docker Container gestartet, der die Library direkt von Maven Central holt, das Fuzzing macht und Output über die Tests sowie
Befunde erstellt. Für unser jsoup Beispiel brauchen wir dazu folgende Konfiguration:

  • Name und Version des Maven Artefakts: org.jsoup:jsoup:1.13.1
  • Name der Klasse (Package qualifiziert) und der Methode, die getestet werden soll. Klasse und Methode werden dabei durch ein :: getrennt. In unserem Fall möchten wir in der org.jsoup.Jsoup Klasse die Methode parse testen, was zu folgendem String führt: org.jsoup.Jsoup::parse
  • Der Fuzzing Output wird im Container erstellt. Mittels der -v Option kann dieser beim Starten von Docker in ein lokales Verzeichnis gemappt werden. Hier verwenden wir ein Verzeichnis crashes (relativ zum aktuellen Verzeichnis).
  • Standardmässig läuft Jazzer im Docker “unendlich”. Mit dem Parameter keep_going=n kann jedoch angegeben werden, nach wie vielen Befunden der Container gestoppt werden soll. Wir beginnen mal mit n=1.

Wir starten Docker nun mit den oben beschriebenen Parametern.

docker run \
-v $(pwd)/crashes:/fuzzing \
-it cifuzz/jazzer-autofuzz \
org.jsoup:jsoup:1.13.1 \
org.jsoup.Jsoup::parse \
—keep_going=1

Nach kurzer Zeit erhalten wir folgenden Output:

== Java Exception: java.lang.NullPointerException (1)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:149)
at org.jsoup.helper.DataUtil.load(DataUtil.java:56)
at org.jsoup.Jsoup.parse(Jsoup.java:88)
DEDUP_TOKEN: 02bc2d45ff9bfaaa
== libFuzzer crashing input ==
MS: 0 ; base unit: 0000000000000000000000000000000000000000
artifact_prefix='./';
Test unit written to ./crash-da39a3ee5e6b4b0d3255bfef95601890afd80709 (2)
Base64:
reproducer_path='.';
Java reproducer written to ./Crash_da39a3ee5e6b4b0d3255bfef95601890afd80709.java (3)

Drei Punkte sind wichtig:

  • Wir sehen einen Defekt: eine NullPointerException (für einen leeren Input). (1)
  • Es wurde ein File mit dem Test-Input erstellt. (2)
  • Zusätzlich wurde ein Reproducer erstellt. Das ist ein Stück Java Code, mit dem der Defekt reproduziert werden kann. (3)

Beide Files werden durch die -v Option beim Starten von Docker automatisch in das lokale crashed Verzeichnis kopiert. Hier ein Beispiel eines Reproducers mit nicht leerem Input (erstellt, in dem der Parameter keep_going auf einen höheren Wert gesetzt und einen neuen Docker Run gemacht wird):

File mit Test Input : crash_9cd42147176ec1d4ea37f9eba384e69e303f4ed2

<<]<b<b<<Ao<<Yb<b<b><A<bY<b<m<A<Y ...

Und der dazugehörige Reproducer:  Crash_9cd42147176ec1d4ea37f9eba384e69e303f4ed2.java

public class Crash_9cd42147176ec1d4ea37f9eba384e69e303f4ed2 { 
  public static void main(String[] args) throws Throwable { 
   org.jsoup.Jsoup.parse(“<<]<b<b<<Ao<<Yb<b<b><A<bY<b<m<A<Y ...); 
  } 
} 

Falls die Möglichkeiten von Jazzer mit Docker nicht genügen, kann Jazzer lokal installiert werden. Es werden somit weitere Möglichkeiten definiert, was getestet werden soll. Zudem kann bestimmt werden, wie das Mapping des generierten Inputs auf die testende Methode genau aussieht. Dazu wird ein spezielles Jazzer API verwendet, welches über Maven Central heruntergeladen werden kann. Hier ein Beispiel, wie das für jsoup aussehen könnte: Es wird wiederum die parse Methode getestet und dabei der Jazzer Input (als FuzzedDataProvider) auf einen String gemappt.

import com.code_intelligence.jazzer.api.FuzzedDataProvider;

public class JSoupFuzzer {
  public static void fuzzerTestOneInput(FuzzedDataProvider data) {
    Jsoup.parse(data.consumeRemainingAsString());
  }
}

Dies führt jedoch zum Nachteil, dass wir für alles, was getestet werden soll, eine Fuzzer Klasse schreiben müssen (wir sind eben nicht mehr im jazzer-autofuzz Mode). Ein weiterer Nachteil besteht darin, dass Jazzer nicht automatisch die Artefakte von Maven Central herunterlädt.

Fazit

Fuzzing mit Jazzer ist faszinierend. So können wir Jazzer starten und er fängt ohne jeglichen manuellen Input (Testdaten) an, die Software zu testen. Zudem ist mit der Docker Version die Hürde für eigene Experimente sehr gering. So kann ohne Installation einfach direkt losgelegt werden. Wem das nicht genügt, kann Jazzer lokal installieren. Besonders geeignet ist Jazzer für Libraries, die auf einem definierten Input arbeiten.

Wir hatten die Möglichkeit, Jazzer auch auf einem Firmen-Rechner auszuprobieren. Wir haben verschiedene Dinge getestet und sind auf mehrere Probleme gestossen:

  • Wenn Jazzer lokal von der Source erstellt wird, muss das Build-Tool Bazel installiert werden. Dies braucht Administrationsrechte. Das Tool Bazel sollte von der IT-Infrastruktur nicht geblockt werden. Dies stellte bei uns leider ein Problem dar.
  • Wir nutzten die Docker-Version. Hierbei hatte Jazzer mit dem Herunterladen der Maven Artefakte Probleme. Wir konnten dies fixen, in dem wir das Docker-File für unsere Zwecke manuell angepasst haben.
  • Jazzer ist perfekt geeignet zum Fuzzen einer Library. Wenn jedoch eine Business-Applikation (die Dependency Injection etc. verwendet) schnell getestet werden möchte, geht das nicht so einfach. Es muss auf einzelne Aufrufe beschränkt, dafür jeweils eine Fuzzer Klasse geschrieben und die Test-Methode manuell mit Daten befüllt werden (was sonst via Dependency Injection automatisch passiert wäre).

Dies ist nur eine Momentaufnahme. Das Jazzer Team hat die Vision, Jazzer als Maven Dependency zur Verfügung zu stellen. Dann könnten Fuzz-Tests einfach als JUnit Tests geschrieben werden (z.B, mit einer @FuzzTest Annotation). Die Dependencies könnten wir somit (wie heute schon beim Testing üblich) mocken lassen. Die Zukunft bleibt auf jeden Fall spannend.

Weiterführende Links:

Fuzzing Java with Jazzer – https://www.jug.ch/html/events/2022/jazzer.html

Fuzzing als Security-Testing-Methode – https://www.informatik-aktuell.de/betrieb/sicherheit/fuzzing-als-security-testing-methode.html

Jazzer – https://github.com/CodeIntelligenceTesting/jazzer

jsoup Maven Central –

jsoup – https://jsoup.org

Jazzer auf DockerHub – https://hub.docker.com/search?q=cifuzz%2Fjazzer

Jazzer API –

Kommentare sind geschlossen.