Security Concepts9 min read9 October 2026

What Is Web Cache Poisoning? Security Guide | Yrzo AI

Web cache poisoning lets attackers store malicious responses in caches served to other users. Learn how it works, real CVEs, and how to defend your application.

By Yrzo AI — UK cybersecurity specialists

What Is Web Cache Poisoning?

Web cache poisoning is an attack where a threat actor manipulates a caching layer — a CDN, reverse proxy, or application cache — into storing a malicious response, which is then served to other users who request the same resource. The attacker doesn't compromise the origin server directly. Instead, they exploit the gap between what the cache treats as the cache key (the criteria used to decide whether a cached response can be served) and what actually influences the response the origin generates.

The impact is severe: a single successful cache poisoning request can serve a malicious response to thousands of users before the cache expires or is purged. Unlike a stored XSS attack that requires the attacker to persist a payload in the application's database, cache poisoning persists the payload in the caching infrastructure itself.

How Caching Works

To understand cache poisoning, you need to understand the caching model. When a user requests a resource:

1. The request reaches the cache (CDN edge node, reverse proxy like Varnish or Nginx) 2. The cache checks whether it has a stored response for this request — it does this by comparing the request against its **cache key** 3. If the cache key matches a stored entry (cache hit), the stored response is returned without touching the origin 4. If no match (cache miss), the request passes to the origin server, the response is stored in the cache, and served to the user

The cache key typically includes the URL path and query string. It often excludes other parts of the request — headers like `X-Forwarded-Host`, `X-Forwarded-Scheme`, arbitrary custom headers, query parameters that the application processes but the cache ignores.

This exclusion is the vulnerability surface.

The Cache Poisoning Mechanism

If the cache ignores a header but the origin server uses it to construct the response, an attacker can inject a malicious value in that header. The origin includes it in the response. The cache stores that poisoned response under the normal cache key. Subsequent users requesting the same URL get the poisoned response — without the malicious header in their request.

**Step by step:**

1. Attacker identifies that the origin reflects the `X-Forwarded-Host` header in a script tag or redirect URL 2. Attacker sends a request with `X-Forwarded-Host: attacker.com` — the cache key is the URL only, so this header is excluded from the key 3. Origin generates a response like `<script src="https://attacker.com/analytics.js"></script>` 4. Cache stores this response under the normal URL cache key 5. All subsequent users requesting that URL receive the poisoned response, loading `analytics.js` from the attacker's server 6. Attacker serves malicious JavaScript to every affected user

The window lasts until the cache entry expires or is manually purged.

Unkeyed Inputs — The Root Cause

The fundamental cause of cache poisoning is **unkeyed inputs**: request components that influence the server's response but are excluded from the cache key. Finding these is the first step of every cache poisoning attack.

Common unkeyed inputs:

**Unkeyed headers:** - `X-Forwarded-Host` — used by many frameworks to construct absolute URLs - `X-Forwarded-Scheme` / `X-Forwarded-Proto` — used for protocol-relative URLs and redirects - `X-Original-URL` / `X-Rewrite-URL` — used by some frameworks to override the request path - `X-Host` — alternative host override header - `X-Forwarded-For` — reflected in some responses - `Accept-Language` — when cached pages don't vary on language

**Unkeyed query parameters:** - UTM parameters (`utm_source`, `utm_campaign`) — often excluded from cache keys but reflected in canonical URLs or og:tags - Tracking parameters that the application processes but the cache strips

**Unkeyed cookies:** - Cookie values reflected in responses when cookies are excluded from the cache key (rare but exists)

Real-World Cache Poisoning CVEs

CVE-2021-22205 — GitLab Remote Code Execution via Cache Poisoning

GitLab's handling of the `X-Forwarded-For` header in combination with ExifTool image processing led to a remote code execution chain. While the primary vector was image parsing, the cache layer's handling contributed to the attack surface. Attackers exploited this in the wild against self-hosted GitLab instances.

CVE-2020-11022 — jQuery XSS via Cache Poisoning

Vulnerabilities in jQuery's HTML parsing, combined with cache poisoning techniques, allowed injection of malicious responses into CDN-cached assets. Given that jQuery was served from CDNs to millions of sites, the blast radius was significant.

Cloudflare CDN Cache Poisoning (2020, PortSwigger Research)

