Skip to content

narfindustries/http-garden

Repository files navigation

The HTTP Garden

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.

Acknowledgements

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.

Getting Started

Dependencies

  1. The HTTP Garden runs on x86_64 and AArch64 Linux, and is untested on other platforms.
  2. The target servers are built and run in Docker containers, so you'll need Docker.
  3. You'll also need Python 3.12+ and the following Python packages, which you can get from PyPI (i.e. with pip) or from your system package manager:
  • docker
    • For interacting with Docker
  • pyyaml
    • For parsing yaml
  • tqdm
    • For progress bars

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.

Running

  • Build and start up some servers and proxies:
./garden.sh start --build gunicorn hyper nginx haproxy
  • From another shell, start the repl:
./garden.sh repl
  • Send a basic GET request through HAProxy, then send the result to Gunicorn, Hyper, and Nginx origin servers, and display whether their interpretations match:
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.

Directory Layout

images

The 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).

tools

The tools directory contains the scripts that are used to interact with the servers. Inside it, you'll find

  • diagnose_anomalies.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,
  • ...and a few more scripts that aren't user-facing.

Targets

HTTP Servers

Name
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
protocol_http1
puma
tornado
twisted
undertow
uvicorn
waitress
webrick
yahns

HTTP Transducers

Name
apache_httpd_proxy
apache_traffic_server
envoy
go_stdlib_proxy
h2o_proxy
haproxy
lighttpd_proxy
nghttpx
nginx_proxy
openlitespeed_proxy
pound
squid
varnish
yahns_proxy

Omissions

The following are explanations for a few notable omissions from the Garden:

Name Rationale
Anything from Microsoft MSRC told us "HTTP smuggling is not consider a vulnerability," and I feel no particular need to help Microsoft.
unicorn Uses the same HTTP parser as yahns.
SwiftNIO Uses llhttp for HTTP parsing, which is already covered by node_stdlib.
Bun Uses picohttpparser for HTTP parsing, which is already covered by h2o.
Deno Uses hyper for HTTP parsing, which is already in the Garden.
Daphne Uses twisted for HTTP parsing, which is already in the Garden.
pitchfork Uses the same parser as yahns.
nghttpx Uses lhttp for HTTP parsing, which is already covered by node_stdlib.
Cheroot Ignores our reports.
CherryPy Uses cheroot for HTTP parsing.
libhttpserver Uses libmicrohttpd for HTTP parsing, which is already in the Garden.
Werkzeug Uses the CPython stdlib for HTTP parsing, which is already in the Garden.
Caddy Uses the Go stdlib for HTTP parsing, which is already in the Garden.
Tengine Uses Nginx's HTTP parser.
OpenResty Uses Nginx's HTTP parser.
Google Cloud Global External Application Load Balancer Based on Envoy.
Google Cloud Regional External Application Load Balancer Based on Envoy.
Phusion Passenger Uses llhttpd for HTTP parsing, which is already covered by node_stdlib.
passim Uses libsoup for HTTP parsing, which is already in the Garden.
boa Unmaintained.
Ulfius Uses libmicrohttpd, which is already in the Garden.
Vultr Load Balancer It's just HAProxy, which is already in the Garden.
VMWare Avi Load Balancer It's just Nginx, which is already in the Garden.
Sanic Uses httptools, which is already covered by Uvicorn.
openwrt uhttpd Ignores our reports
CPython http.server Not intended for production use.
Cheroot Has too many bugs. Would consider reintroducing in the future.
openjdk_stdlib Provides no coherent vulnerability disclosure channel.
gevent Not intended for production use. See this issue.
dart_stdlib Ignored prior reports.

Results

See TROPHIES.md for a complete list of bugs that the Garden has found.

About

Differential testing framework for HTTP implementations

Topics

Resources

License

Stars

Watchers

Forks