HTTP Request Smuggling: Part-2 (Identify & Exploit)

Praveen Kanniah
Nerd For Tech
Published in
14 min readJul 31, 2021


in simple words: For Pen-Testers

In case you were one of those members who took a free trip to space and landed back here directly, its recommended that you take a look at Part-1 first :)

HTTP Request Smuggling vulnerability, an old timer, resurfaced when James Kettle, a security researcher, found interesting ways to exploit the vulnerability in 2019. This is an effort to simplify the white paper, Part 2.


Assuming you have read Part-1 and now that we are clear on the basic concepts, let’s take a look at identifying and exploiting the different combinations of HTTP Request Smuggling. But before that, let’s address the elephant in the room.

Addressing the elephant in the room

“According to the HTTP/1.1 standards, if both Content-Length and Transfer-Encoding headers are passed, Transfer-Encoding takes precedence.”

The obvious meaning of course, is that if two headers were passed, Transfer-Encoding takes precedence, but also that, servers must be capable of processing both the headers at any given time. Let’s look at this with an example:

Ideal Scenario
  • User blue sends an ideal request with only Content-Length header, the front-end process CL and the back-end also processes CL, the response is happily served
  • Similarly, user green sends an ideal request with only Transfer-Encoding header, the front-end processes TE and the back-end also processes TE, the response is happily served

On contrary, lets break our smuggled request it into two parts:

Parent and Child smuggled requests
  • Parent Smuggled Request - A request that has both the headers to trick the servers
  • Child Smuggled Request - An ideal request hidden inside the parent-smuggled request, why ideal? because it only has one of the headers

So, what’s really the elephant in the room we are addressing here ? The front-end server processes CL and forwards the entire request to the back-end server.

When the above request reaches the back-end server, the parent-smuggled request is processed with the Transfer-Encoding header and when the unprocessed chunk aka our child-smuggled request is left out and when the next user’s request gets added to it, the child smuggled request now is processed with the Content-Length header, which means that a server or specifically the back-end server in this case is capable of processing both CL and TE at any given time and thats the elephant in the room :)

Back-end processing both TE and CL

Awesome right ? Yes it is, now if you understood this well, HTTP request smuggling is as easy as a piece of cake.

Identifying the combination CL.TE or TE.CL

I got several questions after Part-1, which were mostly, how do i identify that the front-end is CE and back-end is TL or vice versa. Good question. And that is exactly why i wanted to address the elephant in the room first, and that is, servers are capable of processing both the headers, if sent one at a time.

So what is CL.TE or TE.CL then? It’s our payload or more specifically the result of our payload, the payload that tricks one server to process Content-Length and another server to process Transfer-Encoding or vice versa. Let’s take an example of such a payload and dissect, note - there is a space in front of Transfer-Encoding:

Content-Length: 10
Transfer-Encoding : chunked

It’s also key to understand, HTTP/1.1 is just a standard and servers implement it, which also means every server is going to have its own implementation of the same standard.

The above payload lead to a CL.TE:

  • the front-end server processed only Content-Length even when two headers were passed, why ? The server had a validation to not process headers with a space in front of it, so it saw the Transfer-Encoding header as an invalid one and easily picked Content-Length
  • The back-end server in contrary processed Transfer-Encoding, why ? Possible that the server did not have validation for spaces in front of the headers, meaning it accepted space and also the Transfer-Encoding header, so it found both the headers and according to the HTTP/1.1 standard Transfer-Encoding took precedence

Frankly, this is just one payload, there are a number of payloads like this to confuse the servers and arrive at a combination.

Summarising it all, to identify request smuggling and its combination, we will need to try all of the available payloads and see which one works. But remember, this needs to be done safely without disrupting the application.

The HTTP Request Smuggler burp extension does a pretty good job in trying out all possible payloads and alert you if a combination is identified. It also identifies the vulnerability safely using time-out based mechanisms.

Know when to capture the Queue

This vulnerability is not like the traditional ones where you send a HTTP request with the payload and based on the immediate response you will be able to confirm the existence of the vulnerability. You are dealing with multiple HTTP requests inside a TCP connection, remember.

There are two possible scenarios when you are trying to identify/exploit HTTP Request Smuggling:

  • Someone else getting the response - You want someone else to get the response of the smuggled request we sent. Example - when we are trying to de-sync the queue. How do we do this ?simple, just send the parent-smuggled (red) request, wait for the queue to get de-synced, someone else(green) gets the unexpected response
Someone else gets the response
  • You want the response back - Most of the time, someone else getting a response is not going to help us. It’s because when we are trying to identify/exploit the vulnerability, as a proof, we will need the response back to ourselves. So, how do we do it? Well, we become user green ourselves meaning we need send multiple HTTP requests continuously/concurrently and capture the queue so that the chances of getting our response back is increased. The red is also our request, dark red is also our request.
