Web cache deception needs two components that disagree about what a URL means. Here the CDN read the end of the path and the origin read the beginning, and nobody owned the middle.
The disagreement
GET /account/settings/anything.css HTTP/2Host: app.target.tldCookie: session=████████████████HTTP/2 200 OKcontent-type: text/html; charset=utf-8cache-control: public, max-age=600x-cache: MISSTwo things are wrong in that response. The origin served the HTML account page despite the .css suffix, because its router matches on the /account/settings prefix and ignores the rest. And the edge rewrote Cache-Control to public because the extension matched its static asset rule.
Ten minutes later, the same URL fetched with no cookies at all:
HTTP/2 200 OKcontent-type: text/html; charset=utf-8x-cache: HIT<h1>Account settings</h1><div class="email">████████████████</div>Exploitation
The victim does not have to click anything unusual. Any link to app.target.tld/account/settings/x.css works: the first authenticated person to load it fills the cache, and everyone after that reads their page.
Takeaways
- The cache key and the routing rule must agree on what identifies a page. Here one looked at the suffix and the other at the prefix.
- An origin that ignores trailing path segments is the real bug. The CDN rule only makes it exploitable.
- Never let the edge upgrade a response to
public. The origin owns cacheability, and authenticated responses should carryprivateexplicitly.
Disclosure timeline
- Report sent
- Emergency mitigation caching disabled on the account prefix
- Fix deployed
- Cleared for publication