Page MenuHomePhabricator

API incorrectly marks formatted html output as public cacheable even though it contains csrf token
Closed, ResolvedPublicSecurity

Description

Note: not exploitable in WMF's config, see bottom for details why.

Consider a URL like https://en.wikipedia.org/w/api.php?action=query&uselang=en&maxage=6000&smaxage=6000&uselang=en

If you are logged in, the response body will contain your edit token (Inside a blob of JS on the page).

It will also contain the header cache-control: s-maxage=6000, max-age=6000, public even if you are logged in

This is a signal to proxies that this response is safe to cache in a shared cache.

We should not be sending public cache headers for any request with private user data in it.

In theory, the vary: cookie header should stop compliant proxies from doing anything bad, but often people disable varrying on all cookies.

This is not exploitable on WMF as far as i can tell because CDN servers refuse to cache anything where the request has a cookie with session or token in the name. I believe this might be exploitable in other configurations of MediaWiki with different varnish configs. You can get around that using centralauthtoken parameter, however this puts RL into safe mode, and i couldn't find anything useful to do with a cached safemode request where the URL is unguessable (POST of course, also causes it to become uncacheable).

Anyways, i don't think this is exploitable, but definitely not best practise. Anything using a "formatted" output should be marked 'anon-public-user-private' similar to how uselang=user is handled.

Event Timeline

Since this is not exploitable on WMF servers, I'm marking this as a low risk ticket. However, it would still be valuable for someone from the Mediawiki Engineering team to check this out since CSRF tokens shouldn't be viewable at all, anywhere.

Mstyles changed Risk Rating from N/A to Low.Jan 8 2024, 11:09 PM
Mstyles removed a project: Security-Team.
daniel subscribed.

I just confirmed that the response from the URL given in the task description now contains cache-control: private, must-revalidate, max-age=6000. Resolving.

sbassett changed the visibility from "Custom Policy" to "Public (No Login Required)".
sbassett changed the edit policy from "Custom Policy" to "All Users".

Change 992771 had a related patch set uploaded (by Zabe; author: Daniel Kinzler):

[mediawiki/core@REL1_41] API: mark HTML output as non-cacheable

https://gerrit.wikimedia.org/r/992771

Change 992772 had a related patch set uploaded (by Zabe; author: Daniel Kinzler):

[mediawiki/core@REL1_40] API: mark HTML output as non-cacheable

https://gerrit.wikimedia.org/r/992772

Change 992773 had a related patch set uploaded (by Zabe; author: Daniel Kinzler):

[mediawiki/core@REL1_39] API: mark HTML output as non-cacheable

https://gerrit.wikimedia.org/r/992773

Change 992773 merged by jenkins-bot:

[mediawiki/core@REL1_39] API: mark HTML output as non-cacheable

https://gerrit.wikimedia.org/r/992773

Change 992772 merged by jenkins-bot:

[mediawiki/core@REL1_40] API: mark HTML output as non-cacheable

https://gerrit.wikimedia.org/r/992772

Change 992771 merged by jenkins-bot:

[mediawiki/core@REL1_41] API: mark HTML output as non-cacheable

https://gerrit.wikimedia.org/r/992771