Fuzzing
„Millionen von zufälligen Eingaben auf dein Programm werfen und hoffen, dass es nicht explodiert. Wissenschaft!"
Sinn & Zweck
Fuzzing ist die Kunst, Software mit Müll zu füttern, bis sie sich verschluckt. Automatisiert. Millionenfach. 24/7.
Die Idee ist brutal simpel: Generiere zufällige, ungültige oder unerwartete Eingaben und beobachte, ob das Programm abstürzt, hängt oder sonstiges seltsames Verhalten zeigt. Jeder Crash ist ein potenzieller Bug – und jeder Bug könnte eine Sicherheitslücke sein.
Google hat mit Fuzzing tausende Bugs in Chrome gefunden. OSS-Fuzz hat zehntausende CVEs in Open-Source-Projekten aufgedeckt. Fuzzing funktioniert – auch wenn es sich wie digitaler Vandalismus anfühlt.
Arten von Fuzzing
Black-Box Fuzzing (Dumb Fuzzing)
Keine Kenntnis des Programms. Einfach zufällige Daten rein.
Input: jksd89f7as9d8f7asdf
Input: ∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞
Input: AAAAAAAAAAAAAAAAAAAAAAAAA (buffer overflow, anyone?)
Input: ../../../etc/passwd
Input: <script>alert(1)</script>
Pro: Einfach, braucht keine Vorbereitung Contra: Ineffizient, findet nur oberflächliche Bugs
White-Box Fuzzing
Vollständiger Zugriff auf Source Code. Symbolische Execution.
Generiert Inputs, die spezifische Code-Pfade auslösen – mathematisch berechnet.
Pro: Sehr gründlich Contra: Skaliert nicht, langsam, komplex
Grey-Box Fuzzing (Coverage-Guided)
Der Sweet Spot. Weiß nicht alles, beobachtet aber das Programmverhalten.
Prinzip:
- Starte mit Seed-Inputs
- Mutiere sie zufällig
- Führe das Programm aus
- Messe Code-Coverage
- Behalte Inputs, die neue Code-Pfade erreichen
- Repeat ∞
┌──────────────┐
│ Seed Inputs │
└──────┬───────┘
↓
┌──────────────┐
┌────────→│ Mutator │←────────┐
│ └──────┬───────┘ │
│ ↓ │
│ ┌──────────────┐ │
│ │ Target │ │
│ │ Program │ │
│ └──────┬───────┘ │
│ ↓ │
│ ┌──────────────┐ │
│ │ Coverage │ │
│ │ Feedback │ │
│ └──────┬───────┘ │
│ ↓ │
New path? ┌──────────────┐ │
Yes ←────────│ Analysis │─────────┘
└──────────────┘ No
↓
Crash? Bug!
Fuzzing-Tools
AFL / AFL++ (American Fuzzy Lop)
Der König des Grey-Box-Fuzzing.
# Compile with instrumentation
afl-gcc -o target target.c
# Start fuzzing
afl-fuzz -i seeds/ -o findings/ -- ./target @@
Features:
- Coverage-guided
- Intelligent mutation
- Crash deduplication
- Performance optimiert
LibFuzzer
In-process fuzzing, integriert in LLVM/Clang.
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
MyFunction(data, size);
return 0;
}
clang++ -fsanitize=fuzzer,address -o fuzz_target fuzz_target.cpp
./fuzz_target corpus/
Honggfuzz
Google's Fuzzer, gut für Multi-Prozess-Fuzzing.
OSS-Fuzz
Google's kontinuierliche Fuzzing-Infrastruktur für Open Source.
- 1000+ Projekte
- 10.000+ Bugs gefunden
- Kostenlos für qualifizierte Projekte
Jazzer
JVM-Fuzzing (Java, Kotlin, etc.)
go-fuzz / Fuzz (Go 1.18+)
Native Fuzzing für Go.
func FuzzParseJSON(f *testing.F) {
f.Add([]byte(`{"valid": "json"}`))
f.Fuzz(func(t *testing.T, data []byte) {
var result interface{}
json.Unmarshal(data, &result)
})
}
Atheris
Python-Fuzzing mit Coverage-Feedback.
Boofuzz
Netzwerk-Protokoll-Fuzzing (Nachfolger von Sulley).
Mutation Strategies
Bit Flips
Original: 01001010 01010011 01001111 01001110
Mutated: 01001010 01010010 01001111 01001110
^
Byte Replacement
Original: { "id": 123 }
Mutated: { "id": 0xFF }
Arithmetic Operations
Original: length=100
Mutated: length=101, length=99, length=0, length=-1
Dictionary-based
Interessante Tokens einfügen:
../
..\\
%00
' OR '1'='1
<script>
0xFFFFFFFF
Havoc Mode (AFL)
Kombination mehrerer Mutationen gleichzeitig – maximales Chaos.
Was Fuzzing findet
Memory Corruption
- Buffer Overflows
- Use-After-Free
- Double Free
- Heap Overflow
- Stack Overflow
Crashes & Assertions
- NULL Pointer Dereferences
- Unhandled Exceptions
- Failed Assertions
- Infinite Loops (Timeouts)
Undefined Behavior
- Integer Overflow
- Out-of-bounds Access
- Data Races
Logic Errors
- Unexpected States
- Resource Exhaustion
- Denial of Service
Sanitizers – Die besten Freunde des Fuzzers
Sanitizers instrumentieren den Code und finden Bugs, die nicht sofort crashen würden.
AddressSanitizer (ASan)
Findet Memory-Bugs: Buffer Overflow, Use-After-Free, etc.
gcc -fsanitize=address -o target target.c
MemorySanitizer (MSan)
Findet uninitialized Memory.
UndefinedBehaviorSanitizer (UBSan)
Findet Undefined Behavior: Integer Overflow, etc.
ThreadSanitizer (TSan)
Findet Data Races.
Best Practice: Immer mit ASan und UBSan fuzzen!
Risiken & Grenzen
Coverage ≠ Security
100% Coverage bedeutet nicht 100% sicher. Fuzzing findet, was crasht – nicht, was unsicher ist aber nicht crasht.
Semantic Bugs
Fuzzing ist schlecht bei:
- Business Logic Errors
- Authentication Bypasses
- Authorization Failures
// Fuzzing findet diesen Bug nicht:
if (user.role == "admin" || user.role == "ADMIN") {
// Bug: Case-sensitive, aber sollte es nicht sein
}
State Explosion
Programme mit viel State sind schwer zu fuzzen. Ein Input allein löst nicht alle Pfade aus.
Crash Triage
Fuzzer: "Ich habe 50.000 Crashes gefunden!"
You: "Wie viele sind unique?"
Fuzzer: "Äh... 3?"
Ressourcenhunger
Ernsthaftes Fuzzing braucht:
- CPU (viel davon)
- Zeit (Tage bis Wochen)
- Storage (für Corpus und Crashes)
Reproduzierbarkeit
Manchmal sind Crashes nicht reproduzierbar. Non-determinismus, Timing-Issues, Heisenberg-Bugs.
Vorteile
Skaliert mit Hardware
Mehr CPUs = mehr Fuzzing = mehr Bugs. Parallelisierung ist einfach.
Findet das Unerwartete
Fuzzing findet Bugs, an die niemand gedacht hat – weil niemand so bescheuerte Inputs schicken würde. Außer Angreifer.
Automatisiert & Kontinuierlich
Einmal aufgesetzt, läuft es. 24/7. Ohne Pause. Ohne Urlaub.
Complement zu anderen Methoden
- SAST: Findet Patterns
- DAST: Findet bekannte Vulnerabilities
- Fuzzing: Findet das, was beide übersehen
Real Bugs, Real Crashes
Wenn der Fuzzer einen Crash findet, gibt es den Bug. Kein False Positive. Reproduzierbar.
Best Practices
-
Gute Seeds starten
- Valide Inputs als Basis
- Je mehr Varianten, desto besser
-
Sanitizers aktivieren
- ASan + UBSan mindestens
- Findet Bugs, die sonst nicht crashen
-
Corpus pflegen
- Minimieren (afl-cmin)
- Deduplizieren
- Versionieren
-
Harness schreiben
- Direkt zur Parsing-Funktion
- Keine I/O, keine Netzwerk
- Schnell muss es sein
-
Lange laufen lassen
- Stunden sind Minimum
- Tage/Wochen für tiefe Bugs
- OSS-Fuzz läuft kontinuierlich
-
Crash-Triage automatisieren
- Deduplizierung
- Severity-Bewertung
- Root Cause Analysis
-
In CI/CD integrieren
- Regression Fuzzing
- Neue Bugs sofort finden
Fuzzing in der Praxis
Beispiel: JSON-Parser fuzzen
// fuzz_target.c
#include <json.h>
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
char *input = malloc(size + 1);
memcpy(input, data, size);
input[size] = '\0';
json_object *obj = json_tokener_parse(input);
if (obj) {
json_object_put(obj); // Free
}
free(input);
return 0;
}
Seeds (Corpus)
{"valid": "json"}
[]
{"nested": {"deep": {"value": 123}}}
{"array": [1, 2, 3]}
{"unicode": "こんにちは"}
Run
clang -fsanitize=fuzzer,address -o fuzz_json fuzz_target.c -ljson-c
./fuzz_json corpus/
Fuzzing-Ergebnisse (Real-World)
| Projekt | Tool | Bugs gefunden |
|---|---|---|
| Chrome | libFuzzer, AFL | 27.000+ |
| Firefox | AFL | 7.000+ |
| Linux Kernel | Syzkaller | 5.000+ |
| OpenSSL | OSS-Fuzz | 200+ |
| SQLite | AFL | Dutzende |
Fazit
Fuzzing ist die Chainsaw unter den Security-Tools: Brutal, laut und verdammt effektiv. Es findet Bugs, die kein Mensch und kein statischer Analyzer je finden würde – weil es einfach alles ausprobiert, was ein kreativer Angreifer auch versuchen würde.
Dein Code denkt, er kann mit allem umgehen. Der Fuzzer beweist das Gegenteil.
Weiterführende Ressourcen:
- AFL++
- OSS-Fuzz
- libFuzzer
- The Fuzzing Book – Hervorragendes Online-Buch
- Google Fuzzing Tutorial