Today, we are going to discuss a lesser-known vulnerability, HTTP request smuggling.
Behind this somewhat intimidating term, HTTP Request Smuggling, lies a very interesting vulnerability.
DISCLAIMER: For this blog post, I assume that you are technically familiar with the HTTP protocol. If you are not, I have released a video as well as a blog post on the subject.
Before delving into request smuggling, let's take a step back and review its versions (talking about HTTP) to better understand the origin of the vulnerability.
It's important to note that in its 0.9 version, the only way to send 3 requests was to open 3 separate TCP/IP connections to the server and request the target document each time. As you can imagine, this was quite cumbersome and resource-intensive.
![]() |
---|
How HTTP operated in version 0.9 |
HTTP has always been, and still is, a rather simple protocol, especially in its 0.9 version. Take note of the use of \r\n
to represent the carriage return and line feed characters, which are line-ending markers. This will be important later on.
![]() |
---|
Explanation of what \r\n is (used to mark the end of a line) |
With the arrival of HTTP/1.0, a very important feature was introduced, as seen in the video and blog post on HTTP : headers. Here's the first request in HTTP/1.0:
![]() |
---|
How HTTP operates in version 1.0 with \r\n displayed |
It's crucial to note the Content-Length header, which corresponds to the number of 7-bit ASCII characters starting from <html
, including line-ending characters, totaling 138 bytes.
To fully understand request smuggling, remember that size matters (within the context of HTTP, of course).
With the arrival of HTTP/1.1, three important features were added (especially for understanding request smuggling):
The Connection: Keep-Alive mode is a header that signals to the server that it can handle multiple requests/responses within the same TCP/IP connection.
As you can imagine, this changes significantly compared to version 0.9, where each request required opening a new TCP/IP connection.
![]() |
---|
Fonctionnement du header keep-alive (source: imperva) |
It's also possible to specify Connection: Close, which instructs the server to terminate the connection after the first request/response exchange. However, remember that the Keep-Alive mode is widely used because it's often enabled by default on web servers to optimize connections.
Another significant addition in this version of HTTP is pipelining. This concept involves bundling multiple HTTP requests into a single TCP connection without waiting for responses for each request. As you might have already understood, the operation of pipelining heavily relies on the Keep-Alive header mentioned just above.
![]() |
---|
Pipelineless operation |
This greatly optimizes the speed of request/response
because person B won't have to wait for person A's response to retrieve their own response!
![]() |
---|
Operation with pipelining |
Finally, in version 1.1, came the chunked transfer (Transfer-Encoding: chunked), which is an alternative to the Content-Length header.
While Content-Length announces the final complete size of the message (as seen previously), chunked transfer allows transmitting a message in several small packets (chunks). Each chunk announces its size using a special last chunk with an empty size to mark the end of the message. Once again, you'll notice the \r\n
AKA CRLF
.
![]() |
---|
Representation of Chunks in an HTTP Request |
It was a bit lengthy, but now you have all the necessary information to understand request smuggling.
Let's recap:
In an architecture with a frontend and a backend:
When the frontend server forwards HTTP requests to a backend server, it typically sends multiple requests over the same TCP/IP connection, as we saw with pipelining.
Hence, the front end and back end must agree on the boundaries between requests, or else, an attacker could send an ambiguous request that is interpreted differently by the front and back ends.
HTTP Request Smuggling occurs when an attacker sends a request to the front end that contains two HTTP requests in one.
This may seem a bit unclear at the moment, but we will see how this is possible!
Most request smuggling vulnerabilities arise because the HTTP specification provides two different ways to specify where a request ends: the Content-Length header and the Transfer-Encoding header.
Given this specificity, it's possible for the same message to use both methods simultaneously, causing them to conflict with each other.
Request smuggling involves placing the Content-Length header and the Transfer-Encoding header in a single HTTP request and manipulating them in a way that the frontend and backend process the request differently.
Let's take this HTTP request as an example:
![]() |
---|
HTTP POST Request |
As you can see, even though the two servers use a different header to determine the size of the request, everything works smoothly.
![]() |
---|
Behavior of Two Servers Regarding This HTTP Request |
But what happens if an attacker adds a second header indicating the size at the end of the request?
He will add the Transfer-Encoding header, which is used to specify that the message body uses chunked encoding.
This means that the message body contains one or more chunks of data. Each chunk consists of the chunk size in bytes (in hexadecimal), followed by a newline, and then the content of the chunk. The message ends with a zero-sized chunk.
![]() |
---|
HTTP Request for Performing Request Smuggling |
So, what happens?
As you can see in the GIF, the front-end server processes the Content-Length header and determines that the request body is 6 bytes long, ending at SMUGGLED. This request is forwarded to the backend.
The backend processes the Transfer-Encoding header and, therefore, treats the message body as using this header.
It processes the first chunk, which is declared as having zero length and is, therefore, considered to end the request (because of \r\n0\r\n\r\n
). The subsequent bytes, SMUGGLED, are not processed, and the backend server considers them the start of the next request in the sequence. So, SMUGGLED is treated as a new request, and it would be possible to include a payload like "GET /admin" to bypass the frontend and access the admin panel.
![]() |
---|
Operation of Request Smuggling |
There are several types of request smuggling based on the behavior of the two servers:
Now that request smuggling is clearer to you, let's move on to the demonstration!
To prevent vulnerabilities related to request smuggling, here are the measures to follow:
http-smuggling | makina-corpus
HTTP-keep-alive | imperva
HTTP request smuggling| portswigger
http-request-smuggling | thehacker.recipes