Recommended
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
Using these examples: they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.
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
- Make password resets safe in Security testing
- Keep private pages out of shared caches in Security testing
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/adversarial-testing/requests-and-files/web-cache-poisoning">Agent Wranglers security testing playbook</a>. In code you write, add a comment next to it with the same link.