Fuzzing - Automatisiertes Schwachstellen-Testing
Fuzzing (Fuzz Testing) ist eine automatisierte Testmethode bei der ein Programm mit massenhaft zufaelligen oder systematisch mutierten Eingaben bombardiert wird um Crashes, Assertions oder unerwartetes Verhalten auszulösen das auf Sicherheitsschwachstellen hinweist. Coverage-guided Fuzzer wie AFL++ und LibFuzzer maximieren Codeabdeckung. Wichtigste Einsatzgebiete: Netzwerkprotokolle, Dateiparser, Browser-Engines, Kryptobibliotheken.
Fuzzing ist eine der effektivsten Methoden um Sicherheitsschwachstellen in Software zu finden, die manuelle Code-Reviews und statische Analyse übersehen. Ein Fuzzer generiert automatisch massive Mengen an Testfällen, sendet sie an das Ziel-Programm und überwacht ob dieses abstürzt, hängt oder unerwartete Fehler produziert - Indikatoren für Buffer-Overflows, Use-After-Free oder andere speicher-basierte Schwachstellen. Google OSS-Fuzz hat mit Fuzzing über 10.000 Schwachstellen in Open-Source-Software gefunden.
Fuzzing-Arten
Fuzzing-Taxonomie:
1. Black-Box-Fuzzing (kein Source-Code-Zugriff):
→ Generiert Inputs ohne Kenntnis der Programmstruktur
→ Einfach zu starten, aber niedrige Code-Coverage
→ Beispiel: zufällige HTTP-Requests an Web-API
→ Tools: Peach Fuzzer, Boofuzz (Netzwerk-Protokolle)
2. Grey-Box-Fuzzing / Coverage-Guided (Stand der Technik):
→ Instrumentiert das Binary → muss Code-Coverage messen
→ Behält Inputs die neue Codepfade entdecken (genetischer Algorithmus!)
→ Exponentiell effizienter als Black-Box
→ Tools: AFL++ (American Fuzzy Lop), LibFuzzer, HonggFuzz
Ablauf:
a. Fuzzer startet mit Seed-Corpus (echte Testdateien)
b. Mutiert Inputs: bit flips, byte swaps, interessante Werte
c. Führt Programm aus, misst Coverage
d. Neuer Code-Pfad erreicht? → Input in Corpus behalten!
e. Kein neuer Pfad? → Input verwerfen
f. Crash? → Bug gefunden! Input als Crash-Reproducer speichern
3. White-Box-Fuzzing / Symbolic Execution:
→ Analysiert Quellcode + Pfadbedingungen symbolisch
→ Generiert Inputs die exakte Codepfade abdecken
→ Tools: KLEE (LLVM), angr, S2E
→ Sehr rechenintensiv, aber präzise für komplexe Logik
Fuzzing-Kategorien nach Ziel:
□ Dateiparser: PDF, PNG, MP4, Office-Dokumente → LibFuzzer
□ Netzwerkprotokolle: HTTP, TLS, DNS, SMB → Boofuzz, Peach
□ Web-APIs: REST, SOAP, GraphQL → RESTler, CATS
□ Browser-Engines: V8, SpiderMonkey → Domino, Dharma
□ Kryptobibliotheken: OpenSSL, BoringSSL → Google OSS-Fuzz
□ Betriebssystem-Kernel: syscall-Fuzzing → syzkaller
□ Embedded/IoT: Firmware-Fuzzing → FIRM-AFL, Firmfuzz
Coverage-Guided Fuzzing mit AFL++
AFL++ (American Fuzzy Lop++) - Praxis:
Installation:
apt install afl++ # Ubuntu
brew install afl++ # macOS
Schritt 1: Binary instrumentieren (Source verfügbar):
# C/C++ mit AFL-Compiler-Wrapper:
CC=afl-clang-fast ./configure
make
→ Erzeugt instrumentiertes Binary mit Coverage-Tracking
Schritt 2: Seed-Corpus erstellen:
mkdir seeds/
echo '{"user":"test"}' > seeds/basic.json
echo '{"user":"a"}' > seeds/short.json
# Reale Samples → besserer Start!
# Tools: afl-cmin (minimiert Corpus)
Schritt 3: Fuzzing starten:
afl-fuzz -i seeds/ -o findings/ ./target_binary @@
# @@ = Platzhalter für Eingabedatei
# -i: Input-Corpus-Verzeichnis
# -o: Output-Verzeichnis (Crashes, Hangs, Queue)
Schritt 4: Ergebnisse auswerten:
ls findings/crashes/ → Crash-reproduzierende Inputs!
ls findings/hangs/ → Inputs die zu Timeouts führen!
ls findings/queue/ → Coverage-erhöhende Inputs
AFL++-Ausgabe interpretieren:
execs/sec: > 500 → gute Performance
stability: sollte > 90% sein
coverage: wie viele Pfade entdeckt
crashes: Anzahl Crashes → sofort analysieren!
Paralleles Fuzzing (mehrere CPU-Kerne):
# Master:
afl-fuzz -M fuzzer01 -i seeds/ -o findings/ ./target @@
# Slaves (Anzahl = CPU-Kerne - 1):
afl-fuzz -S fuzzer02 -i seeds/ -o findings/ ./target @@
afl-fuzz -S fuzzer03 -i seeds/ -o findings/ ./target @@
→ Teilen Corpus untereinander → bessere Coverage!
Crash-Analyse:
# Reproduzieren:
./target findings/crashes/id:000001
# Debuggen mit AddressSanitizer:
CC=afl-clang-fast ASAN_OPTIONS=... ./configure
make
ASAN_OPTIONS=detect_leaks=0 ./target findings/crashes/id:000001
LibFuzzer (Google/LLVM)
LibFuzzer - Für Bibliotheken und Code-Einheiten:
LibFuzzer-Unterschied zu AFL++:
→ LibFuzzer ist in-process: kein neuer Prozess pro Input
→ Schneller (keine fork()-Overhead)
→ Ideal für Bibliotheken/Parser (libpng, zlib, JSON-Parser)
→ Coverage via LLVM SanitizerCoverage
Fuzz-Target schreiben (C/C++):
// fuzz_target.cpp
#include <stdint.h>
#include <stddef.h>
#include "my_parser.h"
extern "C" int LLVMFuzzerTestOneInput(
const uint8_t *Data, size_t Size) {
// Dieser Code wird mit jedem Fuzzer-Input aufgerufen
parse_json(Data, Size);
return 0; // 0 = kein Crash erwartet
}
Kompilieren und ausführen:
clang++ -g -O1 -fsanitize=fuzzer,address \
fuzz_target.cpp my_parser.cpp -o fuzzer
./fuzzer corpus_dir/ -max_len=4096 -jobs=4
Mit AddressSanitizer (empfohlen!):
-fsanitize=fuzzer,address,undefined
→ Findet Memory-Bugs + Undefined Behavior
Go-Fuzzing (seit Go 1.18, built-in):
func FuzzReverse(f *testing.F) {
f.Add("hello") // Seed
f.Fuzz(func(t *testing.T, s string) {
rev := Reverse(s)
// Invariante: doppeltes Reverse = Original
if Reverse(rev) != s {
t.Errorf("Reverse(%q) = %q, want %q", s, rev, s)
}
})
}
go test -fuzz=FuzzReverse -fuzztime=60s
Python-Fuzzing (Atheris):
pip install atheris
import atheris
import sys
@atheris.instrument_func
def TestOneInput(data):
fdp = atheris.FuzzedDataProvider(data)
input_str = fdp.ConsumeUnicodeNoSurrogates(100)
your_function(input_str)
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()
Web-API Fuzzing
REST-API Fuzzing:
Werkzeuge:
RESTler (Microsoft): automatisches REST-API Fuzzing
→ Liest OpenAPI/Swagger-Spec
→ Generiert Test-Sequenzen basierend auf API-Abhängigkeiten
→ Findet Business-Logic-Fehler, Crashs, Auth-Bypass
CATS (REST API Fuzzer):
→ OpenAPI-basiert
→ 97+ built-in Fuzz-Tests pro Endpoint
→ Findet: XSS, SQLi, SSRF, Zugriffskontrolle-Fehler
ffuf (für HTTP-Endpoints):
# Parameter-Fuzzing:
ffuf -u 'https://api.example.com/user?FUZZ=value' \
-w /usr/share/wordlists/common.txt
# Body-Fuzzing:
ffuf -u 'https://api.target.com/api/create' -X POST \
-H 'Content-Type: application/json' \
-d '{"name": "FUZZ", "type": "user"}' \
-w mutations.txt
Manuelle Fuzzing-Ansätze (Burp Suite):
Intruder → Cluster Bomb für mehrere Parameter:
→ Payload 1: Zahlen 0-65535 (Integer-Overflows)
→ Payload 2: Sonderzeichen (<, >, ', ", null-bytes)
→ Kombiniert: alle Parameter-Kombinationen
Interessante Fuzz-Werte:
Zahlen: 0, -1, 2147483647 (MAX_INT), 2147483648, 9999999999
Strings: "", null, "null", "\x00", "A"*4096 (Buffer)
Arrays: [], [null], sehr große Arrays
Typen: String statt Int, Boolean statt String
Unicode: "\u0000", Emoji, RTL-Zeichen, sehr lange Strings
Continuous Fuzzing (CI/CD Integration)
OSS-Fuzz (Google) - für Open-Source-Projekte:
→ Google betreibt kontinuierliches Fuzzing für Open-Source
→ 1000+ Projekte: OpenSSL, libjpeg, curl, SQLite, etc.
→ Fuzz-Ergebnisse: 10.000+ Sicherheitslücken gefunden
→ Kostenlos für qualifizierte Open-Source-Projekte
Google ClusterFuzz / FuzzBench:
→ Enterprise-Grade distributed Fuzzing
→ Skaliert auf tausende CPU-Kerne
→ Automatische Crash-Deduplication + Triage
CI/CD Integration (GitHub Actions Beispiel):
# .github/workflows/fuzzing.yml
- uses: google/oss-fuzz/infra/cifuzz@master
with:
oss-fuzz-project-name: 'myproject'
fuzz-seconds: 600 # 10 Minuten Fuzzing pro PR
→ Regression-Fuzzing: neuer Code → automatisch gefuzzt
→ Wenn Crash: PR blockiert, Bug-Report erstellt
Best Practices:
□ Seed-Corpus aus echten Produktionsdaten
□ AddressSanitizer (ASAN) + UndefinedBehaviorSanitizer (UBSAN) immer aktiviert
□ Crash-Reproducer speichern + in Regression-Tests aufnehmen
□ Dictionary für Protokoll-Keywords (verbessert Coverage)
□ AFL_PRELOAD + afl-cov für Coverage-Reports