Detecting Bugs Using Network Protocol Fuzzing

TLDR; This is an entry-level post. It goes over the concept of network-based fuzzing using Boofuzz, takes HTTP protocol as an example to practice finding bugs in real-world implementations of HTTP servers, briefly reviews 6 different exploits, and finally shows the process of finding a new unknown bug in an HTTP protocol implementation.

Introduction

Fuzzing is a way to find software vulnerabilities. The idea of fuzzing is to deliver inputs to target software and detect crashes. There are different types of Fuzzers, Random Fuzzers, Guided Fuzzers, and Generational Fuzzers.

Random Fuzzers, also known as Dump Fuzzers, are used to generate and deliver random inputs with no systematic method for generating the inputs. An example of this type is Radmasa. This type’s main disadvantage is that it takes a long time to find crashes since it doesn’t understand what the targeted program needs to function.

Guided Fuzzers depend on sample inputs at the start. Guided Fuzzers monitors each test’s execution flow and uses the target’s behavioral feedback to generate new test cases to test the new sections of code. An example of this type is WinAFL.

Generational Fuzzers get the structure of the input it can use, then they use it to generate test cases. This type is used to generate random inputs based on a predefined protocol. This type is typically used to fuzz network protocol or file format implementations. An example of this type is BooFuzz.

Network-based Fuzzing

