Post

PortSwiggerlabs: HTTP request smuggling, confirming a CL.TE vulnerability via differential responses

PortSwiggerlabs: HTTP request smuggling, confirming a CL.TE vulnerability via differential responses

Information :

This lab involves a front-end and back-end server, and the front-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.

Note :

Although the lab supports HTTP/2, the intended solution requires techniques that are only possible in HTTP/1. You can manually switch protocols in Burp Repeater from the Request attributes section of the Inspector panel.

Solution :

Quick note:

HTTP Request Smuggling happens because of a disagreement between servers. In a typical setup you have a front-end (proxy, load balancer, CDN) forwarding requests to a back-end, and they share persistent TCP connections. The issue is that HTTP gives two ways to tell a server where a request body ends: Content-Length, which just counts bytes, and Transfer-Encoding: chunked, which sends the body in chunks ending with a zero-size chunk. When both headers are present in the same request, different servers may prioritize differently and that gap is what we exploit.

The idea is: you craft a request the front-end considers complete and forwards normally, but the back-end reads it differently and treats part of your body as leftover data sitting in the TCP buffer. That leftover then gets prepended to the next incoming request — yours or another user’s. That’s how you end up bypassing access controls, hijacking requests, or poisoning responses.

The three main variants are CL.TE, TE.CL, and TE.TE , basically which server trusts which header, or in the TE.TE case, how you obfuscate the Transfer-Encoding header enough that one server just ignores it entirely.

Before jumping into Burp, two things worth knowing: first, disable Update Content-Length in Repeater options , if Burp auto corrects your Content-Length you’re defeating the whole point. Second, make sure you’re sending over HTTP/1, not HTTP/2. When testing manually, timing delays are your main signal if the response hangs after your crafted request, the back-end is likely waiting for data that isn’t coming, which means it’s parsing things differently from the front-end. That’s your confirmation.

Now back to the lab:

image

Now first thing first , we go to Burp , send our request to Repeater to start modifying it :

image

Detecting CL.TE :

Now to detect the vulnerability i will follow this graph from Jarno Timmermans :

image

Why do we get a delay ?

Let’s say we’re sending this payload :

1
2
3
4
5
6
7
Content-Lenght: 6
Transfer-Encoding: chunked
\r\n
1\r\n
123\r\n
X\r\n
# The \r\n are just line separators (We can easily show them using burp) . 

Now if the Front end uses Content Length (CL) : it will only transfer 1\r\n123 And stop , the rest : \r\nX\r\n will be ignored .

From there if the Backend Server uses Transfer-Encoding (TE) , it will wait for the 0 (the terminating chunk) inside the request body to know that when the request ends . But it won’t recieve anything , that’s why we get a timeout .

So if we ever get a delay this is a huge indicator that we got a CL/TE vulnerability

Confirming CL.TE :

Now to confirm the Vulnerability i will use this amazing graph from Jarno Timmermans that summarizes the attack perfectly , this is what i used to understand the attack .

image

We first send an attack Request , which will be a POST Request containing our payload :

1
2
3
4
5
6
7
8
POST / HTTP/1.1
Content-Length: 41
Transfer-Encoding: chunked 

0\r\n
\r\n
GET /Throws404 HTTP /1.1\r\n
X-Ignore: X

Now since the Front end uses CL , it will send the entire request , but Since the Backend uses TE , the moment it sees 0 it will stop since it will consider the request to have ended , so the rest will be stored in the server’s Buffer .

image

1
2
GET /Throws404 HTTP /1.1\r\n
X-Ignore: X

Now if we make a new request , just a normal one that should get a 200 :

1
2
3
GET / HTTP/1.1\r\n
Host: lab.....
\r\n

Once it reaches the backend it will look like this :

1
2
3
4
GET /Throws404 HTTP /1.1\r\n
X-Ignore: XGET / HTTP/1.1\r\n
Host: lab.....
\r\n

Which will return a 404 instead confirming that we were able to smuggle another request when we sent our Attack Request .

Now one important thing to note is that We must not have a return line after the “X-Ignore: X”, otherwise it will look like this :

image

1
2
3
4
5
GET /Throws404 HTTP /1.1\r\n
X-Ignore: X
GET / HTTP/1.1\r\n
Host: lab.....
\r\n

Which will be treated like 2 different GET Requests.

Now to test this , we first go to Repeater , then from Inspector we change the request attribute to HTTP/1.1 :

image

Then we send it to confirm that the server accepts HTTP/1.1 :

image

From there we change the request method : it will change it to POST :

image

Then we delete the unecessary Headers , we only need the HOST , Content-Type and content-Length , then we remove the hidden non printable characters to see our return lines (\r\n)

image

From there we remove the update content-length since that’s what we’re exploiting :

image

We’re putting it at 6 so that it never Forwards the X to the backend .

Now if we send the request :

image

We see that the request took way too long and eventually we get a request time out since the backend still expects the 0 to end the request .

Perfect this does confirm the vulnerability , now what we should do is send a new request containing the endpoint we want to store in the buffer , this time we don’t care about the length since we’re looking to end the request with a terminating character (0) on the backend before we try to make any request .

In other words , what we’re doing is sending a new request where we add this inside the body :

1
2
GET /Non_Existing HTTP /1.1\r\n
X-Ignore: X

But before we need to tell the server that the request ended before sending our new one , we do that by adding the 0 before this , so the command should be :

1
2
3
4
5
6
7
8
9
POST / HTTP/1.1
Host: 0a49005304a3846d80dde979003b00b9.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 7
Transfer-Encoding: chunked

0
GET /Non_Existing HTTP /1.1\r\n
X-Ignore: X

Now 2 things to note here , we can’t add line returns after the X-Ignore like we mentionned before , and for content lenght let’s re enable auto update length :

image

Perfect , now to confirm that our new request for /Non_Existing is indeed in the buffer , if we request a normal valid endpoint , we should get an error .

Since it will automatically append it to the /Non_Existing that we had smuggled earlier :

image

Perfect we get a Not Found as expected .

If we send it again , it should work just fine :

image

Now finally if we go back to the lab , we should see that it was solved .

image

That was all for this lab , see you in the next one :)

This post is licensed under CC BY 4.0 by the author.