LibAFL icon indicating copy to clipboard operation
LibAFL copied to clipboard

Trying to write an HTTP fuzzer

Open AshrafIbrahim03 opened this issue 11 months ago • 17 comments

Wasnt't sure if there was a forum to post questions, so I figured I'd ask here. As the title says, I'm trying to write an HTTP fuzzer for practice, but right now I'm kind of stuck at how to link things together. I'm writing an HTTPFeedback that holds a set of HTTP codes, and the idea is that in its is_interesting function, it will see if the HTTP status code is in its set, and return a value based on that. The only problem is, how do I communicate the returned HTTP status code to the feedback? Would I do that through the ExitKind or state or something else?

Thanks if you're able to help out, and if this isn't the right place to ask, I'd be more than happy to repost my question somewhere else.

AshrafIbrahim03 avatar Dec 31 '24 10:12 AshrafIbrahim03

I would say that just the http status code might not be enough feedback for some really useful fuzzing (albeit possible). But in general, yes, a HTTPFeedback should be straight forward. Store previously seen codes in a custom Metadata, then check if the current code is new or not (i.e., use a set or a bloom filter).

Maybe also take a look at https://github.com/TNO-S3/WuppieFuzz for inspiration, it sounds very much related.

domenukk avatar Jan 01 '25 20:01 domenukk

Been working on this for the past few days and the approach I'm taking is rewriting the the GenericInProcessExecutor but adding the metadata to state in the run_target function. I also modified the harness to return an ExitKind and u16 tuple, to represent the http status code. Does this seem like the best approach to do this? I initially meant to just make a new feedback and didn't expect to make an executor.

AshrafIbrahim03 avatar Jan 06 '25 03:01 AshrafIbrahim03

I'm currently writing a TCP/IP fuzzer for my master's thesis, where I'm using the TCP flags in the header of the returned packets as feedback. I'm using a custom Executor which takes the handle a custom Observer to store the data. That in turn is then used in Feedback(s). Take a look at the project repo maybe?

(Disclaimer: Still very much work in progress, should be done in a bit more than a month)

riesentoaster avatar Jan 16 '25 14:01 riesentoaster

BTW for the TCP/IP thing, you could do an async mutational stage that spawns a bunch of tasks and collects and evals the results later (right?)

domenukk avatar Jan 16 '25 16:01 domenukk

One could do that probably, yes. I cannot — no time left :D

Also: overcommit is a quick and dirty fix.

riesentoaster avatar Jan 16 '25 17:01 riesentoaster

@domenukk With storing the http status codes in a custom metadata, are there any recommended ways to return the status codes? As far as I know, I can't add metadata into state from inside the harness and the harness only returns an exitkind, so it seems like there's no standard way to pass data from inside the harness to the outside. I tried rewriting GenericInProcessExecutor to have a harness that returns a tuple of (ExitKind, u16) instead of just an exitkind, but I figured there's probably a better way to do it.

I also asked on the WuppieFuzz repo about the same issue, i.e. how they pass data outside the harness, and they actually don't pass data out the harness but rather return ExitKind::Crash when there's an 5xx status code.

AshrafIbrahim03 avatar Jan 23 '25 16:01 AshrafIbrahim03

If I may: Have you considered just writing a custom Executor instead of changing a lot of existing code? That's often surprisingly straightforward. You can then just pass it an observer to store your HTTP codes in, and you can then use that observer in any way you like in feedbacks.

riesentoaster avatar Jan 23 '25 16:01 riesentoaster

@domenukk With storing the http status codes in a custom metadata, are there any recommended ways to return the status codes? As far as I know, I can't add metadata into state from inside the harness and the harness only returns an exitkind, so it seems like there's no standard way to pass data from inside the harness to the outside. I tried rewriting GenericInProcessExecutor to have a harness that returns a tuple of (ExitKind, u16) instead of just an exitkind, but I figured there's probably a better way to do it.

I also asked on the WuppieFuzz repo about the same issue, i.e. how they pass data outside the harness, and they actually don't pass data out the harness but rather return ExitKind::Crash when there's an 5xx status code.

You can use a ValueObserver, set a value in a global variable (or similar), https://github.com/AFLplusplus/LibAFL/blob/c5b7c7c23514c79c5b5df50d6c577b2d23fec430/libafl/src/observers/value.rs#L33

Then you can use a custom Feedback to write to metadata (or observe the value directly if that's what you want)

domenukk avatar Jan 23 '25 17:01 domenukk

Maybe there's a cleaner, more rusty way, @addisoncrump may know

domenukk avatar Jan 23 '25 17:01 domenukk

@riesentoaster I didn't think about writing one from scratch honestly - I think I'lll also try that out, thanks!

AshrafIbrahim03 avatar Jan 23 '25 21:01 AshrafIbrahim03

@riesentoaster I didn't think about writing one from scratch honestly - I think I'lll also try that out, thanks!

Check out the baby_fuzzer_custom_executor example, it may be a good starting point. And maybe the executor of my thesis project I linked above, although that is much more involved, but it shows how one can do custom oberservers/logging feedbacks.

riesentoaster avatar Jan 23 '25 21:01 riesentoaster

Finally back and I have written some code to try and get this idea out. Here's a link to the repo where I have it. There's still bugs, that I'm not sure how to iron out.

The HTTPExecutor uses a harness that returns a reqwest::blocking::Response. I added HashMapState and StatusLogic to HTTPExecutor because a) I was trying to pass the Responses to the feedbacks, but that wasn't working so I figured I'd just use a hashmap with the current run as the key and the returned Response as the value so I didn't have to make a wrapper type to implement SerdeAny on to get the Response into a normal state struct provided by libafl. and b) I needed to derive an ExitKindfrom the Response so I could pass something on to the observers, so I just made another struct that maps a Response to an ExitKind. In this case, I just implemented StatusLogic to take in some status codes and return ExitKind::Crash if the given Response has any of those status codes.

