1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//! first, update the variable named target with a valid url to scan
//!
//! then run the example with the following command
//! cargo run --features blocking --example cartesian-product
use feroxfuzz::client::{BlockingClient, HttpClient};
use feroxfuzz::corpora::{RangeCorpus, Wordlist};
use feroxfuzz::fuzzers::BlockingFuzzer;
use feroxfuzz::mutators::ReplaceKeyword;
use feroxfuzz::observers::ResponseObserver;
use feroxfuzz::prelude::*;
use feroxfuzz::processors::RequestProcessor;
use feroxfuzz::requests::ShouldFuzz;
use feroxfuzz::responses::BlockingResponse;
use feroxfuzz::schedulers::ProductScheduler;
use std::time::Duration;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// create two corpora, one with a set of user names, and one with a range of ids
// where only even ids are considered
let users = Wordlist::with_words(["user", "admin"])
.name("users")
.build();
let ids = RangeCorpus::with_stop(10).step(2).name("ids").build()?;
// associate the user names with the `users` corpus name, and the user ids with the
// `ids` corpus name
//
// i.e. key-value pairs
let corpora = [ids, users];
let mut state = SharedState::with_corpora(corpora);
// byo-client, this example uses reqwest
let req_client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(1))
.build()?;
// with some client that can handle the actual http request/response stuff
// we can build a feroxfuzz client, specifically a blocking client in this
// instance. Because we're using a blocking client, there's an implicit
// opt-in to using the BlockingResponse implementor of the Response trait
let client = BlockingClient::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 user_mutator = ReplaceKeyword::new(&"USER", "users");
let id_mutator = ReplaceKeyword::new(&"ID", "ids");
// 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
let request = Request::from_url(
"http://localhost:8000/?user=USER&id=ID",
Some(&[ShouldFuzz::URLParameterValues]),
)?;
// a RequestProcessor provides a way to inspect each request and decide upon some Action based on the
// result of the mutation that was performed. In this case, the RequestProcessor doesn't care about
// checking the mutation, we simply want to print the mutated fields to show how a ProductScheduler
// does its work.
//
// example output:
// http://localhost:8000/?user=user&id=0
// ...
let request_printer = RequestProcessor::new(|request, _action, _state| {
print!("{}?", request.original_url());
for (i, (key, value)) in request.params().unwrap().iter().enumerate() {
if i == 0 {
print!("{key}={value}&");
} else {
print!("{key}={value}");
}
}
println!();
});
// the ProductScheduler is a scheduler that creates a nested for-loop scheduling pattern.
// Ordering of the loops is determined by passing the corpus names to the constructor.
//
// for this particular example, the order of the corpora is:
// users -> ids
//
// what this means is that the outermost loop will iterate over the users corpus, and the innermost
// loop will iterate over the ids corpus.
//
// the result should produce a product of the following:
// user1 -> id1
// user1 -> id2
// user1 -> id3
// user1 -> id4
// user1 -> id5
// user2 -> id1
// user2 -> id2
// user2 -> id3
// user2 -> id4
// user2 -> id5
let order = ["users", "ids"];
let scheduler = ProductScheduler::new(order, state.clone())?;
// a ResponseObserver is responsible for gathering information from each response and providing
// that information to later fuzz stages. 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<BlockingResponse> = ResponseObserver::new();
// 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 observers = build_observers!(response_observer);
let mutators = build_mutators!(user_mutator, id_mutator);
let processors = build_processors!(request_printer);
let mut fuzzer = BlockingFuzzer::new()
.client(client)
.request(request)
.scheduler(scheduler)
.mutators(mutators)
.observers(observers)
.processors(processors)
.build();
// the fuzzer will run until it iterates over the entire corpus once
fuzzer.fuzz_once(&mut state)?;
println!("{state:#}");
// example output:
//
// http://localhost:8000/?user=USER&id=ID?user=user&id=0
// http://localhost:8000/?user=USER&id=ID?user=user&id=2
// http://localhost:8000/?user=USER&id=ID?user=user&id=4
// http://localhost:8000/?user=USER&id=ID?user=user&id=6
// http://localhost:8000/?user=USER&id=ID?user=user&id=8
// http://localhost:8000/?user=USER&id=ID?user=admin&id=0
// http://localhost:8000/?user=USER&id=ID?user=admin&id=2
// http://localhost:8000/?user=USER&id=ID?user=admin&id=4
// http://localhost:8000/?user=USER&id=ID?user=admin&id=6
// http://localhost:8000/?user=USER&id=ID?user=admin&id=8
// SharedState::{
// Seed=24301
// Rng=RomuDuoJrRand { x_state: 97704, y_state: 403063 }
// Corpus[ids]=RangeCorpus::{start=0, stop=10, step=2},
// Corpus[users]=Wordlist::{len=2, top-2=[Static("user"), Static("admin")]},
// Statistics={"timeouts":0,"requests":10.0,"errors":3,"informatives":0,"successes":3,"redirects":4,"client_errors":1,"server_errors":2,"redirection_errors":0,"connection_errors":0,"request_errors":0,"start_time":{"secs":1665661333,"nanos":517789344},"avg_reqs_per_sec":610.5293851456247,"statuses":{"308":2,"203":1,"201":1,"403":1,"304":1,"204":1,"301":1,"500":2}}
// }
Ok(())
}