90DaysOfDevOps/2023/day17.md
2023-03-23 16:45:18 +01:00

8.9 KiB

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 and SmartBear. 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.

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:

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:

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:

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:

./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:

$ ./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:

-----
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 interface:

// 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)
}
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:

go build -buildmode=plugin -o log exampleplugins/log/log.go

and then we can plug it into httpfuzz via the --post-request flag:

$ ./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

OWASP: Fuzz Vectors

Hacking HTTP with HTTPfuzz

Fuzzing the Stack for Fun and Profit at DefCamp 2019

HTTP Fuzzing Scan with SmartBear

Fuzzing Session: Finding Bugs and Vulnerabilities Automatically

Fuzzing the CNCF Landscape

See you on Day 18.