别急,这不是另一个命令行工具,这次是一个库!😁
更具体地说,FeroxFuzz 是一个结构感知的 HTTP 模糊测试库。
编写 FeroxFuzz 的主要目标是将一些核心组件从 feroxbuster 中剥离出来,放到一个能对其他开发者普遍有用的地方。这样一来,我希望任何想用 Rust 编写 Web 工具和/或一次性 Web 模糊测试器的人,都能以最小的努力做到这一点。
FeroxFuzz 的整体设计源自 LibAFL。FeroxFuzz 实现了 LibAFL: A Framework to Build Modular and Reusable Fuzzers (pre-print) 中列出的大多数组件。当 FeroxFuzz 有所偏离时,通常是为了支持异步代码。
与 LibAFL 类似,FeroxFuzz 是一个可组合的模糊测试库。然而,与 LibAFL 不同的是,FeroxFuzz 只专注于黑盒 HTTP 模糊测试。
下图展示了 FeroxFuzz 所使用的不同组件、钩子以及控制流。

FeroxFuzz 功能非常强大,是为满足我对新版 feroxbuster 的所有规划需求而打造的。不过,随着新版 feroxbuster 相关工作的展开,我预计 FeroxFuzz 的 API 仍会发生变化,至少会有小幅调整。
在 API 稳定之前,破坏性变更 可能 必定会发生。
最简单的入门方式是将 FeroxFuzz 添加到项目的 Cargo.toml 中。
[dependencies]
feroxfuzz = { version = "1.0.0-rc.13" }
除了 examples/ 目录之外,API 文档中还包含各组件及其使用示例的详细文档。
下面的示例(examples/async-simple.rs)展示了使用 FeroxFuzz 编写模糊测试器所需的最少代码。
如果使用源码,可以从 feroxfuzz/ 目录下通过以下命令运行该示例:
注意:除非你的机器上 @ 8000 端口运行着 Web 服务器,否则你需要修改传入
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();
// a `ResponseProcessor` provides access to the fuzzer's instance of `ResponseObserver`
// as well as the `Action` returned from calling `Deciders` (like the `StatusCodeDecider` above).
// Those two objects may be used to produce side-effects, such as printing, logging, calling out to
// some other service, or whatever else you can think of.
let response_printer = ResponseProcessor::new(
|response_observer: &ResponseObserver<AsyncResponse>, action, _state| {
if let Some(Action::Keep) = action {
println!(
"[{}] {} - {} - {:?}",
response_observer.status_code(),
response_observer.content_length(),
response_observer.url(),
response_observer.elapsed()
);
}
},
);
// `Scheduler`s manage how the fuzzer gets entries from the corpus. The `OrderedScheduler` provides
// in-order access of the associated `Corpus` (`Wordlist` in this example's case)
let scheduler = OrderedScheduler::new(state.clone())?;
// the macro calls below are essentially boilerplate. Whatever observers, deciders, mutators,
// and processors you want to use, you simply pass them to the appropriate macro call and
// eventually to the Fuzzer constructor.
let deciders = build_deciders!(decider);
let mutators = build_mutators!(mutator);
let observers = build_observers!(response_observer);
let processors = build_processors!(response_printer);
let threads = 40; // number of threads to use for the fuzzing process