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.
To solve the lab, smuggle a request to the back-end server, so that a subsequent request for / (the web root) triggers a 404 Not Found response.
this follows the cl.te lab. that one had the front end trusting Content-Length and the back end trusting Transfer-Encoding. this lab is the mirror image, the two servers swap which header they obey, so the smuggle request has to be built the other way around.
what is te.cl vulnerability ?#
the naming stays the same, cl is Content-Length and te is Transfer-Encoding, and the pair tells you which server trusts which header. te.cl means the front end uses the Transfer-Encoding and the back end uses the Content-Length. the description confirms it from the other direction by saying the back end does not support chunked encoding, so the back end can only count bytes with Content-Length while the front end honours the chunked Transfer-Encoding.
we made the back end stop early by giving it a zero chunk. now it is the front end that reads the chunks, so we use the chunk sizes to make the front end swallow a big block as one chunk, while the back end, reading only a tiny Content-Length, stops almost immediately and treats the rest of that block as a fresh request. the leftover is our smuggled request.
Step 1 - Set repeater up for raw http#
as before, the lab runs over http/2 and smuggling is an http/1 problem, so we intercept a request, send it to repeater, and in the inspector switch the protocol to http/1. we also open the repeater menu and make sure the update content length option is unchecked, because this attack depends on us setting a deliberately wrong Content-Length and we do not want burp fixing it for us.
Step 2 - Send the smuggle request#
here is the request.
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
5e
POST /404 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
x=1
0
how this request works?#
the request carries both length headers again, but this time the trick is in the chunk sizing. let me walk it through.
the front end obeys Transfer-Encoding: chunked. in chunked encoding each chunk starts with its length in hexadecimal on its own line, and our body starts with 5e, which is hex for 94. so the front end expects a chunk of 94 bytes, reads exactly the next 94 bytes as the chunk data, and those 94 bytes are the whole block from POST /404 down to x=1. after that it reads the final 0 chunk and considers the body complete. so the front end sees one tidy request and forwards the entire thing to the back end.
the back end obeys Content-Length: 4. that says the body is just 4 bytes long. the first 4 bytes of the body are 5e followed by the carriage return and newline, so the back end reads only those 4 bytes as the body and decides our post request ends right there. everything after those 4 bytes, starting at POST /404 HTTP/1.1, is bytes the back end never accounted for, so it treats them as the beginning of the next request on the connection. that is the smuggled request.
the smuggled POST /404 even carries its own Content-Length: 15. that is there to keep the back end from choking, it makes the smuggled request wait for a little more body, which is neatly supplied by the x=1 and the leading bytes of whoever's request arrives next, so the smuggled request stays well formed and lined up.
Step 3 - Send it again and read the Differential response#
the first send leaves the smuggled POST /404 parked at the front of the back end connection. we send the same request a second time

this time our request runs into the smuggled POST /404 waiting ahead of it, so the back end answers the /404 first and returns a 404 Not Found. the request we sent was for the web root, and yet the response is a 404, which that request would never produce on its own.

that mismatch is the confirmation. the response does not correspond to the request we just sent, which can only happen because an earlier request left a fragment behind in the connection, and that is te.cl smuggling proven by differential responses. we did not need to read any private data, the response simply changing because of a request we sent before is proof enough that the front end and back end disagree on where the body ends.

with this, we solved the lab!
