Skip to content

Request Smuggling

Takes advantage of the way a Front-end and Back-end handles the two headers that determine the size of the request. In this case I refer to the Front-end as a load-balancer, reverse-proxy, CDN, etc... and the back-end as the webserver.

HTTP/1.1 Pipelining

HTTP/1.1 Pipelining is a key part of a lot of these attacks and it is best to understand what it does. Pipelining allows for multiple requests and responses to use the same connection. This makes requests faster by removing the overhead of establishing a connection for each request. Your browser will probably not use it, but more importantly the connection between a front-end and back-end will use it.

Pipelining relies on either the Transfer-Encoding or Content-Length header to be able to tell when one of the pipelined requests stops and the next one begins.

The front-end may be configured to have a variety of connection settings to the back-end. It may be configured so that all requests use the same connection regardless of their origination. The front-end may use one connection for each source IP. Finally, the front-end may use a separate connection for each request. The range of other users requests can have consequences on any desync attack performed. For example, if all requests use separate connections, you will only be able to desync yourself. Although this may seem like a big limitation, it does not rule out attacks such as web cache poisoning.

One further note on Pipelining. Request Smuggling is easily confused with HTTP/1.1 Pipelining. In the image below I show two requests and two responses. To setup this image I ran an Nginx docker container and created two files. I then requested both files in a single request in Burp. I had to put a space between the host and the port for it to show both response. Even though this may appear to be a smuggled request, it is just Pipelining.

HTTP/1.1 Pipelining

HTTP/1.1

RFC 7230 (first paragraph on page 34) states:

If a message is received with both a Transfer-Encoding and a Content-Length header field, the Transfer-Encoding overrides the Content-Length. Such a message might indicate an attempt to perform request smuggling (Section 9.5) or response splitting (Section 9.4) and ought to be handled as an error. A sender MUST remove the received Content-Length field prior to forwarding such a message downstream.

As we will see, not all servers will ignore the Content-Length when both headers are present.

Counting the Body Length

When sending HTML messages all line-endings are Carriage Return Line Feeds (CRLF). When needed I will use \r\n to represent the CRLF.

I wrote a web-based length calculator found here: Request Smuggling Calculator to help determine the proper values for both the Content-Length and Transfer-Encoding.

Content-Length

Content-Length is a base-10 value counting all characters. So a message like below will have a Content-Length of 32.

This is the body of the request.

This time the Content-Length is 41, this is because at the end of the first line there are the two CRLF characters.

This is the first line.
The second line.

Transfer-Encoding

Tranfer-Encoding is a base-16 value counting all characters between the start and the end of the chunk. The last chunk will have a 0 and be followed by two CRLFs. The following shows the chunk count on the first line, the chunk contents next and then ends with a 0 along with two CRLFs.

20
This is the body of the request.
0

The following chunk has a length of 29:

29
This is the first line.
The second line.
0

CL.TE

In this scenario the front-end does not follow the RFC and ignores the Transfer-Encoding information to instead use the Content-Length header. The back-end uses the Transfer-Encoding header. In the following example the Content-Length includes the whole request and ends its count right after the X. (See Counting the Body Length). The 0 followed by a blank-line is interpreted by the back-end as the end of the body. It will hold the rest of the message (in this case, just the X in the buffer and prepend the next request with it.)

CL.TE Example
POST / HTTP/1.1
Host: 0a4d0003046f739bc095c929003f0034.web-security-academy.net
Content-Length: 6
Transfer-Encoding: chunked

0

X

Message Boundaries

The next request sent to the server will be prepended with the X:

CL.TE Results

Manual Detection

To detect CL.TE issues we can send a request that will make the back-end wait for additional chunks creating an observable time delay and possible a time out response. In this example the front-end will see the length of 4 and determine that the message body ends after the t and forwards it to the back-end. The back-end will see the first chunk with a length of 1 and then not see an ending chunk and blank line. The back-end will wait for that data, eventually timing out:

POST / HTTP/1.1
Host: 0a3f00ce03a47646c065943f0075000a.web-security-academy.net
Content-Length: 4
Transfer-Encoding: chunked

