
Создавайте структурно-осведомлённые black-box HTTP-фаззеры на Rust с компонуемыми мутаторами, планировщиками, наблюдателями, решателями и процессорами для кастомного тестирования веб-приложений и API.
Спокойно, это не очередной инструмент командной строки, это библиотека! 😁
Если точнее, FeroxFuzz — это библиотека HTTP-фаззинга с учётом структуры.
Основная цель написания FeroxFuzz заключалась в том, чтобы вынести некоторые ключевые части feroxbuster в отдельное место, где они могли бы быть полезны другим людям. Благодаря этому я надеюсь, что любой, кто захочет писать веб-инструменты и/или разовые веб-фаззеры на Rust, сможет делать это с минимальными усилиями.
Общий дизайн FeroxFuzz основан на LibAFL. FeroxFuzz реализует большинство компонентов, перечисленных в LibAFL: A Framework to Build Modular and Reusable Fuzzers (pre-print). Когда FeroxFuzz отклоняется от этой схемы, это обычно связано с поддержкой асинхронного кода.
Как и LibAFL, FeroxFuzz является компонуемой библиотекой для фаззинга. Однако, в отличие от LibAFL, FeroxFuzz сосредоточен исключительно на фаззинге HTTP «чёрного ящика».
Ниже представлено визуальное описание различных компонентов, хуков и потока управления, используемых в FeroxFuzz.

FeroxFuzz обладает широкими возможностями и был создан так, чтобы покрыть все мои запланированные потребности для нового feroxbuster. Тем не менее, я всё же ожидаю, что API FeroxFuzz изменится, по крайней мере незначительно, когда начнётся работа над новой версией feroxbuster.
Пока API не стабилизируется, разрушающие изменения могут будут происходить.
Самый простой способ начать — добавить FeroxFuzz в Cargo.toml вашего проекта.
[dependencies]
feroxfuzz = { version = "1.0.0-rc.13" }
В дополнение к папке examples/, в API-документации есть подробное описание компонентов и примеры их использования.
Пример ниже (examples/async-simple.rs) показывает минимально необходимый код для написания фаззера с помощью FeroxFuzz.
Если вы используете исходный код, пример можно запустить из каталога feroxfuzz/ следующей командой:
примечание: если на вашей машине не запущен веб-сервер на порту 8000, вам нужно будет изменить цель, передаваемую в
Request::from_url
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();