Exploiting HTTP request smuggling to perform web cache deception

4 min read Medium PortSwigger
HTTP request smuggling
Contents

On this page

The lab

here is how portswigger describes it

This lab involves a front-end and back-end server, and the front-end server doesn't support chunked encoding. 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.

what is anti caching header ?#

before the attack, a quick word on the header that would have stopped it. servers can mark a response as not cacheable using headers like Cache-Control: no-store, Cache-Control: private, or Pragma: no-cache. these are anti caching headers, they tell any cache in front of the site that this response is personal or sensitive and must never be stored. a logged in account page, which contains private data, should always carry one. checking the responses here, the account page has no such header, so nothing is telling the cache to keep its private data out, which is the gap this attack drives through.

The idea#

the front end caches static resources, it decides what to cache mostly by the url, so a request that looks like a path to a static file gets its response stored and reused. deception works by getting a victim's private page returned as the answer to one of those cacheable requests.

we smuggle a request for /my-account. when the victim's own request comes down the shared connection, our smuggled /my-account is answered using the victim's session, because the victim's cookies ride along with their request, so the back end produces the victim's real account page, api key included. the front end, meanwhile, thinks it is caching the victim's original request for a static resource, so it stores that account page response under the static url. then we simply request that static url ourselves and read the victim's api key straight out of the cached copy.

Step 1 - Log in and check for caching headers#

we log in with the provided wiener:peter credentials, then look through the burp history at the response headers.

1111

the account page response has no anti caching header on it, no Cache-Control: no-store or private, so the front end cache is free to store it if it is ever tricked into treating it as a cacheable resource. that is the weakness we build on.

Step 2 - Smuggle a request for the account page#

we intercept a request, send it to repeater, and smuggle this.

HTTP
POST / HTTP/1.1
Host: your-lab-id.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 42
Transfer-Encoding: chunked

0

GET /my-account HTTP/1.1
X-Ignore: X
2222

this is the cl.te frame. the front end forwards the whole body by Content-Length, the back end reads Transfer-Encoding: chunked, ends at the 0 chunk, and leaves GET /my-account as the smuggled start of the next request. the X-Ignore header at the end soaks up the next request's own request line, so our smuggled /my-account stays intact, while the victim's cookies, which come after their request line, still attach to it. that is the crucial part, the smuggled account request runs with the victim's session, so it returns the victim's account page.

we repeat this a few times to keep the smuggled prefix in place on the connection.

Step 3 - Line it up with a cached static path and time the age#

now we load a static asset, /resources/js/tracking.js, the same kind of cacheable path as the previous lab, and use the cache age to time things.

HTTP
GET /resources/js/tracking.js HTTP/1.1
Host: your-lab-id.web-security-academy.net
Connection: close

the timing works exactly as in the poisoning lab. we keep sending the tracking.js request and watch its Age header climb, and the cache refreshes around 30 seconds. when the age reaches about 27, we go back to the smuggle attack request and send it, then come straight back and send the tracking.js request again. the aim is for the cache to refresh at the moment our smuggled /my-account is waiting, so the fresh copy the cache stores under the tracking.js url is actually the victim's account page. if the timing misses we let the age climb again and retry.

Step 4 - Read the api key from the cache and submit it#

once the victim's account page is cached under the static url, we request that url and the cached response hands us the victim's account page, with their api key sitting in it. we copy the key and submit it as the solution.

3333
4444

with this, we solved the lab!

to see the whole shape of it, the site cached static resources by url and never marked its private account page as uncacheable, and request smuggling let us make that private page the response to a cacheable request. we smuggled a /my-account request that ran under the victim's session, timed it so the victim's account page landed in the cache under a static file's url, and then read the victim's api key out of the cache at our leisure. no interaction from the victim beyond simply using the site.