Using HTTP request smuggling to hijack a user’s session – exploit walkthrough
During a recent customer engagement, I came across an instance of a rather rare vulnerability class called HTTP request smuggling. Over the course of several grueling days of exploit development, I was eventually able to abuse this vulnerability to trigger a response queue desynchronization, allowing me to capture other users’ requests, leading to session hijacking.
Here’s what I’ve learned, how I did it, and what you should look out for to protect your organization from this new vulnerability.
What is HTTP request smuggling?
HTTP request smuggling has been extremely well documented here by Portswigger, so I’ll briefly cover the basics of my specific case. In short, HTTP request smuggling vulnerabilities arise from a disagreement between a front-end and back-end server about the size of a message’s body. This disagreement can be abused to interfere with the way a web application processes requests received from users.
This is most often triggered by adding both methods of specifying the message length (the “Content-Length” and “Transfer-Encoding” headers) to one single request. If the front-end and back-end servers disagree on which message length specification to use, then a desynchronization occurs, and the application is likely exposed to a request smuggling vulnerability.
Detection of TE:CL request smuggling
Detection of request smuggling vulnerabilities can be tricky because of how headers are defined by the HTTP specification. However, Portswigger have developed an excellent tool to speed up the process. While completing our usual discovery phase against the application in question, we received a prompt from Portswigger’s tool tentatively letting us know that there may be a discrepancy in how the application handles the “Content-Length” and “Transfer-Encoding” headers.
In our case, the detected discrepancy laid in the fact that the front-end would use the “Transfer-Encoding” header and the back-end would use the “Content-Length” header, hence “TE:CL Request Smuggling”. To confirm the prompt from the tool, we performed a request that looked something like this:
In figure 1 above, you can see the front-end receiving our request and defaulting to the “Transfer-Encoding” header (highlighted in yellow). The front-end therefore reads in everything up to, but not including, the “X” character, and forwards the request on to the back-end server. The back-end server, which uses the “Content-Length” header, tries to read in 6 bytes of data but only receives 5, because the front-end ignored the last “X” character as specified by the “Transfer-Encoding” header.
This causes a noticeable delay and eventually a timeout as the back-end server waits for the last byte of data to arrive. This delay confirms a desynchronization exists and allows us to focus on assessing various request smuggling techniques.
Confirming TE:CL request smuggling vulnerabilities
Once we have manually confirmed that a desynchronization occurs, we can attempt to “smuggle” a request to the back-end server, which will prove that the request smuggling vulnerability is exploitable. To do this, we must send a pair of requests. The first will be an attacker’s attempt to smuggle a request prefix into a regular request’s body, and the second will be a regular request that simulates our victim user.
In figure 2 below, you can see the first request in this pair (the attacker’s request). Again, the front-end server uses the “Transfer-Encoding” header which, importantly, is correctly adjusted, forwarding the entire smuggled request prefix in the body to the back-end server.
The back-end, once again, reads in this request using the “Content-Length” header, which stops after the characters “74”. The attacker will now simply receive a response from the original POST request towards “/”. However, the smuggled “GET /404 HTTP/1.1” prefix remains in the request queue as the back-end sees this as the start of a new request. This “new” request has a “Content-Length” of 15 (longer than the specified request body of “x=y 0”) causing the back-end to wait for the remaining bytes.
If the attacker times it right, and the victim happens to send a request shortly after the smuggle request, then their request will be read in by the back-end as the remaining bytes of the smuggled request. Depending on what they’ve smuggled, this gives the attacker control over the user’s .
Figure 3 – Victim user’s perspective of request smuggling
In figure 3 above, you can see the flow of the second request in the chain (the victim user’s request). The entire flow occurs while the back-end server waits for the remainder of the “GET /404 HTTP/1.1” request’s body. Since the back-end is waiting for the remaining bytes, the victim’s request is simply appended to our smuggled prefix, resulting in the victim requesting their profile page, and receiving a 404 page instead. This process confirms that we can manipulate the victim’s request by smuggling requests prefixes.
Researching an exploit
Serving users with random 404 pages is not the kind of impact we’re looking for when highlighting the risks of a vulnerability to our customers. We want to build something that realistically demonstrates the full potential of HTTP request smuggling vulnerabilities.
On a similar vein to the explanation shown in the previous section, we were able to smuggle a prefix for an open redirect vulnerability, given that when smuggling requests, you have complete control over the host header:
In figure 4, you can see the smuggle request built to perform the 302 redirect. When this request is smuggled, any victim request that is appended to the smuggled request will result in a redirection towards Outpost24.com. This would in-theory be almost unnoticeable to the untrained eye given that the redirect may occur when the victim clicks on any link on the legitimate site’s pages. This could allow an attacker to redirect users to malicious content, such as a fake login page, for an advanced phishing attack. While this attack is powerful, it relies on heavy user-interaction and still opens the possibility for a well-trained user to spot that they have been redirected to a new domain.
After much research and frantic googling, I found a quick explanation online of HTTP/1 Response Queue Poisoning, a technique that I initially assumed was only valid in HTTP/2. This exploit, offered the chance to poison the application’s response queue, allowing us to receive a response intended for another user. If we combined that technique with our existing smuggling of partial request prefixes, we would be able to abuse a small, reflected parameter on the application’s login page, to store victim’s requests in a HTTP response. We could then have that response delivered to us (the attacker) instead of the victim.
Response queue desynchronization
The first step in the exploit is to poison the response queue by smuggling an extra request. This will trigger a desynchronization in the response queue, causing users to receive responses belonging to other users. This sounds quite complicated but is actually a simple extension of request smuggling vulnerabilities.
So far, in the examples we’ve looked at, we have always tried to smuggle a request “prefix”. In other words, only the start of a request, causing then next request in the queue to be appended to our smuggled prefix. In these cases, the front-end and back-end never disagree on which request to map a response too. However, if we smuggle an entire request to the back-end server, then the front-end will suddenly be dealing with an extra response that it did not expect. See figures 5, 6, 7 and 8 for an overview of this process.
Figure 8 – GIF of the entire flow
As you can see, by smuggling an extra request into the queue, an attacker is able to receive a victim’s response rather than their own. This attack alone is quite powerful, but for the application I was testing, I wanted to capture the victim’s request as it would always contain the victim’s API access token.
Capturing users’ requests
To capture other users’ requests using response queue poisoning, I needed a gadget that I could use to reflect a victim’s request onto a page. This could be achieved on the application’s login page via traditional request smuggling. The hard part was chaining this process with response queue poisoning, so that I (the attacker) would receive the response that contained the page that the user’s request was written into.
Let’s start by looking at how we can reflect the victim’s request onto the login page. This was achievable, because the login request contained a “VersionInfo” parameter that would reflect arbitrary input onto the page (Figure 9). All we needed to do is smuggle this request so that the user’s request ends up as the content of the “VersionInfo” parameter.
In figure 10 above, you can see how I smuggled the reflection gadget. Remember that the victim user’s request would be appended to this one, due to the very large “Content-Length” header. Figure 11, shows a demonstration of reflecting our own request back onto the login page using this technique.
Now that I have shown that a request can be reflected onto the login page, it’s time to chain the attack with response queue poisoning so that I (the attacker) receive the response to the reflection gadget and end up capturing another user’s request. To achieve this, I sent something like the following request:
In figure 12 above, you can see a representation of the final payload used in this attack. The request simultaneously causes a response queue desynchronization and smuggles the prefix for our reflection gadget request, resulting in the victim’s request being stored in the response to the “/ReflectionGadget” endpoint which, thanks to the response queue desynchronization, is sent back to the attacker.
To break this down, I have highlighted each individual request. In black, you can see the normal request smuggling attack, with the “Transfer-Encoding” and “Content-Length” headers set in order to smuggle both requests to the back-end server.
In green, you can see the request that causes the response queue desynchronization. Remember, this request has a correctly adjusted “Content-Length” header. This request also intentionally takes a long time to be processed to increase the likelihood of a victim’s request ending up in the right position in the queue (just after our smuggled “ReflectionGadget” request).
In red, the “ReflectionGadget” request, which is a request prefix (because its “Content-Length” is much larger that the size of the actual request body) allowing our victim’s request to be appended to it.
Figure 13 – Final exploit request flow
In figure 13, you can see the final exploit flow, resulting in the attacker receiving the victim’s request. By repeatedly sending the first attacker’s request while there is an active victim user, the application will eventually serve us a response containing the victim user’s request, allowing us to steal their access token and therefore hijack the user’s session.
Figure 14 below shows the final exploit being used to capture other users’ requests. Initially we capture our own request, which is a common side effect of sending our exploit over and over again to the application. However, the second response we check, contains a victim user’s request, including their access token.
Figure 14 – Capturing users’ requests live.
Protect your organization from vulnerabilities and exploits
To prevent HTTP request smuggling vulnerabilities, we recommend the following high-level measures (from Portswigger), as well as regular and manual testing. You will only find this type of vulnerability with in-depth manual pen testing services. Security testers will need time to detect and develop vulnerabilities into a finding and Proof-of-Concept (like capturing a users’ request).
Pen testing is a vital way to make sure flaws in an application don’t turn into serious threats – but it can be a challenging and time-consuming task to undertake in-house. The vulnerability and exploit shown in this article is a fantastic example of the lengths our testers will go to push a vulnerability to its limits. It’s also worth noting that it can take days focusing on findings like these to build a workable proof-of-concept that can be demonstrated concisely in our findings.
Outpost24 offer a comprehensive pen testing as a service (PTaaS) solution, SWAT, which delivers continuous monitoring of internet facing web applications via a SaaS delivery model. The solution can be fully customized to your needs, minimizing unnecessary load, or risk to any sensitive environments. Most vulnerability findings are produced by our in-house testing team, and peer reviewed by a senior pen tester.
SWAT combines the depth and precision of manual penetration testing with vulnerability scanning to secure web applications at scale. You can also interact directly with our security experts for validation and remediation guidance, all via the portal. Learn more about SWAT and request a live demo.
Ghost Labs is the specialist security unit within Outpost24, offering enhanced security services such as advanced network penetration testing, web application testing, Red Teaming assessments and complex exploitation. In addition, the Ghost Labs team is an active contributor to the security community with vulnerability research and coordinated responsible disclosure programs.
Ghost Labs performs hundreds of successful penetration tests for its customers ranging from global enterprises to SMEs. Our team consists of highly skilled ethical hackers, covering a wide range of advanced testing services to help companies keep up with evolving threats and new technologies.