| Platform | Type | Difficulty | Objective |
|---|---|---|---|
| PortSwigger Web Security Academy | Web Cache Deception | Apprentice | Trick the cache into storing carlos's /my-account response by using a crafted URL with a .js extension, then retrieve the cached API key |
Exploiting path mapping for web cache deception¶
Logged in as wiener:peter and pulled up /my-account to see what the baseline response looked like.
No cache headers on that response, which made sense — /my-account is dynamic content and shouldn't be cached under normal circumstances. To figure out what the cache actually does treat as cacheable, went looking for a known static resource on the site:
GET /resources/js/tracking.js HTTP/2
Cache-Control: max-age=30
X-Cache: hit
Confirmed the cache stores responses for anything ending in .js. That's the rule to abuse. Next step was checking whether the origin server actually cares about extra path segments after /my-account, so tried appending a fake .js filename:
GET /my-account/teto.js HTTP/2
HTTP/2 200 OK
Cache-Control: max-age=30
X-Cache: hit
Content-Length: 3928
(my-account body including API key)
That's the discrepancy right there. The origin server uses REST-style URL mapping, so it abstracts /my-account as the endpoint and just ignores the extra /teto.js segment tacked onto the end, returning the account page like normal. The cache, on the other hand, only cares about what the path ends with — it sees .js and treats the whole response as a cacheable static resource, API key and all.
With the discrepancy confirmed, the plan was to get carlos's browser to request that same crafted path so his own account response — API key included — would land in the cache under a URL I could just request myself afterward. Hosted a redirect on the exploit server to do that:
<script>
document.location="https://0a66006f043a31f08083b87e00cd00c0.web-security-academy.net/my-account/miku.js"
</script>
Delivered the exploit to the victim. Carlos's browser requested /my-account/miku.js on his own authenticated session, the origin server returned his account page complete with his API key, and the cache stored that response keyed to the .js path — no path traversal or anything fancy needed, just the plain extension mismatch between how the two servers parse the URL.
Requested /my-account/miku.js myself right after:
Got back the cached response with carlos's account data and his API key sitting right there. Submitted it to close out the lab.
Lab solved aaa