We will learn how to use BooFuzz to fuzz network protocol implementations. BooFuzz is an improved version of an known open source project called Sulley. BooFuzz records test cases, supports serial fuzzing, ethernet, IP-layer, and UDP broadcast, and detects crashes. (https://github.com/jtpereyda/boofuzz).

BooFuzz docs can be found here: https://boofuzz.readthedocs.io/en/stable/.

The goal is to practice writing PoCs for different network protocols. An easy way to start this is by writing a PoC of a very well-known protocol like HTTP. First, let’s look at how the standard HTTP GET requests look like when visiting this random website: http://info.cern.ch.

If you open Wireshark, you will notice that your browser sent multiple requests, as shown below:

.

The red rectangle shows the HTTP structure. If you click on one of the packets and follow the HTTP stream, you will find the exact HTTP request sent and received from the server. The next screenshot shows that:

.

The red rectangle highlights what the browser sent to the server. The browser knows what to send and what it needs to show the correct data. It asked for the content of the page and some other requests to get things like the website’s favicon.ico file. In response, the website replied with the requested info, as it should do. Because browser and server follow the HTTP stander without adding unexpected inputs, there is no problem. Even if the server was getting some unexpected random data, since it’s only following the HTTP stander, it should able to handle any request. It also should be able to ignore strange requests and continue serving legitimate clients.

When developers implement network standers in their programs, they know that they should handle all types of requests and only serve those who follow the stander. Developers implement checks to verify that the coming requests are following the standers and validate the data. However, sometimes bugs pass the developers’ tests.

To find these bugs, we need to somehow think like a developer. When testing a well-known project like Nginx, we certainly could assume that sending random data to Nginx will be handled and will not crash the server. Thus, using random fuzzers is very useless in this case. But we can assume that the protocol has been implemented almost perfectly but with some hidden bugs. Therefore, when we write our fuzzer, we should assume that the HTTP stander was followed during the development.

Before we write a fuzzing PoC, if you want to read more about HTTP protocol, you can read about it on the RFC website https://tools.ietf.org/html/rfc2616. The RFC website explains the protocol in detail.

Now, let’s think about how we can fuzz an HTTP service. Let’s go back to Wireshark and look at one of the requests sent by the browser. We can see that the headers are filled out by my browser. The values of each header are not fixed, so we can try to fuzz them. The values are highlighted in the next screenshot:

.

At the same time, following the stander, we can also add a new header with a new value and try fuzz this way. Or maybe fuzz the method section, protocol name, or the path.

.

This way, we can guarantee that the server will at least be able to understand the sent data even though the data might contain unhandled exceptions that cloud break the program.

Another example is when we want to fuzz a PHP application that is being interpreted by an HTTP server. If we want a fuzzer to reach the PHP application, the fuzzer needs to be able to generate HTTP friendly tests to find bugs in the running application, and again that means Random fuzzers are useless in this case.

Vulnerable HTTP servers and applications

The following list of programs are great examples of bugs in services and web applications that BooFuzz can find:

Program Issue
MiniWeb HTTP Server No field bounds-check in the HTTP body - PoC link
Sysax Multi Server 6.50 No file bounds-check - PoC link
Ultra MiniHTTPd 1.2 No path bounds-check - PoC link
XiongMai UC-HTTPd 1.0.0 No bounds-check for username values - PoC link
HFS Http File Server No bounds-check for the mode value - PoC link
Oracle 9i XDB 9.2.0.1 Basic HTTP Authorization bypass - PoC link

Boofuzz

As practice, let’s start readubg BooFuzz examples that can find the bugs mentioned above then let’s try to write a fuzzer to find a new bug.

Examples

MiniWeb HTTP Server BooFuzz fuzzer:

from boofuzz import *

# Start a boofuzz session
session = Session(target = Target(connection = SocketConnection("192.168.1.1", 80, proto='tcp')))

# Initialize a new block request named `Req`
s_initialize("Req")

# Add a static value to `Req` block
s_static("POST /index.html HTTP/1.1\r\n")
# Add static headers to `Req` block
s_static("Host: 192.168.1.1:80\r\n")
s_static("From: header-data\r\n")
s_static("Content-Type: application/x-www-form-urlencoded\r\n")
# The end of the HTTP headers
s_static("\r\n")

# The part where we want to fuzz
s_string("field1")
s_static("=")
s_string("param_data1")
s_static("&")
s_string("field2")
s_static("=")
s_string("param_data2")
s_static("\r\n")
s_static("\r\n")

session.connect(s_get("Req"))
session.fuzz()

The above fuzzer is aimed to fuzz the HTTP body for POST methods. You can add another additional layer by adding other options other than the POST method by using the next line:

s_group("Method", ["GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE"])

Sysax Multi Server 6.50 BooFuzz fuzzer:

from boofuzz import *

target = 'webbackup'
port = 80
sid = '57e546cb7204b60f0111523409e49bdb16692ab5'
pid="dltslctd_name1" 


# Start a boofuzz session
session = Session(target = Target(connection = SocketConnection("192.168.1.1", 80, proto='tcp')))

# Initialize a new block request named `Req`
s_initialize("Req")

# Add a static value to `Req` block
s_static("POST /scgi?sid="+sid+"&pid="+pid+".htm HTTP/1.1\r\n")
# Add static headers to `Req` block
s_static("Host: 192.168.1.1:80\r\n")
# The end of the HTTP headers
s_static("User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0\r\n")
s_static("Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n")
s_static("Accept-Language: en-us,en;q=0.5\r\n")
s_static("Accept-Encoding: gzip, deflate\r\n")
s_static("Referer: http://gotcha/scgi?sid="+sid+"&pid="+pid+".htm\r\n")
s_static("Proxy-Connection: keep-alive\r\n")
s_static("Content-Type: multipart/form-data; boundary=---------------------------20908311357425\r\n")
s_static("Content-Length: 1337\r\n")
s_static("If-Modified-Since: *\r\n")
s_static("\r\n")
s_static("-----------------------------217830224120\r\n")
s_static("\r\n")
s_static("\r\n")
s_static("\r\n")
# The part where we want to fuzz
s_string("RANDOM")
s_static("\r\n")
s_static("\r\n")

session.connect(s_get("Req"))
session.fuzz()

The above fuzzer is aimed to fuzz the ability to upload files.

Ultra MiniHTTPd 1.2 BooFuzz fuzzer:

from boofuzz import *

# Start a boofuzz session
session = Session(target = Target(connection = SocketConnection("192.168.1.1", 80, proto='tcp')))

# Initialize a new block request named `Req`
s_initialize("Req")

# Add a static value to `Req` block
s_static("POST /")
# The part where we want to fuzz
s_string("AAAA")
s_static(" HTTP/1.1\r\n")
# Add static headers to `Req` block
s_static("Host: 192.168.1.1:80\r\n")
s_static("From: header-data\r\n")
s_static("Content-Type: application/x-www-form-urlencoded\r\n")
# The end of the HTTP headers
s_static("\r\n")
s_static("\r\n")

session.connect(s_get("Req"))
session.fuzz()

The above fuzzer is aimed to fuzz only the path part.

XiongMai UC-HTTPd 1.0.0 BooFuzz fuzzer:

from boofuzz import *

# Start a boofuzz session
session = Session(target = Target(connection = SocketConnection("192.168.1.1", 80, proto='tcp')))

# Initialize a new block request named `Req`
s_initialize("Req")

# Add a static value to `Req` block
s_static("POST /login.htm HTTP/1.1\r\n")
# Add static headers to `Req` block
s_static("Host: 192.168.1.1:80\r\n")
s_static("From: header-data\r\n")
s_static("Content-Type: application/x-www-form-urlencoded\r\n")
# The end of the HTTP headers
s_static("\r\n")

# The part where we want to fuzz
s_string("command")
s_static("=")
s_string("login")
s_static("&")
s_string("username")
s_static("=")
s_string("payload")
s_static("&")
s_string("password")
s_static("=")
s_string("PoC")

s_static("\r\n")
s_static("\r\n")

session.connect(s_get("Req"))
session.fuzz()  

The above fuzzer is aimed to fuzz the HTTP body for POST methods.

HFS Http File Server BooFuzz fuzzer:

from boofuzz import *

# Start a boofuzz session
session = Session(target = Target(connection = SocketConnection("192.168.1.1", 80, proto='tcp')))

# Initialize a new block request named `Req`
s_initialize("Req")

# Add a static value to `Req` block
s_static("HEAD /?mode=")
s_string("payload")
s_static(" HTTP/1.1\r\n")
# Add static headers to `Req` block
s_static("Host: 192.168.1.1:80\r\n")
# The end of the HTTP headers
s_static("\r\n")

session.connect(s_get("Req"))
session.fuzz()  

The above fuzzer is targeted towards the HTTP body for POST methods.

Oracle 9i XDB 9.2.0.1 BooFuzz fuzzer:

from boofuzz import *

# Start a boofuzz session
session = Session(target = Target(connection = SocketConnection("192.168.1.1", 80, proto='tcp')))

# Initialize a new block request named `Req`
s_initialize("Req")

# Add a static value to `Req` block
s_static("GET / HTTP/1.1\r\n")
# Add static headers to `Req` block
s_static("Host: 192.168.1.1:80\r\n")
s_static("Authorization: Basic ")
# The part where we want to fuzz
s_string("payload")
s_static(" \r\n")
# The end of the HTTP headers
s_static("\r\n")

session.connect(s_get("Req"))
session.fuzz()  

The above fuzzer is targeted towards the Authorization header value.

Summary of the examples

The examples are similar to each other. There are many ways to write a fuzzer using Boofuzz. In the provided examples, we start by defining a session, then initialize a block, then pushing data onto the created block. boofuzz.s_static() is used to add static strings onto blocks. boofuzz.s_string() is used to define buffers that get fuzzed. Another option is to use boofuzz.Bytes() to add binary byte strings to blocks instead of ascii strings. BooFuzz has many other features that can be used to customize the structure of boofuzz blocks and customize its behavior.

A more generic approach is provided in the BooFuzz examples folder. The sample uses more than a block and a variety of HTTP methods with well-documented variables.

from boofuzz import *

def define_proto(session):
    # disable Black formatting to keep custom indentation
    # fmt: off
    req = Request("HTTP-Request", children=(
        Block("Request-Line", children=(
            Group(name="Method", values=["GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE"]),
            Delim(name="space-1", default_value=" "),
            String(name="URI", default_value="/index.html"),
            Delim(name="space-2", default_value=" "),
            String(name="HTTP-Version", default_value="HTTP/1.1"),
            Static(name="CRLF", default_value="\r\n"),
        )),
        Block("Host-Line", children=(
            String(name="Host-Key", default_value="Host:"),
            Delim(name="space", default_value=" "),
            String(name="Host-Value", default_value="example.com"),
            Static(name="CRLF", default_value="\r\n"),
        )),
        Static(name="CRLF", default_value="\r\n"),
    ))
    # fmt: on

    session.connect(req)


def define_proto_static(session):
    s_initialize(name="Request")
    with s_block("Request-Line"):
        s_group("Method", ["GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE"])
        s_delim(" ", name="space-1")
        s_string("/index.html", name="Request-URI")
        s_delim(" ", name="space-2")
        s_string("HTTP/1.1", name="HTTP-Version")
        s_static("\r\n", name="Request-Line-CRLF")
        s_string("Host:", name="Host-Line")
        s_delim(" ", name="space-3")
        s_string("example.com", name="Host-Line-Value")
        s_static("\r\n", name="Host-Line-CRLF")
    s_static("\r\n", "Request-CRLF")

    session.connect(s_get("Request"))

def main():
    session = Session(
        target=Target(connection=TCPSocketConnection("127.0.0.1", 80)),
    )
    define_proto(session=session)
    session.fuzz()

main()

Finding a new unknown bug in MiniWeb HTTP Server.

So know you have a good understanding of how Boofuzz works. Now, practically apply this on an example I would recommend downloading MiniWeb HTTP Server and start fuzzing it to find new bugs.

To practice

First, install Boofuzz:

$ sudo apt-get install python3-pip python3-venv build-essential
$ mkdir boofuzz && cd boofuzz
$ python3 -m venv env
$ source env/bin/activate
(env) $ pip install -U pip setuptools
(env) $ pip install boofuzz
  • To download MiniWeb HTTP Server: https://sourceforge.net/projects/miniweb/files/miniweb/0.8/miniweb-win32-20130309.zip/download.

You will notice that the process opens up a listener at port 8000, and if you browse, it will find the page shown below.

.

  • Write your own fuzzer and then run it

Sure enough, it crashed the program. It crashed it using the next request:

GET <<-A-LOT-OF-THEM<< HTTP/1.1\r\n
Host: 10.10.1.169:8000\r\nAAAA\r\n\r\n

I didn’t copy the whole request, but you can see in the one below screenshot that it stops at test number 296.

.

If we go to the server, we will find the process crashed and frozen, as shown below.

.

The found crash looks different from the one reported on Exploit-DB, so it’s very likely that this is a different vulnerability. I am sure if we attach the process to the fuzzer, we will end up with way more crashes than this, but we already know this is a poorly designed server, so it is not worth the time.

Finally, you can attach it to a debugger and find where the crash occurs exactly, and develop a PoC to exploit it.

The end

I hope you find tons of vulns : )

Resources

  • https://grayhat.co/event/bb-1011-fuzzing-and-finding-vulnerabilities-with-winafl-afl/
  • https://boofuzz.readthedocs.io/en/stable/
  • https://github.com/jtpereyda/boofuzz
  • https://github.com/jtpereyda/boofuzz/tree/master/examples