we get back the response

In Burp, you can use the Repeater to send multiple requests and observe the difference in responses or you can also use the Turbo Intruder to send concurrent requests. In ZAP, you can use Fuzz and configure concurrent scanning threads in Options.

Exploiting CL.TE - (SSRF)

Ok, lets get into the actual story, in Part-1, we saw how we could de-sync a queue using CL.TE combination and made a user receive a response which the user didn’t intend to receive. Let’s go a step further and look at how we can exploit CL.TE to launch an SSRF attack.

Networks, applications are always configured with ease of use as priority and not security. You will find certain applications designed in such a manner, where, once you are past the front-end server, you have access to either a limited set of internal applications or pretty much the full stack, Remember these are otherwise only accessible, if you are inside an organisation’s network.

Back-end server being a proxy

In the above example, the back-end server is a proxy. It forwards the requests received from the front-end to a different server based on headers like HOST header. So what happens if we sent the below request, with ‘stg’ environment value in the HOST header.


The front-end server will out-right reject the request because the HOST header value is a staging environment and it will not let you access its internal network.

But remember how in certain applications once you are past the front-end server you can access limited or full internal stack, what happens if we somehow put this request directly to the back-end server? Then there is definitely a chance that this request gets processed by the ‘stg’ server. Now, does that ring a bell ? Yes? No?Ok, how about this, we use CL.TE, place a smuggled request that accesses the ‘stg’ server and have it processed only by the back-end server. Cool ?

In real-time, what we want to access via SSRF is very application specific or component specific, like trying to access a tomcat admin page. For this scenario, let’s assume there is a ‘/admin’ page in ‘’ which has default credentials ‘admin:admin’ and that is what will try to do an SSRF on.

Revising our concepts on whether we need the response back here or not, its SSRF, so we definitely need the response back. And if we need our response back, we need to capture the queue, meaning we send multiple requests along with our parent-smuggled request to increase the chances of getting back our response. Next, our parent-smuggled request, what happens if sent this request to the front-end server ?

Parent-Smuggled Request
  • Front-end server receives the request, ignores Transfer-Encoding, probably because its validation engine does not permit repeated headers, processes Content-Length. Its value is 159, which is the entire size of the body including the child smuggled request. Its perfect and it forwards the request to the back-end.
  • The back-end server receives the request, ignores Content-Length probably because its validation engine does not have any rules for repeated headers, so it sees both CL and TE headers and according to HTTP/1.1 standards, Transfer-Encoding takes precedence, it processes the first chunk which is a valid but an empty chunk. It cannot process the next chunk, because it’s invalid, the chunk remains.
  • Now, user green’s request comes. Note, since we are sending multiple requests, we are user green as well, so our next request would be:
our next request in the queue

This gets appended to our unprocessed chunk

Next request gets appended to the unprocessed chunk
  • Now is the interesting part, remember the elephant in the room, the same back-end server that processed the parent-smuggled request with TE, will now process the child-smuggled request with CL. And what is the CL we have specified ? 25, which is exactly the length of our POST body so that the next request is ignored.
  • If the /admin page indeed had the default credentials, the response will be sent back to user green which in this case is also us and boom the SSRF attack is successful.
HTTP/1.1 200 OKYou login is successful !

Using Request-Reflection to identify HEADERS

We can consider the above to be a simple example. All we had to do was change the host header in the child-smuggled request, right ? Now, what if we received this response instead of a 200 OK.

HTTP/1.1 400 Bad RequestMissing Internal Header 1 and Internal Header 2

Note, an example of an internal header in real-time would be an ‘X-Forwarded-For’ header. Wait, but who adds these headers ? The front-end processes the requests and adds these additional internal headers. Ok, now how do we find these internal headers, let’s look at a way to do it by using a concept called Request Reflection.

Note- Request Reflection will be an additional pre-step you have to do, before you do the actual SSRF step.

To explain Request Reflection, let’s consider the below HTTP Request/Response:


POST /forgotuser HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 26
Connection: Keep-Alive
email=ironman@avengers.comRESPONSEHTTP/1.1 200 OKEmail "" does not exist

Isn’t this a typical scenario where a user input or the value of the email parameter is entirely reflected back in the response? We do see a lot of these during our pen-tests, don’t we ? Ok, now how is this related to getting the internal headers though? Now assume the application which you are trying to exploit an SSRF has a similar kind of request, we will pick it and create a parent-smuggled request. Yes, pre-step is also another request smuggling attack. What happens if we send the below parent-smuggled request to the front-end server?

Parent-smuggled request before processed by Front-End server
  • The same story again, front-end processes CL, the CL is 156, sends the entire request to the back-end. But before it does, it adds a couple of internal headers to the request like this.
