已发布 11 个版本
1.0.0-rc.12 | 2023 年 10 月 14 日 |
---|---|
1.0.0-rc.10 | 2023 年 6 月 29 日 |
1.0.0-rc.8 | 2023 年 1 月 19 日 |
1.0.0-rc.4 | 2022 年 11 月 24 日 |
0.1.0-rc.0 | 2022 年 9 月 3 日 |
#72 in Testing
29 每月下载量
1.5MB
16K SLoC
🚀 FeroxFuzz 🚀
结构感知 HTTP 模糊测试库
🤔 另一个 ferox?为什么? 🤔
别担心,这不是另一个命令行工具,这是一个库! 😁
更具体地说,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
需要而制作的。然而,我仍然预期 FeroxFuzz 的 API 将会发生变化,至少在开始开发新版本的 feroxbuster
的时候。
在 API 稳定之前,可能会有破坏性的变化。
入门
开始的最简单方法是将其包含在项目的 Cargo.toml
中。
[dependencies]
feroxfuzz = { version = "1.0.0-rc.11" }
文档
除了 examples/
文件夹外,API 文档还详细介绍了组件及其用法示例。
- FeroxFuzz API 文档:FeroxFuzz 的 API 文档,由本仓库中的文档注释自动生成。
- 官方示例:FeroxFuzz 的专用可运行示例,非常适合深入理解特定概念,并且注释详尽。
示例
以下示例(examples/async-simple.rs)展示了使用 FeroxFuzz 编写 fuzzer 的最小化示例。
如果使用源代码,可以使用以下命令从 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(
"https://127.0.0.1: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
// the `Fuzzer` is the main component of the feroxfuzz library. It wraps most of the other components
// and takes care of the actual fuzzing process.
let mut fuzzer = AsyncFuzzer::new(threads)
.client(client)
.request(request)
.scheduler(scheduler)
.mutators(mutators)
.observers(observers)
.processors(processors)
.deciders(deciders)
.post_loop_hook(|state| {
// this closure is called after each fuzzing loop iteration completes.
// it's a good place to do things like print out stats
// or do other things that you want to happen after each
// full iteration over the corpus
println!("\n•*´¨`*•.¸¸.•* Finished fuzzing loop •*´¨`*•.¸¸.•*\n");
println!("{state:#}");
})
.build();
// the fuzzer will run until it iterates over the entire corpus once
fuzzer.fuzz_once(&mut state).await?;
println!("{state:#}");
Ok(())
}
上述 fuzzer 产生的结果类似于以下内容。
[200] 815 - http://localhost:8000/?admin=Ajax - 840.985µs
[200] 206 - http://localhost:8000/?admin=Al - 4.092037ms
----8<----
SharedState::{
Seed=24301
Rng=RomuDuoJrRand { x_state: 97704, y_state: 403063 }
Corpus[words]=Wordlist::{len=102774, top-3=[Static("A"), Static("A's"), Static("AMD")]},
Statistics={"timeouts":0,"requests":102774.0,"errors":44208,"informatives":3626,"successes":29231,"redirects":25709,"client_errors":18195,"server_errors":26013,"redirection_errors":0,"connection_errors":0,"request_errors":0,"start_time":{"secs":1662124648,"nanos":810398280},"avg_reqs_per_sec":5946.646301595066,"statuses":{"500":14890,"201":3641,"307":3656,"203":3562,"101":3626,"401":3625,"207":3711,"308":3578,"300":3724,"404":3705,"301":3707,"302":3651,"304":3706,"502":3682,"402":3636,"200":3718,"503":3762,"400":3585,"501":3679,"202":3659,"205":3680,"206":3676,"204":3584,"403":3644,"303":3687}}
}
🤓 使用 FeroxFuzz 的项目 🤓
chameleon |
贡献者 ✨
感谢以下这些可爱的人(emoji key)
iustin24 💻 |
andreademurtas 💻 |
本项目遵循 all-contributors 规范。欢迎所有类型的贡献!
依赖项
~5–39MB
~617K SLoC