The lab
here is how portswigger describes it
This lab involves a front-end and back-end server, and the back-end server doesn't support chunked encoding. There's an admin panel at /admin, but the front-end server blocks access to it.
To solve the lab, smuggle a request to the back-end server that accesses the admin panel and deletes the user carlos.
this follows the cl.te front end bypass lab. the goal is identical, get past a front end block on /admin by smuggling the request to the back end. the only thing that changes is the direction of the disagreement, this is te.cl the front end trusts Transfer-Encoding and the back end trusts Content-Length, so the smuggle request is shaped with chunk sizes rather than a zero chunk up front
The idea#
the front end enforces the block and the back end does not, and a smuggled request never passes through the front end as a request, so it slides past the block just like the last lab. what differs is how we hide the request. in te.cl the front end reads chunks, so we open with a chunk whose stated size is big enough to swallow our whole smuggled request as chunk data, while the back end reads only a tiny Content-Length and stops almost at once, leaving everything after it as a new request on the connection.
Step 1 - Confirm the front end blocks admin#
we try to visit /admin directly and it is refused with Path /admin is blocked.

so the direct route is closed and we go in through the back end by smuggling.
Step 2 - Smuggle a request for admin#
we intercept a request, send it to repeater, switch to http/1 in the inspector disable the automatic content length update and send this.
POST / HTTP/1.1
Host: your-lab-id.web-security-academy.net
Content-Length: 4
Transfer-Encoding: chunked
60
POST /admin HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
x=1
0
one important detail, there must be a trailing \r\n\r\n after that final 0, an extra blank line, or the chunked body is not properly terminated and the attack fails.
How this request works?#
the front end obeys Transfer-Encoding: chunked. our body opens with 60, which is hexadecimal for 96, so the front end reads the next 96 bytes as one chunk, and those 96 bytes are the entire block from POST /admin down through x=1. it then reads the closing 0 chunk and treats the whole thing as one complete request, which it forwards to the back end.
the back end obeys Content-Length: 4. that says the body is only 4 bytes, and the first 4 bytes are 60 followed by a carriage return and newline. so the back end reads just those 4 bytes as the body of our post, decides the request ends there, and everything after it, starting at POST /admin HTTP/1.1, becomes the smuggled request waiting at the front of the connection.
the smuggled POST /admin carries its own Content-Length: 15 with a small x=1 body, and that is deliberate, it makes the smuggled request absorb a few of the following bytes as its body so it stays well formed and lined up rather than colliding with whatever arrives next.

we send it a second time so our request meets the smuggled one

the smuggled POST /admin runs on the back end, but the response says Admin interface only available to local users. same as the previous lab, the back end only serves admin to requests that look local, which it decides from the Host header, so our smuggled request needs Host: localhost
Step 3 - Add a localhost host header#
we add Host: localhost to the smuggled request. its block is now longer, so the opening chunk size has to grow to match, from 60 to 71.
POST / HTTP/1.1
Host: your-lab-id.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 4
Transfer-Encoding: chunked
71
POST /admin HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
x=1
0

we send it twice, the smuggled POST /admin now runs as a local request with a single valid Host: localhost, and the admin panel comes back.
Step 4 - Delete carlos#
now we swap the smuggled request for a GET to the delete endpoint with carlos as the username, and again the chunk size changes.
POST / HTTP/1.1
Host: your-lab-id.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 4
Transfer-Encoding: chunked
87
GET /admin/delete?username=carlos HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
x=1
0

why the chunk size is 87 ?#
the opening number is the size of the smuggled block in hexadecimal, and the front end uses it to know how many bytes to read as one chunk, so it has to match the block exactly. each time we changed the smuggled request the block got longer, so the number had to be recalculated, 60 for the plain admin request, 71 once we added Host: localhost, and now 87 because the request line grew from /admin to the longer /admin/delete?username=carlos. if this number is wrong the front end reads too few or too many bytes and the smuggle breaks, so whenever you edit the smuggled request you must recount its length and update the chunk size to the new hexadecimal value.
we send it twice, the smuggled delete runs on the back end as a local admin request, and carlos is deleted.

with this, we solved the lab!
