From cb402197786a8eb35713a2d4a3f6ef0afbf90dd7 Mon Sep 17 00:00:00 2001 From: Anton Sankov Date: Sun, 8 Jan 2023 02:07:42 +0200 Subject: [PATCH] Day 17: Fuzzing Advanced --- 2023/day17.md | 242 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 242 insertions(+) diff --git a/2023/day17.md b/2023/day17.md index e69de29..cf7f191 100644 --- a/2023/day17.md +++ b/2023/day17.md @@ -0,0 +1,242 @@ +# Fuzzing Advanced + +Yesterday we learned what fuzzing is and how to write fuzz tests (unit tests with fuzzy inputs). +However, fuzz testing goes beyond just unit testing. +We can use this methodology to test our web application by fuzzing the requests sent to our server. + +Today, we will take a practical approach to fuzzy testing a web server. + +Different tools can help us do this. + +Such tools are [Burp Intruder](https://portswigger.net/burp/documentation/desktop/tools/intruder) and [SmartBear](https://smartbear.com/). +However, there are proprietary tools that require a paid license to use them. + +That is why for our demonstration today we are going to use a simple open-source CLI written in Go that was inspired by Burp Intruder and provides similar functionality. +It is called [httpfuzz](https://github.com/JonCooperWorks/httpfuzz). + + +## Getting started + +This tool is quite simple. +We provide it a template for our requests (in which we have defined placeholders for the fuzzy data), a wordlist (the fuzzy data) and `httpfuzz` will render the requests and send them to our server. + +First, we need to define a template for our requests. +Create a file named `request.txt` with the following content: + +```text +POST / HTTP/1.1 +Content-Type: application/json +User-Agent: PostmanRuntime/7.26.3 +Accept: */* +Cache-Control: no-cache +Host: localhost:8000 +Accept-Encoding: gzip, deflate +Connection: close +Content-Length: 35 + +{ + "name": "`S9`", +} +``` + +This is a valid HTTP `POST` request to the `/` route with JSON body. +The "\`" symbol in the body defines a placeholder that will be substituted with the data we provide. + +`httpfuzz` can also fuzz the headers, path, and URL params. + +Next, we need to provide a wordlist of inputs that will be placed in the request. +Create a file named `data.txt` with the following content: + +```text +SOME_NAME +Mozilla/5.0 (Linux; Android 7.0; SM-G930VC Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36 +``` + +In this file, we defined two inputs that will be substituted inside the body. +In a real-world scenario, you should put much more data here for proper fuzz testing. + +Now that we have our template and our inputs, let's run the tool. +Unfortunately, this tool is not distributed as a binary, so we will have to build it from source. +Clone the repo and run: + +```shell +go build -o httpfuzz cmd/httpfuzz.go +``` + +(requires to have a recent version of Go installed on your machine). + +Now that we have the binary let's run it: + +```shell +./httpfuzz \ + --wordlist data.txt \ + --seed-request request.txt \ + --target-header User-Agent \ + --target-param fuzz \ + --delay-ms 50 \ + --skip-cert-verify \ + --proxy-url http://localhost:8080 \ +``` + +- `httpfuzz` is the binary we are invoking. +- `--wordlist data.txt` is the file with inputs we provided. +- `--seed-request requests.txt` is the request template. +- `--target-header User-Agent` tells `httpfuzz` to use the provided inputs in the place of the `User-Agent` header. +- `--target-param fuzz` tells `httpfuzz` to use the provided inputs as values for the `fuzz` URL parameter. +- `--delay-ms 50` tells `httpfuzz` to wait 50 ms between the requests. +- `--skip-cert-verify` tells `httpfuzz` to not do any TLS verification. +- `--proxy-url http://localhost:8080` tells `httpfuzz` where our HTTP server is. + +We have 2 inputs and 3 places to place them (in the body, the `User-Agent` header, and the `fuzz` parameter). +This means that `httpfuzz` will generate 6 requests and send them to our server. + +Let's run it and see what happens. +I wrote a simple web server that logs all requests so that we can see what is coming into our server: + +```shell +$ ./httpfuzz \ + --wordlist data.txt \ + --seed-request request.txt \ + --target-header User-Agent \ + --target-param fuzz \ + --delay-ms 50 \ + --skip-cert-verify \ + --proxy-url http://localhost:8080 \ + +httpfuzz: httpfuzz.go:164: Sending 6 requests +``` + +and the server logs: + +```text +----- +Got request to http://localhost:8000/ +User-Agent header = [SOME_NAME] +Name = S9 +----- +Got request to http://localhost:8000/?fuzz=SOME_NAME +User-Agent header = [PostmanRuntime/7.26.3] +Name = S9 +----- +Got request to http://localhost:8000/ +User-Agent header = [PostmanRuntime/7.26.3] +Name = SOME_NAME +----- +Got request to http://localhost:8000/ +User-Agent header = [Mozilla/5.0 (Linux; Android 7.0; SM-G930VC Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36] +Name = S9 +----- +Got request to http://localhost:8000/?fuzz=Mozilla%2F5.0+%28Linux%3B+Android+7.0%3B+SM-G930VC+Build%2FNRD90M%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.083+Mobile+Safari%2F537.36 +User-Agent header = [PostmanRuntime/7.26.3] +Name = S9 +----- +Got request to http://localhost:8000/ +User-Agent header = [PostmanRuntime/7.26.3] +Name = Mozilla/5.0 (Linux; Android 7.0; SM-G930VC Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36 +``` + +We see that we have received 6 HTTP requests. + +Two of them have a value from our values file for the `User-Agent` header, and 4 have the default header from the template. +Two of them have a value from our values file for the `fuzz` query parameter, and 4 have the default header from the template. +Two of them have a value from our values file for the `Name` body property, and 4 have the default header from the template. + +A slight improvement of the tool could be to make different permutations of these requests (for example, a request that has both `?fuzz=` and `User-Agent` as values from the values file). + +Notice how `httpfuzz` does not give us any information about the outcome of the requests. +To figure that out, we need to either set up some sort of monitoring for our server or write a `httpfuzz` plugin that will process the results in a meaningful for us way. +Let's do that. + +To write a custom plugin, we need to implement the [`Listener`](https://github.com/JonCooperWorks/httpfuzz/blob/master/plugin.go#L13) interface: + +```go +// Listener must be implemented by a plugin to users to hook the request - response transaction. +// The Listen method will be run in its own goroutine, so plugins cannot block the rest of the program, however panics can take down the entire process. +type Listener interface { + Listen(results <-chan *Result) +} +``` + +```go +package main + +import ( + "bytes" + "io/ioutil" + "log" + + "github.com/joncooperworks/httpfuzz" +) + +type logResponseCodePlugin struct { + logger *log.Logger +} + +func (b *logResponseCodePlugin) Listen(results <-chan *httpfuzz.Result) { + for result := range results { + b.logger.Printf("Got %d response from the server\n", result.Response.StatusCode) + } +} + +// New returns a logResponseCodePlugin plugin that simple logs the response code of the response. +func New(logger *log.Logger) (httpfuzz.Listener, error) { + return &logResponseCodePlugin{logger: logger}, nil +} +``` + +Now we need to build our plugin first: + +```shell +go build -buildmode=plugin -o log exampleplugins/log/log.go +``` + +and then we can plug it into `httpfuzz` via the `--post-request` flag: + +```shell +$ ./httpfuzz \ + --wordlist data.txt \ + --seed-request request.txt \ + --target-header User-Agent \ + --target-param fuzz \ + --delay-ms 50 \ + --skip-cert-verify \ + --proxy-url http://localhost:8080 \ + --post-request log + +httpfuzz: httpfuzz.go:164: Sending 6 requests +httpfuzz: log.go:15: Got 200 response from the server +httpfuzz: log.go:15: Got 200 response from the server +httpfuzz: log.go:15: Got 200 response from the server +httpfuzz: log.go:15: Got 200 response from the server +httpfuzz: log.go:15: Got 200 response from the server +httpfuzz: log.go:15: Got 200 response from the server +``` + +Voila! +Now we can at least see what the response code from the server was. + +Of course, we can write much more sophisticated plugins that output much more data, but for the purpose of this exercise, that is enough. + +## Summary + +Fuzzing is a really powerful testing technique that goes way beyond unit testing. + +Fuzzing can be extremely useful for testing HTTP servers by substituting parts of valid HTTP requests with data that could potentially expose vulnerabilities or deficiencies in our server. + +There are many tools that can help us in fuzzy testing our web applications, both free and paid ones. + +## Resources + +[OWASP: Fuzzing](https://owasp.org/www-community/Fuzzing) + +[OWASP: Fuzz Vectors](https://owasp.org/www-project-web-security-testing-guide/v41/6-Appendix/C-Fuzz_Vectors) + +[Hacking HTTP with HTTPfuzz](https://medium.com/swlh/hacking-http-with-httpfuzz-67cfd061b616) + +[Fuzzing the Stack for Fun and Profit at DefCamp 2019](https://www.youtube.com/watch?v=qCMfrbpuCBk&list=PLnwq8gv9MEKiUOgrM7wble1YRsrqRzHKq&index=33) + +[HTTP Fuzzing Scan with SmartBear](https://support.smartbear.com/readyapi/docs/security/scans/types/fuzzing-http.html) + +[Fuzzing Session: Finding Bugs and Vulnerabilities Automatically](https://youtu.be/DSJePjhBN5E) + +[Fuzzing the CNCF Landscape](https://youtu.be/zIyIZxAZLzo)