Cache poisoning turns your performance optimization into a weapon. The same caches that speed up your application for legitimate users can be tricked into serving malicious content to every visitor. One carefully crafted request poisons the cache, and every subsequent user receives the attacker's payload until the cache entry expires.
This is not theoretical. Web cache poisoning has been demonstrated against major CDN providers, popular web frameworks, and production applications serving millions of users. The attack surface has grown as caching has become more sophisticated and more ubiquitous.
How Web Cache Poisoning Works
Web caches store responses and serve them to subsequent requesters who ask for the same resource. The cache uses a "cache key" to determine which requests are "the same" -- typically the URL path, query string, and sometimes the Host header.
The critical insight is that HTTP requests contain many components that are not part of the cache key but still influence the server's response. These are called "unkeyed inputs." If an attacker can find an unkeyed input that changes the response in a useful way, they can inject their payload through that input. The poisoned response is cached and served to other users whose requests match the cache key.
The attack flow:
- Attacker identifies an unkeyed input that is reflected in the response (e.g., the
X-Forwarded-Hostheader) - Attacker sends a request with a malicious value in the unkeyed input
- The server generates a response that includes the malicious value
- The cache stores the response, keyed only on the URL
- Subsequent users requesting the same URL receive the cached poisoned response
Unkeyed Header Attacks
The most common cache poisoning vector is unkeyed HTTP headers. Applications use headers like X-Forwarded-Host, X-Forwarded-Proto, X-Original-URL, and custom headers to generate URLs, set metadata, or control behavior. Caches typically do not include these headers in the cache key.
X-Forwarded-Host poisoning. Many frameworks use X-Forwarded-Host to generate absolute URLs. If the application generates a script tag like <script src="https://[X-Forwarded-Host]/static/app.js">, the attacker sets X-Forwarded-Host: evil.com, and the cached page loads JavaScript from evil.com for all users.
X-Forwarded-Proto poisoning. Applications that generate URLs based on the protocol header can be tricked into generating HTTP URLs when the cache expects HTTPS, causing mixed-content warnings or enabling downgrade attacks.
Vary header abuse. The Vary header tells caches which request headers to include in the cache key. If Vary is missing or incomplete, the cache may serve a response generated for one User-Agent to users with a different User-Agent.
Prevention:
- Audit all request headers that influence your application's response
- Include all influential headers in the cache key (via the
Varyheader) - Strip unexpected headers at the edge (CDN or reverse proxy)
- Avoid reflecting request headers in responses
Parameter-Based Poisoning
Cache keys often include the full query string, but some caches normalize or partially parse query parameters. This creates opportunities:
Unkeyed parameters. Some CDNs strip marketing parameters (utm_source, utm_campaign) from the cache key while still passing them to the origin. If these parameters are reflected in the page, they can be used for poisoning.
Parameter pollution. Sending duplicate parameters (?callback=safe&callback=evil) can result in different values being used for the cache key and the response body, depending on how the cache and application parse duplicate parameters.
Path normalization inconsistencies. The cache normalizes /api/../admin to /admin, but the origin server processes the unnormalized path differently. The attacker gets a response for /api/../admin cached under the key for /admin.
Prevention:
- Ensure cache key includes the full, unnormalized query string
- Do not reflect query parameters in responses without validation
- Normalize paths consistently between the cache and origin
- Use the same URL parser at both layers
Denial of Service Through Cache Poisoning
Cache poisoning is not limited to content injection. An attacker can poison the cache with error responses:
Large header poisoning. Send a request with headers large enough to trigger a 400 or 431 error from the origin. If the cache stores the error response, all users receive the error page.
Invalid encoding. Send a request with invalid character encoding in a header that is not part of the cache key. The origin returns a 500 error, and the cache stores it.
Timeout poisoning. If the cache stores timeout responses (502, 504), the attacker can trigger timeouts on the origin and poison the cache with error pages.
Prevention:
- Configure caches to not store 4xx and 5xx responses
- Set appropriate
Cache-Controlheaders on error responses (no-store,no-cache) - Implement stale-while-revalidate to serve stale content instead of errors
- Monitor cache hit ratios for sudden drops (indicating poisoning)
DNS Cache Poisoning
DNS cache poisoning is a distinct but related attack. The attacker injects forged DNS records into a resolver's cache, redirecting traffic intended for a legitimate domain to an attacker-controlled server.
Classic attack (Kaminsky). The attacker floods the recursive resolver with forged responses to a DNS query, hoping one arrives before the legitimate response and matches the transaction ID. Modern resolvers use source port randomization to make this harder.
BGP hijacking for DNS. By hijacking the IP prefix of a DNS server, the attacker can respond to DNS queries authoritatively. This bypasses transaction ID randomization.
Prevention:
- Deploy DNSSEC to cryptographically validate DNS responses
- Use DNS-over-HTTPS (DoH) or DNS-over-TLS (DoT) to prevent interception
- Minimize DNS cache TTLs for sensitive domains
- Monitor for unauthorized DNS changes
CDN-Specific Considerations
CDNs add complexity to cache poisoning defense because they operate at massive scale and cache aggressively:
Multi-layer caching. CDNs often have multiple cache layers (edge, mid-tier, origin shield). Poisoning at the edge affects one region; poisoning at the origin shield affects all regions.
Cache warming. Some CDNs pre-populate caches by crawling the origin. If the origin is compromised, the CDN propagates the poisoned content globally.
Cache invalidation delays. Even after detecting poisoning, purging the CDN cache takes time. During this window, the poisoned content continues to be served.
Prevention:
- Use the CDN's cache key configuration to include all relevant headers
- Implement origin response validation at the CDN layer
- Set up cache purge automation triggered by monitoring alerts
- Use signed responses or content integrity checks between origin and CDN
Detecting Cache Poisoning
Detection relies on monitoring the gap between what the origin serves and what users receive:
- Content integrity monitoring. Periodically fetch pages through the cache and compare against the origin response. Hash comparison detects modifications.
- Cache hit ratio monitoring. Sudden changes in cache hit ratios can indicate poisoning attempts.
- User reports. Users seeing unexpected content, broken pages, or security warnings may be seeing poisoned cache entries.
- Response header analysis. Compare
Age,X-Cache, andViaheaders between normal and suspicious responses.
How Safeguard.sh Helps
Safeguard.sh helps defend against cache poisoning by monitoring your web framework and reverse proxy dependencies for vulnerabilities that enable poisoning attacks. When a CVE is published for a web framework's header handling or a reverse proxy's caching behavior, Safeguard.sh alerts your team. By maintaining a comprehensive SBOM that includes your CDN integration libraries and web server components, Safeguard.sh ensures that cache-layer vulnerabilities are identified and addressed before attackers can leverage them to serve malicious content to your users.