Researchers at PortSwigger demonstrated cache poisoning against Cloudflare's CDN using the `X-Original-URL` and `X-Rewrite-URL` headers. These headers were excluded from Cloudflare's cache key but forwarded to and processed by origin servers. A single malicious request could poison cache entries served to all users of a given CDN edge node.

Practitioner Cache Poisoning on Major Sites

PortSwigger's James Kettle (who coined much of the modern cache poisoning framework) demonstrated cache poisoning against the websites of major airlines, government bodies, and technology companies — including serving malicious JavaScript from their own CDN-cached pages. Most of these were responsibly disclosed, but they illustrated the prevalence of the vulnerability class.

Cache Poisoning Variants

Web Cache Deception

The inverse attack: rather than poisoning a cached response to serve malicious content, the attacker tricks the cache into storing a victim's *private* response and then retrieves it.

``` Attacker sends victim a link: https://bank.example.com/account/profile/nonexistent.css

Cache sees .css extension → assumes static file → caches the response Response is actually the victim's authenticated profile page Attacker fetches the same URL → retrieves cached private data ```

This works when caching rules are based on file extension rather than content type or cache-control headers.

DOM-Based Cache Poisoning

The attacker poisons a cached resource that is then processed by JavaScript in a way that introduces a DOM-based XSS. The cache serves the poisoned resource; the victim's browser processes it and executes attacker-controlled code.

Fat GET Cache Poisoning

Some caches key on the URL but pass a GET request body to the origin. If the origin processes the body and reflects it, an attacker can poison the cache with a body-based payload:

``` GET /page HTTP/1.1 Host: victim.com Content-Type: application/x-www-form-urlencoded Content-Length: 50

injected_param=<script>alert(document.cookie)</script> ```

The cache keys on the URL; the origin reflects the body parameter; the cache stores the poisoned response.

Detecting Cache Poisoning Vulnerabilities

Identify Unkeyed Inputs

Add arbitrary headers and query parameters to requests and observe whether they affect the response without appearing in the cache key. Tools like Param Miner (a Burp Suite extension) automate this by fuzzing common headers and parameters against the target.

``` GET /page HTTP/1.1 Host: victim.com X-Forwarded-Host: test.com ```

If the response contains `test.com` in a script src, link href, or redirect Location, you've found an unkeyed input.

Confirm the Cache Stores the Response

After triggering the unkeyed input reflection, send the same request *without* the header. If you receive the poisoned response, the cache stored it:

``` GET /page HTTP/1.1 Host: victim.com # No X-Forwarded-Host header

Response: <script src="https://test.com/analytics.js"></script> # Poisoned — this came from the cache ```

Use Cache Buster Parameters During Testing

When testing against production systems, append a unique cache buster parameter (`?cb=12345`) so your test requests don't poison real users' cache entries during the discovery phase.

Prevention

**Disable unneeded request header forwarding at the cache layer.** If your CDN or reverse proxy doesn't need to forward `X-Forwarded-Host`, `X-Original-URL`, or similar headers to the origin, strip them at the edge. What the origin never receives can't be reflected.

**Include all response-influencing inputs in the cache key.** If the origin produces different responses based on a header, that header must be part of the cache key, or the response must not be cached at all.

**Use `Vary` headers correctly.** The HTTP `Vary` header tells caches which request headers must match for a cached response to be reused. If the origin varies on `Accept-Language`, `Vary: Accept-Language` must be set.

**Audit your cache configuration against your origin behaviour.** The mismatch between what the cache keys and what the origin uses is the vulnerability. Mapping this gap is a one-time architectural review that eliminates most of the attack surface.

**Set explicit `Cache-Control: no-store` on authenticated and personalised responses.** Private responses must never be cached by shared caches.

Yrzo AI's continuous scanning tests for cache behaviour anomalies, unkeyed header reflection, and cache deception patterns across your web properties.

**[Test your site for cache poisoning → Start free at yrzoai.dev](https://yrzoai.dev)**

One poisoned cache entry. Thousands of affected users. The leverage is enormous — find it before an attacker does.

Find out if your website has these vulnerabilities

Yrzo AI runs 44 automated security checks and delivers a full report in under 20 minutes. Starting from £399.

Scan your website →