Web Cache Poisoning
Description
Web Cache Poisoning is an attack where an adversary crafts a request that influences the original server response by abusing how a cache system constructs its cache key.
Most caching layers store data using a key and value system. The key is typically based on the HTTP method, host, path, and headers. If other inputs like headers, query parameters, or cookies affect the response but are not part of the key, the attacker can poison the cache. They can then serve the manipulated response to other users, potentially including malicious content.
Impact
A poisoned cache entry can lead to serious security issues. For example, if an application has a difficult-to-exploit XSS vulnerability, an attacker might still be able to generate the malicious response and poison the cache with it. This allows the attacker to serve the XSS payload to other users, even when direct exploitation is impractical.
Even without XSS, cache poisoning can enable phishing. A seemingly harmless user-provided parameter can be used to inject misleading content, such as one that allows manipulation of URLs in an HTML response. Once cached, this content is served from a trusted domain, making it more convincing to victims.
Attackers can also use cache poisoning to disrupt availability. By caching error responses like HTTP 500, they can cause parts of the site to appear broken for all users.
These attacks can be far-reaching. A single poisoned response may be served to thousands of users before eviction. Because the content comes from a trusted source, users are unlikely to question it. Detection is often delayed, as the origin server may never see the malicious response once cached.
Scenarios
Some prerequisites are required for the practical exploitation of web cache poisoning. Two key conditions must be met:
-
Unkeyed Input Vector: The application must process user input that is ignored by the cache key but still influences the response in a meaningful way, such as modifying HTML or headers. This often includes inputs like query parameters or headers (e.g.,
X-Forwarded-Host
) that affect the output but are not considered by the caching layer. -
Cacheable Response: According to the setup, this typically means that the original request is marked as
public
, has a longmax-age
, or lacks headers that prevent caching. These settings allow a poisoned response to persist and be served to multiple users.
Scenario 1: Poisoning Via Unkeyed Header
A website uses the X-Forwarded-Host
header to generate resource URLs but doesn’t include this header in its cache key:
GET /footer.html HTTP/1.1
Host: example.com
X-Forwarded-Host: evil.com
The server generates HTML with references to resources, e.g., <script src="https://evil.com/script.js"></script>
. All users requesting this HTML page receive the poisoned version loading malicious scripts from the attacker’s domain.
Scenario 2: XSS via Unkeyed Query Parameter
The application is vulnerable to a reflected XSS, but exploitation is difficult because the vulnerable parameter is only reflected if the request includes a valid JWT token. This makes it impractical to exploit through traditional means, such as tricking a victim into visiting a crafted URL.
However, the application also caches pages based only on the URL path, ignoring query parameters and authentication headers. This allows an attacker to poison their own authenticated response:
GET /?callback=alert(document.cookie) HTTP/1.1
Authorization: Bearer <token>
The server reflects the callback parameter into a script tag without sanitization: <script>alert(document.cookie);</script>
Because the cache key does not include the query string or authentication, the poisoned response is stored and later served to all other users who visit the same path, effectively turning a hard-to-reach XSS into a client-side attack delivered from a trusted source.
Prevention
To defend against cache poisoning:
-
Align
Vary
Headers: List all inputs affecting outputs (e.g.,Vary: X-Forwarded-Host, User-Agent
). -
Tighten Cache Settings: Use low
max-age
, enforceprivate, no-store
on user pages, separate static (e.g.,/static/
) from dynamic content, and automate purges/versioned URLs. - Validate and Encode Inputs: Sanitize all user-controlled data before inclusion in responses.
-
Disable Risky Features: Remove support for unnecessary headers (e.g.,
X-Forwarded-Host
) at the edge. - Disable Web Caching for Dynamic Content: Consider completely disabling web caching for dynamically-generated content. Application-level caching might be more suitable in these cases.
Testing
Verify that the application properly configures cache keys to include all request components that influence the response content, and that it properly validates and sanitizes inputs that might be reflected in cached responses.
- OWASP ASVS: V8.2.1, V5.1.1
- OWASP WSTG: Testing for Host Header Injection
References
PortSwigger Research - Practical Web Cache Poisoning MDN: HTTP Caching RFC 9111: HTTP Caching