1
t
3

CL.TE Timeout

TE.CL

In this scenario the front-end follows the RFC and will use the Transfer-Encoding header and ignore the Content-Length header. The back-end on the other-hand will prefer the Content-Length header:

TE.CL Example
POST / HTTP/1.1
Host: 0a2a00f2045778e6c0326a68007300c0.web-security-academy.net
Content-Length: 4
Transfer-Encoding: chunked

1
X
0

Message Boundaries

The next request sent to the server will be prepended with the 0:

TE.CL Results

Manual Detection

Detecting TE.CL is a bit trickier. The following example will poison the response queue if the server is vulnerable to CL.TE therefore it is important that we first ensure that we have tested for and concluded that the server is not vulnerable to CL.TE if we want to avoid any potentially harmful testing.

This will timeout on TE.CL vulnerable websites, this is because the first line of the body tells the front-end server that it has reached the end of the body. When the back-end receives the message it will have a longer Content-Length than what it received so it will wait for the remaining text, resulting in a timeout.

POST / HTTP/1.1
Host: example.com
Transfer-Encoding: chunked
Content-Length: 6

0

X

TE.TE

In this scenario, both the front-end and the back-end will use the Transfer-Encoding header if present, but one of the servers can be induced not to process the header. This is done by obfuscating the Transfer-Encoding header in such a way that only one of the two servers will recognize it. Some examples:

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

NOTE: This list is not meant to be exhaustive. It is meant to illustrate some of the possibilities. An exhaustive list would include non-printable ASCII characters such as the Back Space (0x08) character.

The examples shown are each different enough from the specification that it may be possible to have one of the two servers ignore the header, but the other one recognize it. When that happens, it turns into either a CL.TE or TE.CL vulnerability depending on which server ignores the Transfer-Encoding header.

Manual Detection

Using the same request structure as the CL.TE detection, send different variation of the Transfer-Encoding header until either a timeout is received or all options has been exhausted. Once the server is deemed not vulnerable to the CL.TE variation, test the server with the TE.CL variation, again modifying the Transfer-Encoding header for each attempt until a timeout has been received.

Automated Detection

Detection can be performed by the Burp Extension HTTP Request Smuggler written by James Kettle from PortSwigger. To use the tool, right-click on a branch in the Site Map tree or on a single request and then Extensions > HTTP Request Smuggler > Choose an attack or select Launch all scans. Output from the tool can be seen in Extender > Extensions > Output. Requests made by the tool can be seen with something like the Flow extension.

HTTP Request Smuggler

HTTP/2 - More Coming Soon

HTTP/2 uses binary frames to transmit requests. Because of this a pure HTTP/2 is not vulnerable to Request Smuggling. But, not all servers use HTTP/2 end-to-end, instead the front-end server uses HTTP/2 and then downgrades it to HTTP/1.1 before passing it on to the back-end. This process of downgrading requests allows for ambiguity in both the message length and the content of the headers.

Note: Because HTTP/1.1 and HTTP/2 use the same port, the client relies on the ALPN field in the TLS handshake to determine if the server supports HTTP/2. Some websites will support HTTP/2 but not advertise it. So test it anyways. You can do this in Burp Repeater by selecting the Gear Icon and choosing Allow HTTP/2 ALPN override and then in the Inspector > Request Attributes selecting the HTTP/2 Protocol.

Allow HTTP/2 ALPN override

Modify Request Protocol

H2.CL Desync

H2.TE Desync

Possible Results of Request Smuggling

  • Bypass front-end security controls
  • Reveal front-end request rewriting
  • Session hijacking/Retrieve other user's sensitive data
  • Delivering reflected XSS
  • Web Cache Poisoning
  • Web Cache Deception
  • WAF evasion
  • SSRF

Tools

  • Burp Plugin - HTTP Request Smuggler
  • Smuggler - https://github.com/defparam/smuggler

References

Recommendations

  • Front-end: Rely solely on Transfer-Encoding when present
  • Front-end: Normalize ambiguous requests - RFC 7230
  • Front-end: Use HTTP/2 end-to-end
  • Back-end: Drop request and connection
Back to top