#[allow(clippy::wildcard_imports)]
use super::{typestate::*, BlockingFuzzerBuilder, BlockingFuzzing, Fuzzer, FuzzingLoopHook};
use crate::actions::{Action, FlowControl};
use crate::client::BlockingRequests;
use crate::corpora::CorpusItemType;
use crate::deciders::Deciders;
use crate::error::FeroxFuzzError;
use crate::events::{
DiscardedRequest, DiscardedResponse, EventPublisher, FuzzOnce, KeptRequest, KeptResponse,
};
use crate::mutators::Mutators;
use crate::observers::Observers;
use crate::processors::Processors;
use crate::requests::Request;
use crate::responses::BlockingResponse;
use crate::schedulers::Scheduler;
use crate::state::SharedState;
use crate::std_ext::ops::LogicOperation;
use tracing::instrument;
use tracing::log::warn;
#[derive(Debug, Default, Clone)]
pub struct BlockingFuzzer<B, D, M, O, P, S>
where
B: BlockingRequests,
D: Deciders<O, BlockingResponse>,
M: Mutators,
O: Observers<BlockingResponse>,
P: Processors<O, BlockingResponse>,
S: Scheduler,
{
pub(super) client: B,
pub(super) request: Request,
pub(super) scheduler: S,
pub(super) mutators: M,
pub(super) observers: O,
pub(super) processors: P,
pub(super) deciders: D,
pub(super) request_id: usize,
pub(super) pre_send_logic: LogicOperation,
pub(super) post_send_logic: LogicOperation,
pub(super) pre_loop_hook: Option<FuzzingLoopHook>,
pub(super) post_loop_hook: Option<FuzzingLoopHook>,
}
impl<B, D, M, O, P, S> Fuzzer for BlockingFuzzer<B, D, M, O, P, S>
where
B: BlockingRequests,
D: Deciders<O, BlockingResponse>,
M: Mutators,
O: Observers<BlockingResponse>,
P: Processors<O, BlockingResponse>,
S: Scheduler,
{
fn pre_send_logic(&self) -> LogicOperation {
self.pre_send_logic
}
fn post_send_logic(&self) -> LogicOperation {
self.post_send_logic
}
fn pre_send_logic_mut(&mut self) -> &mut LogicOperation {
&mut self.pre_send_logic
}
fn post_send_logic_mut(&mut self) -> &mut LogicOperation {
&mut self.post_send_logic
}
fn reset(&mut self) {
self.scheduler.reset();
}
}
impl<B, D, M, O, P, S> BlockingFuzzing for BlockingFuzzer<B, D, M, O, P, S>
where
B: BlockingRequests,
D: Deciders<O, BlockingResponse>,
M: Mutators,
O: Observers<BlockingResponse>,
P: Processors<O, BlockingResponse>,
S: Scheduler,
{
#[instrument(skip_all, fields(?self.post_send_logic, ?self.pre_send_logic), name = "fuzz-loop", level = "trace")]
fn fuzz_once(&mut self, state: &mut SharedState) -> Result<Option<Action>, FeroxFuzzError> {
let pre_send_logic = self.pre_send_logic();
let post_send_logic = self.post_send_logic();
if let Some(hook) = &mut self.pre_loop_hook {
(hook.callback)(state);
hook.called += 1;
}
state.events().notify(FuzzOnce {
threads: 1,
pre_send_logic,
post_send_logic,
corpora_length: state.total_corpora_len(),
});
loop {
let scheduled = Scheduler::next(&mut self.scheduler);
if matches!(scheduled, Err(FeroxFuzzError::IterationStopped)) {
break;
} else if matches!(scheduled, Err(FeroxFuzzError::SkipScheduledItem { .. })) {
continue;
}
let mut request = self.request.clone();
*request.id_mut() += self.request_id;
let mut mutated_request = self.mutators.call_mutate_hooks(state, request)?;
self.observers.call_pre_send_hooks(&mutated_request);
let decision =
self.deciders
.call_pre_send_hooks(state, &mutated_request, None, pre_send_logic);
if decision.is_some() {
mutated_request.set_action(decision.clone());
state.update_from_request(&mutated_request);
}
self.processors
.call_pre_send_hooks(state, &mut mutated_request, decision.as_ref());
match decision {
Some(Action::Discard) => {
self.request_id += 1;
state.events().notify(DiscardedRequest {
id: mutated_request.id(),
});
continue;
}
Some(Action::AddToCorpus(name, corpus_item_type, flow_control)) => {
match corpus_item_type {
CorpusItemType::Request => {
state.add_request_fields_to_corpus(&name, &mutated_request)?;
}
CorpusItemType::Data(data) => {
state.add_data_to_corpus(&name, data)?;
}
CorpusItemType::LotsOfData(data) => {
for item in data {
state.add_data_to_corpus(&name, item)?;
}
}
}
self.scheduler.update_length();
match flow_control {
FlowControl::StopFuzzing => {
tracing::info!(
"[ID: {}] stopping fuzzing due to AddToCorpus[StopFuzzing] action",
self.request_id
);
state.events().notify(&Action::StopFuzzing);
return Ok(Some(Action::StopFuzzing));
}
FlowControl::Discard => {
self.request_id += 1;
state.events().notify(DiscardedRequest {
id: mutated_request.id(),
});
continue;
}
FlowControl::Keep => {
state.events().notify(KeptRequest {
id: mutated_request.id(),
});
}
}
}
Some(Action::StopFuzzing) => {
state.events().notify(&Action::StopFuzzing);
return Ok(Some(Action::StopFuzzing));
}
Some(Action::Keep) => {
state.events().notify(KeptRequest {
id: mutated_request.id(),
});
}
None => {} }
let response = self.client.send(mutated_request.clone());
if let Err(error) = response {
state.update_from_error(&error)?;
self.request_id += 1;
tracing::warn!(%error, "response errored out and will not continue through the fuzz loop");
continue;
}
self.observers.call_post_send_hooks(response.unwrap());
let decision =
self.deciders
.call_post_send_hooks(state, &self.observers, None, post_send_logic);
state.update(&self.observers, decision.as_ref())?;
self.processors
.call_post_send_hooks(state, &self.observers, decision.as_ref());
match decision {
Some(Action::AddToCorpus(name, corpus_item_type, flow_control)) => {
match corpus_item_type {
CorpusItemType::Request => {
state.add_request_fields_to_corpus(&name, &mutated_request)?;
}
CorpusItemType::Data(data) => {
state.add_data_to_corpus(&name, data)?;
}
CorpusItemType::LotsOfData(data) => {
for item in data {
state.add_data_to_corpus(&name, item)?;
}
}
}
self.scheduler.update_length();
match flow_control {
FlowControl::StopFuzzing => {
tracing::info!(
"[ID: {}] stopping fuzzing due to AddToCorpus[StopFuzzing] action",
self.request_id
);
state.events().notify(&Action::StopFuzzing);
return Ok(Some(Action::StopFuzzing));
}
FlowControl::Discard => {
self.request_id += 1;
state.events().notify(DiscardedResponse {
id: mutated_request.id(),
});
continue;
}
FlowControl::Keep => {
state.events().notify(KeptResponse {
id: mutated_request.id(),
});
}
}
}
Some(Action::StopFuzzing) => {
tracing::info!(
"[ID: {}] stopping fuzzing due to StopFuzzing action",
self.request_id
);
state.events().notify(&Action::StopFuzzing);
return Ok(Some(Action::StopFuzzing));
}
Some(Action::Discard) => {
state.events().notify(DiscardedResponse {
id: mutated_request.id(),
});
}
Some(Action::Keep) => {
state.events().notify(KeptResponse {
id: mutated_request.id(),
});
}
_ => {}
}
self.request_id += 1;
}
if let Some(hook) = &mut self.post_loop_hook {
(hook.callback)(state);
hook.called += 1;
}
Ok(None) }
}
impl<B, D, M, O, P, S> BlockingFuzzer<B, D, M, O, P, S>
where
B: BlockingRequests,
D: Deciders<O, BlockingResponse>,
M: Mutators,
O: Observers<BlockingResponse>,
P: Processors<O, BlockingResponse>,
S: Scheduler,
{
#[allow(clippy::type_complexity)]
#[allow(clippy::new_ret_no_self)]
#[must_use]
pub fn new() -> BlockingFuzzerBuilder<
NoClient,
NoRequest,
NoScheduler,
NoMutators,
NoObservers,
NoProcessors,
NoDeciders,
NoPreSendLogic,
NoPostSendLogic,
NoPreLoopHook,
NoPostLoopHook,
B,
D,
M,
O,
P,
S,
> {
BlockingFuzzerBuilder::new(0)
}
pub fn request_mut(&mut self) -> &mut Request {
&mut self.request
}
pub fn scheduler_mut(&mut self) -> &mut S {
&mut self.scheduler
}
pub fn set_pre_loop_hook(&mut self, hook: fn(&mut SharedState)) {
self.pre_loop_hook = Some(FuzzingLoopHook::new(hook));
}
pub fn set_post_loop_hook(&mut self, hook: fn(&mut SharedState)) {
self.post_loop_hook = Some(FuzzingLoopHook::new(hook));
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::client::BlockingClient;
use crate::corpora::RangeCorpus;
use crate::deciders::{RequestRegexDecider, ResponseRegexDecider};
use crate::fuzzers::BlockingFuzzer;
use crate::mutators::ReplaceKeyword;
use crate::observers::ResponseObserver;
use crate::prelude::*;
use crate::requests::ShouldFuzz;
use crate::responses::BlockingResponse;
use crate::schedulers::OrderedScheduler;
use ::regex::Regex;
use httpmock::Method::GET;
use httpmock::MockServer;
use reqwest;
use std::time::Duration;
#[test]
fn test_blocking_fuzzer_stops_fuzzing_pre_send() -> Result<(), Box<dyn std::error::Error>> {
let srv = MockServer::start();
let mock = srv.mock(|when, then| {
when.method(GET).path_matches(Regex::new("[012]").unwrap());
then.status(200).body("this is a test");
});
let range = RangeCorpus::with_stop(3).name("range").build()?;
let mut state = SharedState::with_corpus(range);
let req_client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(1))
.build()?;
let client = BlockingClient::with_client(req_client);
let mutator = ReplaceKeyword::new(&"FUZZ", "range");
let request = Request::from_url(&srv.url("/FUZZ"), Some(&[ShouldFuzz::URLPath]))?;
let decider = RequestRegexDecider::new("1", |regex, request, _state| {
if regex.is_match(request.path().inner()) {
Action::StopFuzzing
} else {
Action::Keep
}
});
let scheduler = OrderedScheduler::new(state.clone())?;
let response_observer: ResponseObserver<BlockingResponse> = ResponseObserver::new();
let observers = build_observers!(response_observer);
let deciders = build_deciders!(decider);
let mutators = build_mutators!(mutator);
let mut fuzzer = BlockingFuzzer::new()
.client(client)
.request(request)
.scheduler(scheduler)
.mutators(mutators)
.observers(observers)
.deciders(deciders)
.build();
fuzzer.fuzz_once(&mut state.clone())?;
mock.assert_hits(1);
fuzzer.scheduler.reset();
fuzzer.fuzz_n_iterations(1, &mut state.clone())?;
mock.assert_hits(2);
fuzzer.scheduler.reset();
fuzzer.fuzz(&mut state)?;
mock.assert_hits(3);
Ok(())
}
#[test]
fn test_blocking_fuzzer_stops_fuzzing_post_send() -> Result<(), Box<dyn std::error::Error>> {
let srv = MockServer::start();
let mock = srv.mock(|when, then| {
when.method(GET).path_matches(Regex::new("[02]").unwrap());
then.status(200).body("this is a test");
});
let mock2 = srv.mock(|when, then| {
#[allow(clippy::trivial_regex)]
when.method(GET).path_matches(Regex::new("1").unwrap());
then.status(200).body("derp");
});
let range = RangeCorpus::with_stop(3).name("range").build()?;
let mut state = SharedState::with_corpus(range);
let req_client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(1))
.build()?;
let client = BlockingClient::with_client(req_client);
let mutator = ReplaceKeyword::new(&"FUZZ", "range");
let request = Request::from_url(&srv.url("/FUZZ"), Some(&[ShouldFuzz::URLPath]))?;
let decider = ResponseRegexDecider::new("derp", |regex, response, _state| {
if regex.is_match(response.body()) {
Action::StopFuzzing
} else {
Action::Keep
}
});
let scheduler = OrderedScheduler::new(state.clone())?;
let response_observer: ResponseObserver<BlockingResponse> = ResponseObserver::new();
let observers = build_observers!(response_observer);
let deciders = build_deciders!(decider);
let mutators = build_mutators!(mutator);
let mut fuzzer = BlockingFuzzer::new()
.client(client)
.request(request)
.scheduler(scheduler)
.mutators(mutators)
.observers(observers)
.deciders(deciders)
.build();
fuzzer.fuzz_once(&mut state)?;
mock.assert_hits(1);
mock2.assert_hits(1);
Ok(())
}
}