
Baue strukturbewusste Black-Box-HTTP-Fuzzer in Rust mit komponierbaren Mutators, Schedulers, Observers, Deciders und Processors für kundenspezifische Web- und API-Tests.
Entspann dich, es ist kein weiteres Kommandozeilen-Tool, sondern eine Bibliothek! 😁
Genauer gesagt ist FeroxFuzz eine strukturbewusste HTTP-Fuzzing-Bibliothek.
Das Hauptziel beim Schreiben von FeroxFuzz war es, einige Kernkomponenten aus feroxbuster herauszulösen und an einen Ort zu bringen, an dem sie für andere allgemein nützlich sein können. Meine Hoffnung dabei ist, dass jeder, der Web-Tools und/oder einmalige Web-Fuzzer in Rust schreiben möchte, dies mit minimalem Aufwand tun kann.
Das Gesamtdesign von FeroxFuzz ist von LibAFL abgeleitet. FeroxFuzz implementiert die meisten der in LibAFL: A Framework to Build Modular and Reusable Fuzzers (Pre-print) aufgeführten Komponenten. Wo FeroxFuzz abweicht, liegt das in der Regel an der Unterstützung von asynchronem Code.
Ähnlich wie LibAFL ist FeroxFuzz eine komponierbare Fuzzing-Bibliothek. Anders als LibAFL konzentriert sich FeroxFuzz jedoch ausschließlich auf Black-Box-HTTP-Fuzzing.
Nachfolgend siehst du eine visuelle Darstellung der verschiedenen Komponenten, Hooks und des Kontrollflusses, die von FeroxFuzz verwendet werden.

FeroxFuzz ist sehr leistungsfähig und wurde entwickelt, um alle meine geplanten Anforderungen für ein neues feroxbuster zu erfüllen. Ich erwarte jedoch weiterhin, dass sich die API von FeroxFuzz zumindest geringfügig ändern wird, sobald die Arbeit an der neuen Version von feroxbuster beginnt.
Bis sich die API gefestigt hat, können werden Breaking Changes auftreten.
Der einfachste Weg, loszulegen, besteht darin, FeroxFuzz in die Cargo.toml deines Projekts aufzunehmen.
[dependencies]
feroxfuzz = { version = "1.0.0-rc.13" }
Zusätzlich zum Ordner examples/ enthalten die API-Dokumente eine umfangreiche Dokumentation der Komponenten sowie Beispiele für deren Verwendung.
Das folgende Beispiel (examples/async-simple.rs) zeigt das absolute Minimum, um mit FeroxFuzz einen Fuzzer zu schreiben.
Wenn du den Quellcode verwendest, kannst du das Beispiel aus dem Verzeichnis feroxfuzz/ mit dem folgenden Befehl ausführen:
Hinweis: Wenn auf deiner Maschine @ Port 8000 kein Webserver läuft, musst du das in
Request::from_urlübergebene Ziel ändern.
cargo run --example async-simple
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// create a new corpus from the given list of words
let words = Wordlist::from_file("./examples/words")?
.name("words")
.build();
// pass the corpus to the state object, which will be shared between all of the fuzzers and processors
let mut state = SharedState::with_corpus(words);
// bring-your-own client, this example uses the reqwest library
let req_client = reqwest::Client::builder().build()?;
// with some client that can handle the actual http request/response stuff
// we can build a feroxfuzz client, specifically an asynchronous client in this
// instance.
//
// feroxfuzz provides both a blocking and an asynchronous client implementation
// using reqwest.
let client = AsyncClient::with_client(req_client);
// ReplaceKeyword mutators operate similar to how ffuf/wfuzz work, in that they'll
// put the current corpus item wherever the keyword is found, as long as its found
// in data marked fuzzable (see ShouldFuzz directives below)
let mutator = ReplaceKeyword::new(&"FUZZ", "words");
// fuzz directives control which parts of the request should be fuzzed
// anything not marked fuzzable is considered to be static and won't be mutated
//
// ShouldFuzz directives map to the various components of an HTTP request
let request = Request::from_url(
"http://localhost:8000/?admin=FUZZ",
Some(&[ShouldFuzz::URLParameterValues]),
)?;
// a `StatusCodeDecider` provides a way to inspect each response's status code and decide upon some Action
// based on the result of whatever comparison function (closure) is passed to the StatusCodeDecider's
// constructor
//
// in plain english, the `StatusCodeDecider` below will check to see if the request's http response code
// received is equal to 200/OK. If the response code is 200, then the decider will recommend the `Keep`
// action be performed. If the response code is anything other than 200, then the recommendation will
// be to `Discard` the response.
//
// `Keep`ing the response means that the response will be allowed to continue on for further processing
// later in the fuzz loop.
let decider = StatusCodeDecider::new(200, |status, observed, _state| {
if status == observed {
Action::Keep
} else {
Action::Discard
}
});
// a `ResponseObserver` is responsible for gathering information from each response and providing
// that information to later fuzzing components, like Processors. It knows things like the response's
// status code, content length, the time it took to receive the response, and a bunch of other stuff.
let response_observer: ResponseObserver<AsyncResponse> = ResponseObserver::new();