The account page was private.
It needed a session.
It showed the name,
the address,
the last four digits of the card,
and an API key for the integration.
It sat behind a CDN,
like the rest of the site,
and the CDN had one simple rule
to keep the bill down.
Anything ending in .css, .js or .png,
cache it for a day.
Then a support ticket.
A customer's address
was showing on a stranger's screen.
The stranger had been sent a link.
/account/settings/logo.css
Here is the mechanism.
The attacker sent that link
to a signed in customer
and waited for one click.
Your framework
was forgiving about paths.
It matched /account/settings,
ignored the bit on the end,
and served the full private page,
personal details and all,
with a status of two hundred.
The CDN saw a URL
ending in .css
and a successful response.
Its rule said static file.
So it stored the customer's account page
under that URL,
for everyone.
The attacker opened the same link,
with no session at all,
and the CDN handed it over
from its cache.
Your server never saw that request.
Your logs had nothing to show.
This is web cache deception,
and it was shown against PayPal
back in 2017.
Two systems each made a reasonable choice.
The framework said
close enough is fine.
The cache said
the extension tells me what this is.
Together they published a private page.
So make the origin say what it means.
Every response that depends on a session
sends Cache-Control: no-store,
and the CDN is told
to respect what the origin says,
not what the URL looks like.
Make routing strict.
A path you did not define
is a four hundred and four,
not the nearest page that fits.
Turn on the CDN's check
that the content type matches the extension,
if it has one,
so HTML pretending to be a stylesheet
is never stored.
And test it the way the attacker would.
Sign in,
add /anything.css to a private URL,
then fetch it again
from a clean browser.
A cache does not know
who a page was for.
Unless you tell it on every response,
it assumes the answer
was for everyone.
– Serguey Asael Shinder
Top comments (0)