HTTP Request Smuggling
Table of Contents
Guide 11 in Sarah’s Welcome to Web Security Series #
In this series, Sarah discusses some common vulnerability classes found in web security and how you can find and exploit them. Today’s focus is HTTP request smuggling, a more advanced type of attack that interferes with the way a sequence of HTTP requests is processed by a website.
Introduction #
HTTP request smuggling is, in theory, a fairly straightforward attack. In practice however, it is a difficult vulnerability to test for and tricky to execute as it requires a high degree of patience and precision. However, it can have severe consequences for the victim system as it often results in an attacker gaining direct, unauthorised access to protected data. Moreover, it is generally also associated with HTTP/1 requests, though certain architectures may be vulnerable even if they use HTTP/2 in the front-end. The general principle behind a smuggling attack is that the front-end and back-end systems communicate with requests. However, the systems have to agree where one request starts and ends; this is done through the use of specific HTTP/1 headers (Content-Length
and Transfer-Encoding
). A smuggling attack will interfere with how the back-end interprets the request, allowing an attacker to effectively attach a malicious request to the next request.
Finding Vulnerable Requests #
As aforementioned, the vulnerability arises from the Content-Length
and Transfer-Encoding
headers. They function slightly differently from each other but both can be used in the attack.
Content-Length
simply gives the length of the message body in bytes. For example:
POST /search HTTP/1.1
Host: my-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 12
query=monsec
The Transfer-Encoding
header is a little more complicated as rather than giving the length in bytes, it gives the way in which data is being encoded. For smuggling attacks, the value is usually chunked
, meaning the data contains one or more chunks of data. Every ‘chunk’ has the chunk-size in bytes, written in hexadecimal, then the content of the chunk and then a chunk of size zero is used to terminate the request. Each is separated by a newline (and the end of chunks may sometimes need to be carefully formatted to take this into consideration). For example:
POST /search HTTP/1.1
Host: my-website.com
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked
c
query=monsec
0
Noting that ‘c’ is the hexadecimal representation of ‘12’.
Smuggling vulnerabilities arise when the front-end and back-end server do not agree on the limits of successive requests due to manipulated headers.
If this is still confusing, imagine a postal service that has a front desk and a processing centre. When you want to send a parcel, you have to send in a little slip that tells the front desk how many parcels you are sending. Now, Eva wants to get away with cheaper postage so she writes on the slip that she is sending 2 parcels, but she actually has 3. All the parcels arrive at the post office in a big bag. Alice, at the front desk, takes the slip and sees that there are 2 parcels being sent, so she charges Eva for 2 parcels. Bob, in the processing centre, receives the big bag and sees the three parcels. Assuming that Alice has charged Eva for 3 parcels, he packages them and sends them on their way. This means that Eva basically got free postage on one parcel!
The same general concept applies to HTTP request smuggling, where the front-end and back-end’s behaviour is manipulated by changing how the request is processed (more specifically, by changing how the servers understand the ‘start’ and ’end’ of the request). In this example, Alice perceived the “end” of Eva’s “request” (a request to send some parcels) to be 2 while Bob thought it was 3. Rather than getting free postage, however, an attacker can send a malicious request to further compromise the system.
Exploiting Vulnerable Requests #
To actually exploit this confusion, an attacker has to use both types of headers to manipulate the servers into misprocessing successive requests. There are three ways this can be done:
- CL.TE: The front-end uses
Content-Length
and the back-end usesTransfer-Encoding
- TE. CL: The front-end uses
Transfer-Encoding
and the back-end usesContent-Length
- TE.TE: Both ends use
Transfer-Encoding
but one of the servers can be manipulated into not processing the request correctly, usually through obfuscation
In a CL.TE attack, an attacker will trick the back-end server into not reading the rest of the request by including a zero-length chunk at the start and then including a malicious sequence afterwards.1 This entire request is processed as a single request in the front-end (because we tell it that the message is 13 bytes long, which indeed it is). However, in the back-end, it is processed as two requests because of the leading zero-length chunk, illustrated by the green and red colouring. The red portion of the request will be treated as the start of the next request.
POST / HTTP/1.1 Host: my-website.com Content-Length: 13 Transfer-Encoding: chunked 0 |
badstuff |
---|
A TE.CL attack is very similar, but this time an attack will trick the back-end into thinking that the request is much shorter than it actually is by manipulating the Content-Length
header. Here, the front-end processes the request as one chunk and then sends it to the back end, which believes that the message is only 3 bytes long because of the Content-Length
header. Once again, the red portion will be interpreted as the beginning of the subsequent request.
POST / HTTP/1.1 Host: my-website.com Content-Length: 3 Transfer-Encoding: chunked 8 |
badstuff 0 |
---|
A TE.TE attack is a little different because rather than using two types of headers, an attack will obfuscate the Transfer-Encoding
header. This can be done in a number of ways but then the rest of the attack follows the same pattern as the other two,2 by including a second request at the end of the body of the main one.
Regardless of which variant is being used, it is always challenging to create correct chunks and to calculate the correct number for the Content-Length
header. However, BurpSuite does have an extension called “HTTP Request Smuggler” which can simplify this process a little bit. Moreover, in regards to what the ‘badstuff’ actually is, it can be any form of malicious HTTP request. A very simple example could be to force a 404 error with:
GET /404 HTTP/1
X-Ignore: X
More complex payloads might access restricted pages (such as an admin panel), capture other users’ requests (because recall that the malicious payload is prepended to the subsequent request) or be used as an entry point for other attacks, such as web cache poisoning.
Defending Against HTTP Request Smuggling #
The easiest way to defend against HTTP request smuggling is to not use HTTP/1, on both the front-end and back-end systems. This may require banning HTTP downgrading in some circumstances. It is also prudent to reject requests that have newlines in headers, unexpected colons or spaces to prevent obfuscated headers getting through. Furthermore, do not make the assumption that requests will not have a body because this can lead to client-side desynchronisation errors and it is the root cause of vulnerabilities arising from Content-Length: 0
. These are not foolproof methods, as a persistent attacker may yet find a way around such restrictions, but they can certainly help.
Conclusion #
HTTP request smuggling is a complex, and possibly critical, vulnerability. Unfortunately, some cybersecurity professionals are not aware of the risks posed by chunked encoding, due to the fact that it is not really seen in browsers’ responses (only really in servers) and tools like BurpSuite automatically unpack chunks by default. However, HTTP/2 is inherently immune to this vulnerability because there is no way to introduce ambiguity about the message length. Therefore, ensuring that your front and back-end use HTTP/2 is the best way to defend against this class of vulnerability.
If you are interested in learning more about HTTP request smuggling and web security, the WebSecurity Academy is a fantastic resource. You can create a free account and explore their labs here.
Recall that when using chunked encoding, a zero-length chunk denotes the end of a request. ↩︎
By “a number of ways”, this basically means you can “muck around with spaces, symbols and spelling” to obfuscate it a bit. E.g.
Transfer-Encoding: xchunked
andTransfer-Encoding : chunked
will both obfuscate the header. ↩︎