Parent-smuggled request after being processed by Front-End server
  • But the sad part is, we will not be able to see these headers, only the back-end server can. We need to figure out a way to see these headers. Don’t worry, it will happen automatically if we captured the queue ;)
  • The back-end receives the request, processes TE, identifies the first chunk, its an empty valid chunk, moves to the next. It would fail processing our child-smuggled request and this would stay as an unprocessed chunk.
  • Now, comes the next request in the queue, since we are capturing the queue, the next request is ours too.
Our next request
  • The internal headers are in here too, of course, the front-end would add the internal headers to all the requests it forwards to the back-end. This request gets attached to the unprocessed chunk.
Our next request gets appended to the unprocessed child-smuggled request
  • Now, back-end will process this request, but as CL, yea, the same elephant in the room concept.
  • But did you notice the CL in this request to be actually bigger than the POST body content ? The actual CL for the POST body ‘’ is only 26, but we have mentioned 165. This is where it starts pulling from the next request, which is also our request btw. So, the next request literally gets attached to the email parameter.
  • We know this is a kind of request where “email” parameter’s value is reflected back in the response and for a wrong email you will get:
HTTP/1.1 200 OKEmail "ironman@avengers.comGET /accountdetails HTTP/1.1
Internal-header: 1
Internal-header: 2
Cookie: JSESSIONID=45af3e9b-e498-4582-afab-" does not exist

Voila, we have got our internal headers and its value. Attach these headers to the child-smuggled request, change the Content-Length of the parent-smuggled request to accommodate the internal headers, send it and BOOM, SSRF should be successful once again.

Content-Length: 188
Transfer-Encoding: chunked
Transfer-Encoding: x
Connection: Keep-Alive
0POST /admin HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Internal-Header: 1
Internal-Header: 2

Content-Length: 25
user=admin&password=adminSSRF RESPONSE:HTTP/1.1 200 OKYou login is successful !

Remember, to make this pre-step successful, the key is to play around with the Content-Length in the child-smuggled request, like how it was 165, keep increasing them until you get what you want or until it times out after you are beyond the request.


Exploiting TE.CL (Request Storage)

The last part, i promise :) Lets, look at exploiting TE.CL using Request Storage. Request storage is very similar to Request reflection except that its going to store somewhere, not the same but somewhat similar to reflected and stored XSS. Lets consider a request where you are trying to update your bio from Photographer to blogger:

Bio in UI
PUT /updateprofile HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 21

Now, lets frame our parent-smuggled request and see what happens if we forwarded it to the front-end server?

  • Front-end server sees both the headers, the validation engine does not validate for the “tab” before chunked in the Transfer-Encoding header. So it sees both the headers and Transfer-Encoding takes precedence. It processes the chunk, its a valid one, it sends the entire request to the back-end server.
  • The back-end server ignores the Transfer-Encoding probably because its validation engine does not allow any header value to have a “tab”, so it processes the valid Content-Length header. The Content-Length is 4, so only “96” and “\r\n” is processed in the above request. The remaining unprocessed data which is our child-smuggled request stays in the back-end server.
  • Note for this attack, we don’t have to capture the queue, we just send the first parent-smuggled request and leave it. So the next request that the front-end server forwards to the back-end server is actually of another user’s.
Actual request of another user

WARNING - Don’t really expect such a jackpot request when you are testing :) but you can expect a cookie for sure !

Now, then next user’s request gets attached to our unprocessed child-smuggled request

Unprocessed request gets attached to another user’s request
  • The back-end server processes the request, it reads the only valid header Content-Length. Its value is 150, which is larger than the child-smuggled request’s POST body, hence it reads the values from another user’s request. Again, it will only read the number of characters specified in the Content-Length. But the most important point here is these extra characters get added to the “bio” parameter.
  • Since we are not capturing the queue and sending only 1 request, the response actually will go another user, right ? We don’t care, why ? The real twist here is when the application code processes the request before sending a response, it will attach the extra characters from another user’s request in our “bio” parameter.
  • So, if we went to our profile page, now we will able to see another user’s request with cookies/parameters.
Another user’s request stored in our BIO

The only downfall with request storage is, since the application code processes the request, it would differentiate parameters starting with ‘&’, so it might cut off any values post the first ‘&’ in your request. But otherwise we are good.

Thats it !!! I would recommend that you read James Kettle’s white paper after this.

Frankly, the second part has been like a bad snake and ladder game for me :) Every time i assumed a concept was right, i was thinking i was closing to 100 in the game, but the snake in 95 would put me down and i would be back to square 1. I hope you enjoyed reading the blog :)

CLAP, if you liked it. You know you can add any number of claps right ? Good Day !

Special Thanks to my friends Stanko Jankovic and Prateek Kumar Nischal for helping me on clearing a few concepts :)




Praveen Kanniah
Nerd For Tech

Loves Application Security. Breaks “Complexity Bias”