APS-47Recommended
Keep unkeyed headers from changing cached pages
Let a cached reply depend only on the parts of the request in the cache key. Never build a cached page from a header the cache ignores.
How to do it
Web cache poisoning sends one request with a header the cache ignores but the app reads, such as X-Forwarded-Host or X-Original-URL. The cache then serves that poisoned reply to every visitor. Build links from a configured site address, never from request headers, as make password resets safe also needs. Accept forwarding headers only from your own proxy, and strip them at the edge. When a reply must change with a header, name it in Vary or in the CDN's cache key. Private pages follow keep private pages out of shared caches.
Examples
JavaScript example
const SITE_URL = new URL(process.env.SITE_URL);
// Bad: new URL(`/shop/${slug}`, `https://${req.get('x-forwarded-host')}`)
const canonical = new URL(`/shop/${slug}`, SITE_URL);
Python example
SITE_URL = os.environ["SITE_URL"]
# Bad: f"https://{request.headers['X-Forwarded-Host']}/shop/{slug}"
canonical = urljoin(SITE_URL, f"/shop/{slug}")
How to check
On the test copy, request a cached page with X-Forwarded-Host: evil.example.net and a fresh query string, such as ?cb=PENTEST-31. Then request the same address without the header. Neither reply contains evil.example.net.
Sources
Related rules
- ACC-69 Make sign-in and reset links single-use and short-lived in Accounts and identity
- APS-46 Keep private pages out of shared caches in Application security
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/application-security/requests-files-and-caches/web-cache-poisoning">Agent Wranglers application security playbook</a>. In code you write, add a comment next to it with the same link.