Skip to main content
  1. Concetti/

Fuzzing - Coverage-Guided, Mutation-Based

·6 mins
Alessio Barnini
Author
Alessio Barnini
Table of Contents

Mappa Globale
#

Cos'e il Fuzzing
#

Il fuzzing e una tecnica di testing dinamico che invia input casuali, malformati o inattesi a un programma per trovare crash, errori non gestiti, memory corruption e potenziali vulnerabilita di sicurezza. Non verifica il comportamento corretto: cerca attivamente i casi in cui il programma si comporta in modo imprevisto.

Il principio e semplice: i software vengono testati con input validi, ma gli attaccanti usano input non validi. Il fuzzing simula l'attaccante in modo automatizzato, generando milioni di variazioni di input in tempi che un tester umano non potrebbe mai coprire.

Gibson lo colloca nel contesto del Dynamic Analysis e del DAST (Dynamic Application Security Testing): e l'applicazione dell'analisi dinamica alla ricerca sistematica di bug di sicurezza.

Dev parallel: Il fuzzing e come unit test con edge cases generati automaticamente, ma per security invece che per correttezza funzionale. In Node.js, fast-check e una libreria di property-based testing che genera input random per verificare invarianti: "questa funzione non deve mai crashare con qualsiasi input stringa". Il fuzzing di sicurezza fa la stessa cosa ma cerca crash, overflow e injection. Hai gia fatto testing con input limite (stringa vuota, null, integer overflow)? Il fuzzer lo fa per milioni di combinazioni in automatico.

Approcci: Black-box, White-box, Grey-box
#

La quantita di informazioni disponibili sul target determina quale approccio e possibile.

flowchart LR
  subgraph BLACK[Black-box Fuzzing]
    BB_IN[Input generator\ncasuale]
    BB_TARGET[Target\n(binary opaco)]
    BB_OUT[Crash / Exception\nrilevati]
  end

  subgraph WHITE[White-box Fuzzing]
    WB_SRC[Codice sorgente\no LLVM IR]
    WB_ANA[Analisi statica\n+ symbolic execution]
    WB_IN2[Input generati\nper coverage]
    WB_TARGET2[Target\n(con instrumentazione)]
  end

  subgraph GREY[Grey-box Fuzzing]
    GB_IN[Input mutati\nda corpus]
    GB_TARGET[Target\n(instrumentato per coverage)]
    GB_FB[Coverage feedback\nguida le mutazioni]
  end

  BB_IN --> BB_TARGET --> BB_OUT
  WB_SRC --> WB_ANA --> WB_IN2 --> WB_TARGET2
  GB_IN --> GB_TARGET --> GB_FB --> GB_IN
ApproccioAccesso al codiceProControEsempio tool
Black-boxNo (solo binario)Funziona su qualsiasi target, replica prospettiva attaccanteEsplorazione casuale, bassa coverageBoofuzz, random fuzzer
White-boxSi (sorgente o IR)Massima coverage teorica, trova bug profondiLento (symbolic execution), scala maleKLEE, SAGE
Grey-boxNo, ma coverage feedbackVeloce + guidato, migliore del black-boxRichiede strumentazione del binarioAFL++, libFuzzer

Il grey-box (coverage-guided fuzzing) e il punto di equilibrio preferito in pratica: non richiede il codice sorgente, ma usa l'instrumentazione del binario per capire quali branch di codice vengono eseguiti e guidare la generazione di nuovi input verso branch non ancora esplorati.

Tipi di Fuzzing per Generazione dell'Input
#

Mutation-Based Fuzzing
#

Il mutation-based fuzzer parte da un corpus di input validi e li muta in modo sistematico: bit flip, byte swap, inserimento di sequenze speciali, troncamento, duplicazione. Non capisce la struttura dell'input: la corrode.

Esempio: per fuzzare un parser PDF, il corpus contiene PDF validi. Il fuzzer produce varianti: cambia i magic bytes, corrompe la lunghezza di un oggetto, inserisce stringhe lunghissime nei metadati. Qualcuno di questi input modificati potrebbe causare un buffer overflow nel parser.

Vantaggio: non serve conoscere il formato dell'input. Svantaggio: se l'input corretto richiede una firma valida (es. checksum) il fuzzer produce molti input che vengono rifiutati prima ancora di essere processati in profondita.

Generation-Based Fuzzing
#

Il generation-based fuzzer costruisce input da zero partendo da specifiche formali: un RFC per un protocollo di rete, uno schema JSON o XML, una grammatica. Capisce la struttura e genera variazioni semanticamente valide ma con valori ai bordi.

Vantaggio: maggiore probabilita che l'input superi la validazione iniziale e raggiunga il codice piu profondo. Svantaggio: richiede di scrivere o trovare le specifiche.

Coverage-Guided Fuzzing
#

