
Differential testing framework for HTTP implementations
The HTTP Garden is a collection of HTTP servers and proxies configured to be composable, along with scripts to interact with them in a way that makes finding vulnerabilities much much easier. For some cool demos of the vulnerabilities that you can find with the HTTP Garden, check out our ShmooCon 2024 talk.
We'd like to thank our friends at Galois, Trail of Bits, Narf Industries, and Dartmouth College for making this project possible.
This material is based upon work supported by the Defense Advanced Research Projects Agency (DARPA) under contract number HR0011-19-C-0076.
pip) or from your system package manager:docker
pyyaml
tqdm
If you're installing Python packages with your system package manager, be aware that the package names may need to be prefixed with py3-, python3-, or python-, depending on the system.
./garden.sh start --build gunicorn hyper nginx haproxy
./garden.sh repl
garden> payload 'GET / HTTP/1.1\r\nHOST: a\r\n\r\n' | transduce haproxy | fanout | grid
'GET / HTTP/1.1\r\nHOST: a\r\n\r\n'
⬇️ haproxy
'GET / HTTP/1.1\r\nhost: a\r\n\r\n'
gunicorn: [
HTTPRequest(
method=b'GET', uri=b'/', version=b'1.1',
headers=[
(b'host', b'a'),
],
body=b'',
),
]
hyper: [
HTTPRequest(
method=b'GET', uri=b'/', version=b'1.1',
headers=[
(b'host', b'a'),
],
body=b'',
),
]
nginx: [
HTTPRequest(
method=b'GET', uri=b'/', version=b'1.1',
headers=[
(b'host', b'a'),
(b'content-length', b''),
(b'content-type', b''),
],
body=b'',
),
]
g
u
n
i h n
c y g
o p i
r e n
n r x
+-----
gunicorn|✓ ✓ ✓
hyper | ✓ ✓
nginx | ✓
Seems like they all agree. (Note that even though Nginx added content-length and content-type headers, the Garden is aware of this and does not let this insignificant discrepancy show up in grid output.)
Let's try a payload that uses a bare LF line ending in a chunked message body. This is disallowed in the spec.
garden> payload 'POST / HTTP/1.1\r\nHost: a\r\nTransfer-Encoding: chunked\r\n\r\n0\n\r\n' | fanout | grid
gunicorn: [
HTTPResponse(version=b'1.1', method=b'400', reason=b'Bad Request'),
]
hyper: [
]
nginx: [
HTTPRequest(
method=b'POST', uri=b'/', version=b'1.1',
headers=[
(b'transfer-encoding', b'chunked'),
(b'host', b'a'),
(b'content-length', b'0'),
(b'content-type', b''),
],
body=b'',
),
]
g
u
n
i h n
c y g
o p i
r e n
n r x
+-----
gunicorn|✓ ✓ X
hyper | ✓ X
nginx | ✓
Okay, so Gunicorn responded 400, Hyper didn't respond, and Nginx accepted. This is a violation of the spec by the Nginx authors that they don't care to fix.
You may also have noticed that even though Gunicorn and Hyper didn't have exactly the same response, they showed as agreeing in the grid output earlier.
This is because their responsees are essentially equivalent (a rejection of the message), and the Garden takes this into account.
imagesThe images directory contains a subdirectory for each HTTP server and transducer in the Garden.
Each target gets its own Docker image.
All programs are built from source when possible.
So that we can easily build multiple versions of each target, all targets are paremetrized with a repository URL (APP_REPO), branch name (APP_BRANCH), and commit hash (APP_VERSION).
toolsThe tools directory contains the scripts that are used to interact with the servers. Inside it, you'll find
probe_quirks.py: A script for enumerating benign HTTP parsing quirks in the systems under test to be ignored during fuzzing,repl.py: The primary user interface to the HTTP Garden,update.py: A script for updating the commit hashes in docker-compose.yml,|-| | aiohttp | | apache_httpd | | apache_tomcat | | appweb | | aws_c_http | | cpp_httplib | | eclipse_grizzly | | eclipse_jetty | | fasthttp | | go_stdlib | | gunicorn | | h2o | | haproxy_fcgi | | hyper | | hypercorn | | ktor | | libevent | | libmicrohttpd | | libsoup | | lighttpd | | mongoose | | netty | | nginx | | node_stdlib | | openbsd_httpd | | openlitespeed | | openwrt_uhttpd | | protocol_http1 | | puma | | tornado | | twisted | | undertow | | uvicorn | | waitress | | webrick | | yahns |