
Fuzzer guidato dalla copertura degli edge per librerie PHP che rileva bug tramite crash, timeout e avvisi. Supporta la gestione del corpus, la minimizzazione dei crash e i report di copertura del codice.
Questa libreria implementa un fuzzer per PHP, che può essere usato per trovare bug in librerie (in particolare librerie di parsing) fornendo loro input "casuali". Il feedback dalla strumentazione della copertura dei bordi viene utilizzato per guidare la scelta degli input "casuali", in modo tale che vengano visitati nuovi percorsi di codice.
Phar (consigliato): Puoi scaricare un pacchetto phar di questa libreria dalla pagina delle release. L'uso del phar è consigliato, perché evita conflitti di dipendenza con librerie che usano PHP-Parser.
Composer: composer global require nikic/php-fuzzer
Prima di tutto, è necessaria una definizione della funzione target. Ecco un esempio di target per trovare bug in microsoft/tolerant-php-parser:
<?php // target.php
/** @var PhpFuzzer\Config $config */
require 'path/to/tolerant-php-parser/vendor/autoload.php';
// Obbligatorio: Il target accetta una singola stringa di input e la passa alla libreria testata.
// Al target è consentito lanciare normali Exception (che vengono ignorate),
// ma gli Error sono considerati un bug trovato.
$parser = new Microsoft\PhpParser\Parser();
$config->setTarget(function(string $input) use($parser) {
$parser->parseSourceFile($input);
});
// Opzionale: Molti target non mostrano bug su input grandi che non possono anche essere
// prodotti con input piccoli. Limitare la lunghezza può migliorare le prestazioni.
$config->setMaxLen(1024);
// Opzionale: Un dizionario può essere usato per fornire frammenti utili al fuzzer,
// come parole chiave del linguaggio. Questo è particolarmente importante se questi
// non possono essere facilmente scoperti dal fuzzer, perché sono gestiti
// da una funzione di estensione PHP non strumentata come token_get_all().
$config->addDictionary('example/php.dict');
Il fuzzer viene eseguito su un corpus di input iniziali "interessanti", che possono ad esempio essere seminati basandosi su test unitari esistenti. Se non viene specificato alcun corpus, verrà creata una directory temporanea per il corpus.
# Esecuzione senza corpus iniziale
php-fuzzer fuzz target.php
# Esecuzione con corpus iniziale (un input per file)
php-fuzzer fuzz target.php corpus/
Se il fuzzing viene interrotto, può essere ripreso in seguito specificando la stessa directory del corpus.
Una volta trovato un crash, viene scritto in un file crash-HASH.txt. Viene fornito nella forma
in cui è stato originariamente trovato, che potrebbe essere inutilmente complessa e contenere frammenti non
rilevanti per il crash. Pertanto, è probabile che tu voglia prima ridurre l'input che causa il crash:
php-fuzzer minimize-crash target.php crash-HASH.txt
Questo produrrà una sequenza di file minimized-HASH.txt successivamente più piccoli. Se vuoi
controllare rapidamente la traccia dell'eccezione prodotta per un input crash, puoi usare il comando run-single:
php-fuzzer run-single target.php minimized-HASH.txt
Infine, è possibile generare un report di copertura del codice HTML, che mostra quali blocchi di codice nel target vengono colpiti durante l'esecuzione di input da un determinato corpus:
php-fuzzer report-coverage target.php corpus/ coverage_dir/
Ulteriori opzioni di configurazione possono essere visualizzate con php-fuzzer --help.
Mentre il fuzzer è in esecuzione, riporta il suo stato continuamente utilizzando una singola riga di output. Questa riga comprende queste parti nel seguente ordine:
NEW o REDUCED: L'azione che ha attivato questa riga di stato. NEW indica che un nuovo input è stato aggiunto al corpus, mentre REDUCED indica che una voce esistente del corpus è stata sostituita con un input più corto.run: N: Il numero totale di iterazioni di fuzzing (esecuzioni del target) eseguite dall'avvio del fuzzer(N/s): La velocità di esecuzione corrente, misurata in esecuzioni al secondoft: N: Il numero totale di caratteristiche uniche scoperte finora(N/s): Il numero medio di nuove caratteristiche scoperte al secondo dall'avvio del fuzzercorp: N: Il numero di input interessanti attualmente memorizzati nel corpus(%s): La dimensione totale di tutti gli input nel corpuslen: %d/%d: Il primo numero è la lunghezza (in byte) dell'input corrente che ha attivato l'azione, il secondo numero è la lunghezza massima consentita corrente per l'inputt: Il tempo totale trascorso dall'avvio del fuzzer, in secondimem: L'utilizzo corrente di memoria del processo PHPIl fuzzer rileva tre tipi di bug per impostazione predefinita:
Error lanciate dal target del fuzzing. Mentre le eccezioni Exception sono considerate un risultato normale per
input malformati, le eccezioni Error non catturate indicano sempre un errore di programmazione. Sono più comunemente prodotte da
PHP stesso, ad esempio quando si chiama un metodo su null.Error.pcntl_alarm() e un gestore di segnale asincrono che lancia un Error al
timeout.Nota che nessuno di questi controlla se l'output del target è corretto, determinano solo che il target non si comporta in modo eccessivamente scorretto. Un modo per verificare la correttezza dell'output è confrontare due diverse implementazioni che dovrebbero produrre risultati identici:
$fuzzer->setTarget(function(string $input) use($parser1, $parser2) {
$result1 = $parser1->parse($input);
$result2 = $parser2->parse($input);
if ($result1 != $result2) {
throw new Error('Results do not match!');
}
});
Molti dei dettagli tecnici di questo fuzzer sono basati su libFuzzer del progetto LLVM. Quanto segue descrive alcuni dettagli implementativi.
Per funzionare in modo efficiente, il fuzzing richiede un feedback riguardo ai percorsi di codice eseguiti durante il test di un particolare input di fuzzing. Questo feedback di copertura viene raccolto "strumentando" il target del fuzzing. La libreria include-interceptor viene utilizzata per trasformare il codice di tutti i file inclusi al volo. La libreria PHP-Parser viene utilizzata per analizzare il codice e trovare tutti i punti in cui è necessario inserire codice di strumentazione aggiuntivo.
All'interno di ogni blocco di base, viene inserito il seguente codice, dove BLOCK_INDEX è un intero univoco per blocco:
$___key = (\PhpFuzzer\FuzzingContext::$prevBlock << 28) | BLOCK_INDEX;
\PhpFuzzer\FuzzingContext::$edges[$___key] = (\PhpFuzzer\FuzzingContext::$edges[$___key] ?? 0) + 1;
\PhpFuzzer\FuzzingContext::$prevBlock = BLOCK_INDEX;
Questo presuppone che l'indice del blocco sia al massimo di 28 bit e conta il numero di coppie (prev_block, cur_block)
osservate durante l'esecuzione. Il codice generato è purtroppo piuttosto costoso, a causa della necessità di gestire
conteggi di archi non inizializzati e dell'uso di proprietà statiche. In futuro, sarebbe possibile creare un'estensione
PHP in grado di raccogliere il feedback di copertura in modo molto più efficiente.