Il coverage-guided fuzzer combina mutation-based con feedback sulla code coverage. Dopo ogni input, monitora quali branch di codice sono stati eseguiti. Preferisce le mutazioni degli input che hanno esplorato branch nuovi rispetto a quelli gia coperti. Nel tempo, massimizza la copertura del codice con il minor numero di esecuzioni.

AFL (American Fuzzy Lop) ha reso questo approccio popolare nel 2014. AFL++ e la versione moderna e attivamente sviluppata.

Tool Principali
#

AFL++ (American Fuzzy Lop++)
#

AFL++ e il fuzzer coverage-guided piu usato nel 2024. Istrumenta il binario (o compila il sorgente con instrumentazione LLVM), esegue milioni di varianti di input al secondo e mantiene un corpus di input "interessanti" (quelli che hanno esplorato nuovi branch).

# Compilazione con instrumentazione AFL++
AFL_USE_ASAN=1 afl-clang-fast -o target_fuzz target.c

# Avvio del fuzzing
afl-fuzz -i corpus/ -o findings/ -- ./target_fuzz @@

Trovato ampiamente usato per: browser (V8, SpiderMonkey), librerie di parsing (libpng, libtiff), codec audio/video, parser XML. Molte CVE critiche scoperte con AFL.

libFuzzer
#

libFuzzer e il fuzzer in-process di LLVM. A differenza di AFL++ che lancia il processo per ogni input, libFuzzer chiama ripetutamente una funzione LLVMFuzzerTestOneInput nello stesso processo. Questo lo rende molto piu veloce per certi target.

// Funzione target per libFuzzer
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
  parseInput(data, size);  // La funzione da fuzzare
  return 0;
}

OSS-Fuzz di Google usa libFuzzer per il fuzzing continuo di migliaia di librerie open source critiche.

Boofuzz
#

Boofuzz e un framework Python per il fuzzing di protocolli di rete. Permette di definire la struttura del protocollo (header, campi, tipi) e genera automaticamente variazioni malformate di ogni campo.

Ideale per: protocolli custom, dispositivi IoT, servizi di rete che non hanno sorgente disponibile.

DAST come Fuzzing per Web App
#

ZAP (OWASP Zed Attack Proxy) e Burp Suite Active Scan sono DAST tool che implementano fuzzing per le web application. Inviano input malformati ai parametri HTTP (GET/POST), agli header e ai cookie per trovare SQL injection, XSS, path traversal e altre vulnerabilita.

ToolTipoUso principale
OWASP ZAPDAST / Web FuzzerOpen source, integrazione CI/CD
Burp Suite ProDAST / Web FuzzerManuale + attivo, industry standard
AFL++Coverage-guidedBinary fuzzing, C/C++
libFuzzerCoverage-guidedIn-process, integrato nel sorgente
BoofuzzNetwork protocolDispositivi di rete, IoT
Peach FuzzerGeneration-basedFormato-specifico, OT/ICS
CERT BFFBlack-boxFuzzing di file format

Fuzzing in Pipeline DevSecOps
#

Il fuzzing si inserisce nel pipeline CI/CD come step aggiuntivo dopo il SAST (Static Application Security Testing). L'ordine tipico:

flowchart LR
  CODE[Commit\nCode]
  SAST2[SAST\nCodeQL/Semgrep\nanalisi statica]
  SCA[SCA\nDependency scan\nSnyk/Trivy]
  BUILD[Build\n+ Unit Tests]
  FUZZ[Fuzzing\nlibFuzzer/AFL++\nO DAST per web]
  DAST2[DAST\nZAP Active Scan\nsu staging]
  DEPLOY[Deploy\nto Production]

  CODE --> SAST2 --> SCA --> BUILD --> FUZZ --> DAST2 --> DEPLOY

OSS-Fuzz: Google mantiene una piattaforma di fuzzing continuo per librerie open source critiche (OpenSSL, libpng, Freetype, cURL, ecc.). Esegue i fuzzer 24/7 e segnala automaticamente i crash. Ha trovato migliaia di vulnerabilita nelle librerie piu usate al mondo.

Corpus management: un buon corpus di input validi e la differenza tra un fuzzer che trova bug in ore e uno che non trova nulla in giorni. Il corpus si costruisce raccogliendo input reali dal traffico di produzione, da test suite esistenti, e dagli input che hanno trovato nuovi branch in sessioni precedenti.

Dev parallel: fast-check in TypeScript/JavaScript e Hypothesis in Python sono librerie di property-based testing: generano input random e verificano che la funzione rispetti certe proprieta (non crashare mai, output sempre nel range atteso, idempotenza). E fuzzing applicato alla correttezza funzionale invece che alla sicurezza. La differenza: il fuzzer di sicurezza cerca crash e memory corruption, il property tester cerca violazioni delle invarianti logiche. Puoi usare entrambi: property-based test nel CI ordinario, fuzzer di sicurezza in un job dedicato su un cluster con timeout lungo.

Related