Fetching latest headlines…

Dev

What Is Web Cache Poisoning? How Can a Cached Response Become a Security Problem?

Dev.toUnited States · NORTH AMERICA

Here's something that should be completely unremarkable. A user requests /homepage. The origin server generates a response. A cache stores it. The next user requests /homepage. The cache returns the s...

0 views0 likes0 comments

Here's something that should be completely unremarkable.

A user requests /homepage. The origin server generates a response. A cache stores it. The next user requests /homepage. The cache returns the stored response without touching the origin.

That's caching working as intended. It reduces latency, saves origin load, and serves popular content efficiently. Nothing suspicious.

Now ask a more precise question: how does the cache decide that two requests are the same?

That question is where web cache poisoning begins.

What the Cache Is Actually Doing

The request path for a typical web application doesn't go directly from browser to origin. It passes through intermediaries:

Browser
   ↓
CDN / reverse proxy / cache
   ↓
Origin server

When a request arrives at the cache, it needs to decide: do I have a stored response for this? To answer that, it computes something called a cache key: a representation of the request that determines which stored responses are candidates for reuse.

A simple cache key might be just the URL path:

GET /profile
Cache key: /profile

Cache miss → forward to origin → store response → return response.

Next request for /profile → cache hit → return stored response.

That's fine as far as it goes. But a URL path is a simplified view of the request. The full HTTP request contains a lot more: headers, query parameters, cookies, content negotiation fields, and other metadata. Some of these can influence what the origin server returns. Whether they're included in the cache key is a separate question.

The Cache Key vs the Origin's Interpretation

Here's the core issue.

The cache uses its cache key to decide equivalence. The origin server uses the full request to generate a response. These two things don't have to agree.

If the origin uses some part of the request that the cache doesn't include in the key, two requests that the cache considers identical can produce different responses from the origin.

Consider a request that includes a header the origin uses to customize the response, but the cache doesn't factor that header into its key:

Request A:
GET /page
X-Custom-Header: value-a

Request B:
GET /page
X-Custom-Header: value-b

Cache key for both: /page

As far as the cache is concerned, these are the same request. As far as the origin is concerned, they're different.

The origin might include the header's value in the response. The cache might store that response under the key /page. The next request for /page gets that stored response, even if it originated from a completely different input.

This is the mismatch that makes cache poisoning possible.

Walking Through the Lifecycle

Let's be precise about what happens:

Attacker requests /page with attacker-controlled input
      ↓
Cache computes key: /page (input not in key)
      ↓
Cache MISS
      ↓
Request forwarded to origin
      ↓
Origin processes request including attacker-controlled input
      ↓
Origin generates response influenced by that input
      ↓
Cache stores response under key: /page
      ↓
Victim requests /page
      ↓
Cache computes key: /page
      ↓
Cache HIT
      ↓
Victim receives stored response

The cache isn't malfunctioning. It's doing exactly what it was configured to do: storing responses and serving them for matching keys. The problem is that the response it stored was generated under conditions the cache didn't capture in its key.

The attacker didn't break into the cache. They caused a specific response to be cached, then let the cache do its normal job.

What Gets Poisoned and What Doesn't

This distinction matters: the cache itself isn't compromised. There's no unauthorized write to some cache database. The cache accepted a legitimate HTTP response from the origin and stored it. That's normal behavior.

The security failure is the mismatch between what the cache considers equivalent and what the origin considers significant.

If the origin's response contains something derived from attacker-controlled input, and the cache stores that response and serves it to subsequent visitors, those visitors receive a response shaped by input they never supplied. What that means depends on what the response contains. If the attacker-controlled input ends up reflected in a response header or body in a way that affects clients, the consequences can range from unexpected behavior to more serious client-side impact.

The cache is faithfully reusing a response that was generated under different conditions than the later requests assumed.

Which Request Components Can Matter

Any part of the request that the origin uses to generate a response is potentially relevant. Whether that part is in the cache key determines whether the cache distinguishes between requests that differ in that part.

Things that might influence origin responses:

  • URL path and query parameters
  • The Host header, which can affect routing and response generation
  • Custom headers that servers use for feature flags, A/B testing, or locale
  • Content negotiation headers like Accept-Language or Accept-Encoding
  • Request metadata passed between intermediaries The critical question for each: does the origin use this to generate the response, and does the cache include it in the cache key?

If the origin uses it and the cache doesn't key on it, you have an unkeyed input that can influence the response. That's the condition for potential poisoning.

Multiple Layers Make This More Complex

Modern applications often have multiple caching layers between the browser and the origin:

Browser
   ↓
CDN
   ↓
Reverse proxy
   ↓
Application server

Each layer has its own view of the request and its own caching configuration. A CDN might key on the URL and a handful of standard headers. A reverse proxy in front of the application might key differently. The application itself might generate responses influenced by headers that neither caching layer keys on.

This means the security assumptions made at one layer might not match the behavior at another. A developer who knows exactly how the application generates responses may not know what the CDN includes in its cache key. That gap is where poisoning conditions can appear.

Cache configuration is part of the application's security boundary, not just an infrastructure detail.

Normal Caching vs Poisoning

The conceptual difference:

Normal caching:
Two requests mean the same thing to both the cache and the origin → the cache reuses the response correctly.

Cache poisoning:
Two requests mean the same thing to the cache but different things to the origin → the cache reuses a response that was generated under conditions different from what the later request would produce.

The vulnerability isn't "caching is bad." Caching is necessary and correct. The vulnerability is incorrect equivalence: the cache treating two requests as interchangeable when they produce meaningfully different responses.

Defenses

The defenses follow directly from the mechanism.

Make cache keys reflect the inputs that matter. If the origin uses a request component to generate the response, that component should be part of the cache key, or the response shouldn't be cached in contexts where that component varies.

Treat unkeyed inputs carefully. If a CDN or proxy passes headers to the origin without including them in the cache key, those headers are unkeyed inputs. Review whether the origin uses them in ways that affect the response.

Don't cache responses that contain user-specific content unless that's explicit and intentional. Personalized responses shouldn't end up in shared caches.

Configure caching deliberately. Default caching behavior may not match the security assumptions of the application. Cache key configuration and cacheable response criteria should be explicit decisions, not defaults inherited from intermediary software.

Test with varied inputs. Cache behavior should be tested across different request variations, not just for functional correctness but to understand when different inputs produce different cache entries.

A cache doesn't understand your application's intent. It knows which requests it considers equivalent and returns the same stored response for all of them.

If the cache and the origin disagree about what makes two requests different, a response generated for one request becomes the response served for another. The cache is working exactly as designed. The problem is that "exactly as designed" and "what the application expected" turned out to be different things.

That's the gap web cache poisoning lives in.

Comments (0)

Sign in to join the discussion

Be the first to comment!