My code still isn't compiling because of type errors but I'm coming back for more advice on my approach. Should I create a wrapper type around Response and implement SerdeAny on it in order to be able to use one of the standard libafl state maps or is there something here I'm not seeing?

Implementing these things made me wish that we had some sort of ExitKind trait so that harnesses could essentially return anything and that the ExitKind could be passed around with different values that it held. I feel like the exit status of a harness is more complicated than the current ExitKind implementation allows it to be.

AshrafIbrahim03 avatar Mar 20 '25 16:03 AshrafIbrahim03

The correct way is to have an observer that observes whatever http result you want. Then you can use that in a feedback. No need to do anything with ExitKind here. This response should be SerdeAny - you can just derive it (#derive[SerdeAny]). The idea is that we can share observations with other nodes without having to re-run them.

domenukk avatar Mar 20 '25 18:03 domenukk

Thanks!

AshrafIbrahim03 avatar Mar 20 '25 18:03 AshrafIbrahim03

Ok. So I rewrote my code using a ValueObserver, but I'm running into borrow checker issues. When I set the value within the harness, it takes a mutable borrow, but then when I pass it into tuple_list when instantiating executor, it throws an error that says I can't move status_observer when it's being borrowed.

I tried to wrap the ValueObserver in a Mutex, in an Arc<Mutex<>>, I wrapped it in a RefCell, used an RwLock, and I replaced my usage of ValueObserver with RefCellValueObserver. They all did not work when trying to compile this code. I wasn't able to pass a MutexGuard or RwLockReadGuard into GenericInProcessExecutor

Are there any tricks I'm not using to getting around the borrow checker?

const TIMEOUT: u64 = 1000;
type HTTPStatusObserver = ValueObserver<'static, u16>;


fn main() {
    let client = Client::builder()
        .timeout(Duration::from_millis(TIMEOUT))
        .build()
        .expect("Could not build client ");
    let target_url = "http://scanme.nmap.org";
    let mut status_observer = ValueObserver::new(
        "status_observer",
        libafl_bolts::ownedref::OwnedRef::Owned(Box::new(0)),
    );
    let mut feedback = HTTPCodeFeedback::new("feedback", status_observer.handle(), [404, 200]);
    let mut harness = |_: &BytesInput| -> ExitKind {
        if let Ok(res) = client.get(target_url).send() {
            status_observer.set(res.status().as_u16()); // mutable borrow occurs here

            return ExitKind::Ok;
        }
        return ExitKind::Crash;
    };

    let mut state = StdState::new(
        StdRand::with_seed(1),
        InMemoryCorpus::<BytesInput>::new(),
        OnDiskCorpus::new("./crashes").expect("could not create crashes directory"),
        &mut feedback,
        &mut (),
    )
    .expect("could not make state");
    let mon = SimpleMonitor::new(|s| println!("{s}"));
    let mut mgr = SimpleEventManager::new(mon);

    let scheduler = QueueScheduler::new();
    let mut fuzzer = StdFuzzer::new(scheduler, (), ());

    let mut executor = GenericInProcessExecutor::new(
        &mut harness,
        tuple_list!(status_observer), // change in ownership occurs here
        &mut fuzzer,
        &mut state,
        &mut mgr,
    )
    .expect("Could not build executor");

    let mut generator = RandBytesGenerator::new(nonzero!(1));
    state
        .generate_initial_inputs(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8)
        .expect("Could not create initial inputs");

    let input = BytesInput::new(Vec::from("abc"));
    let mutator = StdScheduledMutator::new(havoc_mutations());
    let mut stages = tuple_list!(StdMutationalStage::new(mutator));
    fuzzer.fuzz_loop_for(&mut stages, &mut executor, &mut state, &mut mgr, 10);
}

AshrafIbrahim03 avatar Apr 10 '25 13:04 AshrafIbrahim03

I am not sure this is possible using a GenericInProcessExecutor. Generally, the executor owns the observers. However, the interface of GenericInProcessExecutor is such that you cannot manually access them in the harness (because you only get a reference to the Input and not to the OT/ObserversTuple, from which you could extract your ValueObserver via a Handle).

Now, you could write a custom Executor, but this may also be possible using one of the many flavors of InProcessExecutors. I don't know them well enough to say anything about that though. Maybe @domenukk does?

riesentoaster avatar Apr 11 '25 06:04 riesentoaster

Sorry I was in the hospital for a few days. The way to do this is to use interior mutability: You cannot own the ValueObserver from multiple places, but you can change/own the observed value. The "easiest" solution would be to use unsafe and an OwnedRef that refers to a pointer to a static. It's reasonably safe since the fuzzer never alters the value after execution.

There are other ways with proper RCs and/or Mutex I am sure, but I would have to play around with the code. @addisoncrump may have it handy

domenukk avatar Apr 16 '25 10:04 domenukk