Web cache poisoning is an attack where a malicious response gets stored in a shared cache or CDN and then served to every subsequent visitor who requests that same URL. The attacker doesn't need to compromise your server — they only need to find an "unkeyed" input, a header or parameter the cache ignores when deciding whether two requests are the same, but that your application still uses to build the response. Send one poisoned request, and the cache does the rest: it stores the malicious page and hands it to hundreds or thousands of legitimate users automatically. This makes cache poisoning one of the highest-leverage, most under-tested attack classes in modern web infrastructure — a single request can achieve what would otherwise take a full campaign of individual attacks.
What Makes Cache Poisoning Different From Standard XSS
Most Indian dev and security teams test for reflected and stored XSS, SQL injection, and IDOR — the OWASP staples. Cache poisoning sits in a blind spot because it isn't a flaw in application logic alone; it's a mismatch between how your cache key is computed and how your origin server actually behaves. A header that has zero effect on a normal request-response cycle can become a weapon the moment it influences the response body or headers while being excluded from the cache key.
Understanding the Cache Key
A cache key is the set of request attributes — typically method, host, path, and a subset of query parameters and headers — that a caching layer uses to decide whether an incoming request matches a stored response. Anything not included in the key is "unkeyed": the cache treats requests differing only in that attribute as identical, even though the origin server may generate a different response for each.
Common unkeyed inputs that attackers abuse:
X-Forwarded-Host— often trusted by frameworks to build absolute URLs, canonical links, or password-reset links, but rarely included in the cache key.X-Forwarded-Scheme/X-Forwarded-Proto— can flip protocol-relative resource references.X-Original-URL/X-Rewrite-URL— used by some frameworks and reverse proxies for internal routing.Accept-Language— when reflected into an error page or an unhandled-locale redirect.User-Agentfragments reflected into device-detection redirects or mobile-optimised pages.
Discovery Methodology for Indian Dev Teams
A structured methodology finds these bugs faster than random header fuzzing. The approach below mirrors what a VAPT engagement should cover for any application sitting behind a CDN, reverse proxy, or load balancer — Cloudflare, Akamai, Fastly, AWS CloudFront, or an in-house Nginx/Varnish layer.
- Map the caching layer. Check
Cache-Control,Age,X-Cache,CF-Cache-Status, orX-Vercel-Cacheresponse headers to confirm a cache is in play and observe hit/miss behaviour. - Enumerate candidate unkeyed headers. Send a request with a distinctive header value (
X-Forwarded-Host: probe-test.example) and check whether it's reflected anywhere in the response body, a redirectLocation, or a resource URL. - Confirm cacheability. Repeat the exact same request without the header. If the poisoned response is served on the follow-up request, the header is unkeyed and the response is cacheable — you have a working poisoning primitive.
- Assess impact. Determine whether the reflected value lands somewhere exploitable: a script src, an inline attribute, a redirect target, or a canonical/OG meta tag used by search engines and social crawlers.
- Check cache scope and duration. A poisoned entry on a shared, edge-wide cache key affects every visitor globally until TTL expiry or purge; a poisoned entry scoped to a single edge node or a short TTL has a narrower blast radius, but still matters.
- Document and remediate before disclosure. Cache poisoning findings should include the exact unkeyed input, a reproducible request, the affected URL pattern, and the observed cache behaviour — this is what turns a theoretical bug into an actionable fix for engineering.
Know your vulnerabilities before attackers do
Run a free VAPT scan — takes 5 minutes, no signup required.
Book Your Free ScanReal-World Impact: Why This Scales So Fast
A cache poisoning bug's severity comes from its multiplier effect. Where a stored XSS bug usually requires the attacker to plant a payload in a database record a victim will view, a poisoned cache entry sits directly on a URL that legitimate users, search crawlers, and even monitoring bots all request identically. Documented public research (PortSwigger's web security research team, which popularised much of the modern methodology) has shown poisoning chains that achieve:
- Reflected XSS turned persistent — a payload that would normally require social engineering to deliver instead gets served automatically to every visitor of a popular page.
- Open redirect at scale — a poisoned
Locationheader or canonical URL silently redirects an entire user base to a phishing domain. - Cache deception combined with poisoning — sensitive personalised content gets cached and served to the wrong users when session-specific data leaks into a cacheable response.
- Denial of service via error caching — an attacker triggers and caches a 4xx/5xx error page for a high-traffic route, effectively taking it offline for all subsequent visitors until the cache clears.
CDN Context: Every Layer Computes Keys Differently
The risk compounds because most production stacks run multiple caching layers stacked on top of each other — a CDN edge cache, a reverse proxy cache, and sometimes application-level fragment caching — and each layer can compute its cache key differently. A header excluded from the CDN's key but included at the origin (or vice versa) creates exactly the mismatch attackers look for.
| Layer | Typical cache key scope | Common unkeyed risk |
|---|---|---|
| CDN edge (Cloudflare, Akamai, CloudFront) | Host + path, sometimes query string | Custom headers like X-Forwarded-Host often excluded by default |
| Reverse proxy (Nginx, Varnish, HAProxy) | Configurable via proxy_cache_key / VCL | Misconfigured X-Original-URL or forwarded headers |
| Framework-level cache | Route + selected params | Accept-Language, device-detection headers |
| Browser cache | Full request including cookies | Lower shared-blast-radius, but still a vector for single-user persistence |
Defence: Building Cache Keys That Can't Be Poisoned
The fix is rarely "disable caching" — that destroys performance and cost benefits. The fix is disciplined cache-key design and header hygiene.
- Key everything the origin uses. If
X-Forwarded-Host(or any header) influences the response, it must be part of the cache key, or it must be stripped/normalised before it ever reaches the origin. - Strip untrusted forwarded headers at the edge. Don't let client-supplied
X-Forwarded-*headers pass through unvalidated — set them explicitly at your trusted proxy layer, and reject or overwrite any client-supplied duplicates. - Use explicit
Varyheaders correctly. If a response genuinely differs byAccept-LanguageorAccept-Encoding, declare it — but audit that the CDN actually honoursVaryfor those values, since some default CDN configurations ignore or cap it. - Set conservative
Cache-Controldirectives on dynamic or personalised routes.private, no-storefor anything session- or user-specific; short, explicitmax-ageands-maxagefor genuinely shared content. - Segregate cacheable and non-cacheable routes architecturally. Static assets, marketing pages, and public API responses can be aggressively cached; anything reflecting request-derived data (redirects, canonical URLs, locale pages) needs a tighter, audited key.
- Test cache behaviour as part of every release, not just once. A cache configuration that was safe six months ago can silently regress when a new header gets added to a request-handling function.
Independent testing catches this class of issue reliably because it requires deliberately probing headers that internal QA and functional testing never touch — nobody manually tests X-Forwarded-Host variations during a feature release. This is exactly the kind of advanced, infrastructure-aware testing a proper free VAPT scan is designed to surface, alongside the broader application and network layer coverage. For organisations also mapping this into a data-protection posture — since a poisoned cache serving malicious content to Indian users touches DPDP Act breach-notification obligations — see our DPDP compliance guide for how infrastructure-level findings feed into your incident-response plan. Bachao.AI's VAPT methodology specifically includes unkeyed-input and cache-layer testing as part of its automated and manual review passes, and where CERT-In-empanelled validation is required for regulatory purposes, this is delivered with a CERT-In empanelled partner.
A Practical Audit Checklist
Before your next release, or as a standing quarterly check, run through this sequence against every route sitting behind a shared cache:
- List every header your application reads from the incoming request (not just the ones you intentionally documented).
- Cross-reference that list against your CDN/proxy's configured cache key.
- Flag any header that's read by the origin but absent from the key — treat each as an untested poisoning candidate.
- Confirm
Cache-Control: privateorno-storeis set on every route serving session-specific or personalised data. - Re-run the check after any change to routing, middleware, or reverse-proxy configuration — this is a regression class, not a one-time fix.
Dhisattva AI Pvt Ltd builds this testing methodology around exactly these infrastructure-layer gaps — the vulnerabilities that sit between application code and the network path a request travels, which conventional scanners and manual QA both tend to miss. As more Indian SMBs move behind CDNs and reverse proxies for performance and DDoS protection, cache poisoning moves from "advanced attacker technique" to "standard item on the pentest scope."
Frequently Asked Questions
What is web cache poisoning in simple terms?
How is cache poisoning different from a normal XSS attack?
What is an unkeyed input in cache poisoning terms?
X-Forwarded-Host — that influences how the origin server builds its response, but is not included in the cache key the caching layer uses to decide whether two requests are "the same." This mismatch is the root cause of most cache poisoning bugs.