Request Smuggling
Last updated
Last updated
In modern web applications, traffic often flows through a front-end server (e.g., a load balancer or reverse proxy) that forwards HTTP requests to back-end servers. To improve efficiency, the front-end sends multiple requests over the same connection to the back-end. For this to work, both servers must agree on where one request ends, and the next begins. An HTTP request smuggling attack exploits discrepancies in this interpretation between the front-end and back-end. An attacker sends an ambiguous request that is interpreted differently by the two servers, allowing the attacker to "inject" their request into the next one. This can lead to severe vulnerabilities, such as bypassing security controls or stealing data. Request smuggling is primarily associated with HTTP/1 requests. However, websites that support HTTP/2 may be vulnerable, depending on their back-end architecture.
HTTP/1 specification provides two different ways to specify where a request ends:
Content-Length
header (Number of byte in body)
Transfer-Encoding
header (Chunked)
NB: /n/r/n/r
after 0
In HTTP/1, you can sometimes exploit discrepancies between how servers handle standalone newline (\n
) characters to smuggle prohibited headers (no \r\n
because all HTTP/1 servers agree that this terminates the header).
On the other hand, in HTTP/2 we can try \r\n
because messages are binary rather than text-based.
Try in Name Header and Value Header.
See also HTTP/2-exclusive vectors.
In many applications, the front-end server performs some rewriting of requests before they are forwarded to the back-end server (typically by adding some additional request headers such as X-Forwarded-For
).
To reveal exactly how the front-end server is rewriting requests:
Find a POST request that reflects the value of a request parameter into the application's response.
Shuffle the parameters so that the reflected parameter appears last in the message body.
Smuggle this request to the back-end server, followed directly by a normal request whose rewritten form you want to reveal.
On-site redirects from one URL to another and place the hostname from the request's Host
header into the redirect URL (ex. Apache and IIS web servers eith a request for a folder without a trailing slash, /home
into /home/
).
If we have Location: /example/
(relative path), we can try to inject URL in path.
If any part of your front-end infrastructure is caching content from the back-end (typically static resources for performance reasons), it may be possible to poison the cache with the off-site redirect response.
The smuggled request reaches the back-end server, which responds as before with the off-site redirect. The front-end server caches this response against what it believes is the URL in the second request. From this point onwards, when other users request this URL, they receive the redirection to the attacker's web site.
Similar to web cache poisoning but the attacker causes the application to store some sensitive content belonging to another user in the cache, and the attacker then retrieves this content from the cache.
Smuggle a complete request instead of just a prefix, this beacause for this attack there must be no closing of the TCP connection by either server (servers typically close incoming connections when they receive an invalid request).
Notice that no invalid requests are hitting the back-end, so the connection should remain open following the attack.
To make it easier to differentiate stolen responses from responses to your own requests, try using a non-existent path in both of the requests that you send (to receive a 404 response).
Now, desynchronizing the response queue.
An attacker can continue to steal responses like this for as long as the front-end/back-end connection remains open. Exactly when a connection is closed differs from server to server, but a common default is to terminate a connection after it has handled 100 requests. It's also trivial to repoison a new connection once the current one is closed.
However, as we've learned from looking at CL.0 attacks, it's possible to cause a desync using fully browser-compatible HTTP/1.1 requests.
Probe and Confirm desync CL.0 vector
Use PoC below and add to your malicious site
Identify an exploitable gadget (ex. request that write cookie in comment post)
Web servers and reverse proxies often do downgrading (the process of rewriting HTTP/2 requests using HTTP/1 syntax to generate an equivalent HTTP/1 request) in order to offer HTTP/2 support to clients while communicating with back-end servers that only speak HTTP/1.
If a website is vulnerable to either H2.CL or H2.TE request smuggling, you can potentially leverage this behavior to perform the same attacks that we see in HTTP/1.1.
HTTP/2 requests don't have to explicitly specify their length in a Content-Length
header, but they can. During downgrade, if a Content-Length
header is not present, front-end servers add it to create the HTTP/1.1 request, otherwise they simply reuse the one already present.
The spec dictates that any content-length
header in an HTTP/2 request must match the length calculated using the built-in mechanism, but this isn't always validated properly before downgrading.
As a result, it may be possible to smuggle requests by injecting a misleading content-length
header. Although the front-end will use the implicit HTTP/2 length to determine where the request ends, the HTTP/1 back-end has to refer to the Content-Length
header derived from your injected one, resulting in a desync.
Chunked transfer encoding is incompatible with HTTP/2 and the spec recommends that any transfer-encoding: chunked
header you try to inject should be stripped or the request blocked entirely. If the front-end server fails to do this, and subsequently downgrades the request for an HTTP/1 back-end that does support chunked encoding, this can also enable request smuggling attacks.
When we looked at response queue poisoning, you learned how to split a single HTTP request into exactly two complete requests on the back-end. In the example we looked at, the split occurred inside the message body, but when HTTP/2 downgrading is in play, you can also cause this split to occur in the headers instead.
:method
GET
:path
/
:authority
vulnerable-website.com
foo
bar\r\n
Host: vulnerable-website.com\r\n
\r\n
GET /admin HTTP/1.1
Or
:path
/
?cachebuster=1
HTTP/1.1\r\n
Foo: bar
(HTTP/2)
When rewriting, some front-end servers add the new Host header to the end of the current list of headers, so you need to manually insert it into the header for the first request. You will also need to adjust the positioning of any internal headers that you want to inject in a similar manner.
Many of the request smuggling attacks we've covered are only possible because the same connection between the front-end and back-end server is used for handling multiple requests. HTTP request tunnelling provides a way to craft high-severity exploits even when there is no connection reuse at all.
You can still send a single request that will elicit two responses from the back-end. This potentially enables you to hide a request and its matching response from the front-end altogether. You can use this technique to bypass front-end security measures that may otherwise prevent you from sending certain requests.
Some front-end servers only read in the number of bytes specified in the Content-Length
header of the response, so only the first response is forwarded to the client (Blind
).
You can occasionally make these vulnerabilities non-blind by using HEAD requests (responses to HEAD
requests often contain a content-length
header).
:method
HEAD
:path
/example
:authority
vulnerable-website.com
foo
bar\r\n
\n\r
GET /tunnelled HTTP/1.1\n\r
Host: vulnerable-website.com\n\r
X: x
Try using different endpoint that returns a longer or shorter resource (Content-Length
) or use a reflected input in HEAD request or in the tunnelled request.
With non-blind request tunnelling, you can effectively mix and match the headers from one response with the body of another. If the response in the body reflects unencoded user input, you may be able to leverage this behavior for reflected XSS in contexts where the browser would not normally execute the code.
:status
200
content-type
text/html
content-length
174
As caching takes place on the front-end, caches can also be tricked into serving these mixed responses to other users.