Post

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

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

Information :

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.

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

Detection :

Now the first thing we should do is send a specific request and based on the response we will know whih case we’re in , is it a CL.TE , TE.CL or TE.TE .

For the specific request either use the one provided by HackerReciepe :

1
https://www.thehacker.recipes/web/config/http-request-smuggling/#practice

Personally i like to follow this graph from Jarno Timmermans

image

They both follow the same principle , we first send a request and check whether we get a Delay, Rejection ,etc .

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 looking at the graph , in case the front end is using Chunked transfer encoding , well since there is no 0 to end the request it will immediately reject it , but we don’t know still whether the backend is using TE , or CL , since the request was rejected before it even reaches the backend .

This is when we use the second payload on the graph :

1
2
3
4
5
6
Content-Lenght: 6
Transfer-Encoding: chunked
\r\n
1\r\n
0\r\n
X

In case we send this one , and get a delay , this means the backend is using Content length (since it is waiting for the 6 characters , but it only gets the 5 and it hangs waiting for the rest , but the rest is never sent since for the front end the request has already ended (since it got the terminating 0 ).

And since the front end uses TE , and the backend uses CL , this is an indication that the server is vulnerable to a TE.CL attack .

More explanation :

The first request we sent had Transfer-Encoding: chunked But with no 0\r\n to terminate the request , so in case the front end Rejects it , this proves that it uses TE .

Now to Determine the backend we send a different request , this time we send a request with 6 bytes , but we put a terminating character at the 5th byte , so that only 5 bytes are sent to the backend .

Here the front end is working since it got the terminating character (0\r\n) , and for the backend , in case it was using CL , it will cause a delay in the response , since it’s expecting 6 bytes to consider that the request has ended , but instead it will only get 5 since that’s what the front end sent , and from there it will keep waiting for the last byte which is never sent , which causes the delay .

This is exactly what TE.CL attack is , front end uses TE while backend uses CL to validate whether the request has ended .

Confirmation/Exploitation :

We can use Differential responses , the concept is always the same :

  • We have an Attack Request , in this one we will sepecify whether the CL or TE to be just enough for it to pass the Front end .
  • Once it reaches the backend, the request is well crafted so that the backend takes in a part of the request and store the rest in the buffer .
  • From there we send a normal request which will be appended to what was inside the buffer , so we’re kinda injecting our old attack request into the new one that comes in , hence the name smuggling.

In our case , the TE.CL can be explained using this graph from Jarno Timmermans again :

image

The orange part in the graph is exactly what was stored in the buffer when sending the first request . Since we know the server uses CL , we can specify it to be 4 , and from there have the rest of the request stored in the buffer so that it gets appended to the next request (normal request).

  • The appended request has 15 in the CL , while the entire request stored in the buffer has 10 bytes, which means only 5 bytes of the normal request will be appended , that’s exactly “GET /” and the rest will be ignored .

Don’t worry if this doesn’t make sense to you yet, the practical example should make things clear :

Practical Example :

Detection :

This is the original request :

image

As we said before we first send the detection payload :

  • First we downgrade it to HTTP/1.1 .
  • Add Transfer-Encoding : Chunked .
  • Show Non printable characters, this will be needed to count the number of Bytes.
  • Delete unneccessary Headers .
  • Disable Auto Content-Length .
  • Change Method to POST .
  • Add the Paylod inside the POST Body :
1
2
3
4
5
6
7
8
9
POST / HTTP/1.1
Host: 0a3e004c036d22cc80ebdae70069001a.web-security-academy.net
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Content-Lenght: 6
Transfer-Encoding: chunked

1
123
X

image

Now if we send it :

image

We immediately get an error , this proves that the front end uses TE chunked , since there is no terminating character we got an error .

Now to determine what the Backend uses :

1
2
3
4
5
6
7
8
9
POST / HTTP/1.1
Host: 0a3e004c036d22cc80ebdae70069001a.web-security-academy.net
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Content-Lenght: 6
Transfer-Encoding: chunked

0

X

image

Notice how we’re not adding a return line after the X , this is very important . Now if we send it :

image

We see that we’re getting a delay . Since the backend is waiting for the 6 bytes , for now it has only recieved 5 bytes :

1
2
0\r\n
\r\n

but for the front end the request has already ended after the terminating character (0\r\n) so it will drop the rest and the X is never sent to the backend server , so to confirm that the backend uses CL we should get a delay since X it is dropped, the backend never gets the 6th byte which causes a delay so this confirms that the backend is using CL not TE . And eventually we will get a time out error :

image

Perfect now onto the confirmation phase using a differential attack :

Confirmation :

Now first we need the Attack Request :

1
2
3
4
5
6
7
8
9
10
11
12
13
POST / HTTP/1.1
Host: 0ae800c703d22723817966ca004f0089.web-security-academy.net
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Trasnfer-Encoding: chunked
Content-Length: 'Not_Defined_yet'

<Chunk_Size_Not_Defined_Yet>
POST /throws404 HTTP/1.1
Host: 0ae800c703d22723817966ca004f0089.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 'Not_Defined_yet'

x=1

image

Now we need to specify the Content Length , then since we want the Front end to forward the entire request, we will add the terminating character at the end : (0\r\n).

For the Content Length of the smuggled request , if we select the content of our body, we see that we have 10 Bytes :

1
2
3
x=1\r\n
0\r\n
\r\n

image

We need the CL to be bigger than the actual value of our POST Request , since the backend expects a number of CL to know when the request ended, if we add more Bytes this will append the content of the Next request as well . So we just need the CL to be bigger than 10 , we’ll put 15 for this one .

Now for the Chunk Size , this will go from the start of the Body (POST /Throws404….) until the line before the terminating character without adding the return line character (Without adding the \r\n )

image

We see on Inspector that it is 165 Bytes , and the HEX value is 0xa5 so we will replace the Chunked size with a5 .

Now for the Final Content Length for the entire Request , since the server uses CL , we want it to only process the Bytes before the second request , so in our case just the characters before POST /404 ….

1
a5\r\n

In this case it’s only 4 bytes, and the rest we want it to be stored in the Buffer so that it gets appended to the next normal request .

image

So the Final request will look like this :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST / HTTP/1.1
Host: 0ae800c703d22723817966ca004f0089.web-security-academy.net
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Trasnfer-Encoding: chunked
Content-Length: 4

a5
POST /throws404 HTTP/1.1
Host: 0ae800c703d22723817966ca004f0089.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 15

x=1
0

image

Now we just send it and we should get a 200 :

image

Now the backend only processes the first 4 bytes and keep the rest in the Buffer which means the Buffer contains the :

1
2
3
4
5
6
7
POST /throws404 HTTP/1.1
Host: 0a13001804b3697b81469862004800c2.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 15

x=1
0

So next time we make a normal request , it should be appended to the request inside the Buffer and this should return a 404 :

image

Perfect , we got our Error .

Now if we refresh the lab , we should see that it was solved :

image

That was all for this lab, hope i explained the attack in a clear way, if you have any feedbacks please feel free to reach out, see you in the next one :)

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