Page MenuHomePhabricator

CVE-2025-6594: XSS in Special:ApiSandbox (User interaction required)
Closed, ResolvedPublicSecurity

Description

The Special:ApiSandbox page is vulnerable to XSS when visiting a specific URL. User interaction (pressing a button) is required.

Reproduction Steps

  1. Go to https://en.wikipedia.beta.wmflabs.org/wiki/Special:ApiSandbox#action=parse&text=%3Cpre%3E%3C%2Fpre%20%20%3E%3Cimg%20src%3D%22%23%22%20onerror%3Dalert%281%29%3E%3Cpre%3E%3C%2Fpre%3E&prop=wikitext&format=abc
  2. Click "Make request"

image.png (177×447 px, 9 KB)

Explanation

The following steps happen after pressing the button:

  1. If an invalid format is provided to Special:ApiSandbox (in my example url, abc), the wrappedhtml param is not set to 1 in the API request: https://github.com/wikimedia/mediawiki/blob/97ca97636ca3ea803ea7e8bb59c814988a198fee/resources/src/mediawiki.special.apisandbox/ApiSandbox.js#L307-L315
  2. This param is queried by the API backend and mIsWrappedHtml is set to false: https://github.com/wikimedia/mediawiki/blob/97ca97636ca3ea803ea7e8bb59c814988a198fee/includes/api/ApiFormatBase.php#L64
  3. Since mIsWrappedHtml is set to false, the content type of the response is not set to text/mediawiki-api-prettyprint-wrapped: https://github.com/wikimedia/mediawiki/blob/97ca97636ca3ea803ea7e8bb59c814988a198fee/includes/api/ApiFormatBase.php#L222
  4. Back in the ApiSandbox JS, an if condition in the response handler evaluates to false, since the content type is not set to text/mediawiki-api-prettyprint-wrapped: https://github.com/wikimedia/mediawiki/blob/97ca97636ca3ea803ea7e8bb59c814988a198fee/resources/src/mediawiki.special.apisandbox/ApiSandbox.js#L439
  5. An else if branch then evaluates to true if the entire API response JSON has any <pre> element inside it: https://github.com/wikimedia/mediawiki/blob/97ca97636ca3ea803ea7e8bb59c814988a198fee/resources/src/mediawiki.special.apisandbox/ApiSandbox.js#L457
  6. The pre tag is then inserted into HTML without sanitization: https://github.com/wikimedia/mediawiki/blob/97ca97636ca3ea803ea7e8bb59c814988a198fee/resources/src/mediawiki.special.apisandbox/ApiSandbox.js#L458

In my example, I used the parse API with the wikitext prop to get back exactly what I submitted in the text parameter. However, any API endpoint that can return unsanitized HTML anywhere within its JSON response (e.g. querying the content of revisions or system messages) can be used for this.

Further information

Browser: Firefox 138.0.3 on Fedora 42

Event Timeline

SomeRandomDeveloper updated the task description. (Show Details)

Updated the task since I have found a new payload that only requires pressing the "make request" button.

Really nice find.

Trying to look through the git log for historical context, it looks like this was an accident when converting Extension:ApiSandbox to core. Extension:ApiSandbox would only do this when the content type header had "html" in it. When it got converted to core that part of the if looks to have been dropped.

My guess is that this check was for before the wrappedhtml thing was introduced so presumably it could just be removed.

MSantos subscribed.

We don't have experience or expertise with this code, but moving to the MW-Interfaces-Team board for triage.

@Esanders looks like you worked on this code, can you help out?

I feel like this might just need params.wrappedhtml = 1 set as a default value before the first relevant code block mentioned in the task description: https://github.com/wikimedia/mediawiki/blob/97ca97636ca3ea803ea7e8bb59c814988a198fee/resources/src/mediawiki.special.apisandbox/ApiSandbox.js#L307-L315. Unless that breaks some current assumptions, but it seems like this is an issue of unexpected inputs causing various wrapped html variables to ultimately be set to false.

I feel like this might just need params.wrappedhtml = 1 set as a default value before the first relevant code block mentioned in the task description: https://github.com/wikimedia/mediawiki/blob/97ca97636ca3ea803ea7e8bb59c814988a198fee/resources/src/mediawiki.special.apisandbox/ApiSandbox.js#L307-L315. Unless that breaks some current assumptions, but it seems like this is an issue of unexpected inputs causing various wrapped html variables to ultimately be set to false.

This would probably resolve the bug, but one small mistaken assumption here is that the 'wrappedhtml' can always be used, which is not true – if the formatter is not in the "HTML" mode (https://gerrit.wikimedia.org/g/mediawiki/core/+/c95d10b6dc049f480b8565ab108c4fef5719885a/includes/api/ApiFormatBase.php#380; e.g. it's json instead of jsonfm), the parameter is not valid and will print a warning (https://en.wikipedia.beta.wmflabs.org/w/api.php?action=query&format=json&wrappedhtml). This is mostly harmless, and I'm not sure if this is a reachable code path from the sandbox UI, but it seems a bit confusing to allow it.

Also, API modules may have custom formatters that ignore the 'wrappedhtml' parameter (e.g. ApiFormatFeedWrapper: https://en.wikipedia.beta.wmflabs.org/wiki/Special:ApiSandbox#action=featuredfeed&format=json&feed=featured&formatversion=2), and if any of those can reflect a <pre>…</pre> input, then we would still have a variant of this bug there (I didn't try to review them).

There is definitely some bug here though relating to how invalid values of 'format' are handled.

Trying to look through the git log for historical context, it looks like this was an accident when converting Extension:ApiSandbox to core. Extension:ApiSandbox would only do this when the content type header had "html" in it. When it got converted to core that part of the if looks to have been dropped.

My guess is that this check was for before the wrappedhtml thing was introduced so presumably it could just be removed.

This seems correct to me, and should fix this bug and the hypothetical variant with other formatters. Links for reference:

There is definitely some bug here though relating to how invalid values of 'format' are handled.

Specifically: if you set an invalid 'format' through the URL, ApiSandbox is supposed to display an error when you click "Make request":

image.png (1×1 px, 93 KB)

This would also prevent the vulnerability from happening (although just by a lucky coincidence). But it doesn't, because I broke the validation in rMWc36b4634e813: Use spread operator and variadic params in more places in JS. I thought that surely the worst thing that can happen is the API response displaying the error instead…

Proposed patch, fixing the 'format' validation, and removing the unsafe <pre> detection:

Needs backports to 1.44 and 1.43.


We might want to backport removing the unsafe <pre> detection to older versions too, although they don't include rMWc36b4634e813, so this specific bug is not exploitable.


Proposed patch, fixing the 'format' validation, and removing the unsafe <pre> detection:

CR+2, thanks for working on this! I think this should be good to deploy during next week's security window.

[…]
Proposed patch, fixing the 'format' validation, and removing the unsafe <pre> detection:

I suggest mentioning this task (T395063) in the two inline comments. LGTM, otherwise!

I suggest mentioning this task (T395063) in the two inline comments. LGTM, otherwise!

Done here:

I suggest mentioning this task (T395063) in the two inline comments. LGTM, otherwise!

Done here:

deployed

sbassett added a parent task: Restricted Task.Jun 9 2025, 10:42 PM
sbassett changed the task status from Open to In Progress.Jun 10 2025, 6:59 PM
sbassett triaged this task as Low priority.
sbassett moved this task from Security Patch To Deploy to Watching on the Security-Team board.
Reedy removed a project: Patch-For-Review.
Reedy renamed this task from XSS in Special:ApiSandbox (User interaction required) to CVE-2025-6594: XSS in Special:ApiSandbox (User interaction required).Jun 24 2025, 11:27 PM

Change #1165074 had a related patch set uploaded (by Reedy; author: Bartosz Dziewoński):

[mediawiki/core@REL1_43] SECURITY: apisandbox: Fix reflected XSS when invalid 'format' is provided

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

Change #1165087 had a related patch set uploaded (by Reedy; author: Bartosz Dziewoński):

[mediawiki/core@REL1_39] SECURITY: apisandbox: Fix reflected XSS when invalid 'format' is provided

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

Change #1165100 had a related patch set uploaded (by Reedy; author: Bartosz Dziewoński):

[mediawiki/core@REL1_44] SECURITY: apisandbox: Fix reflected XSS when invalid 'format' is provided

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

Change #1165115 had a related patch set uploaded (by Reedy; author: Bartosz Dziewoński):

[mediawiki/core@master] SECURITY: apisandbox: Fix reflected XSS when invalid 'format' is provided

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

Change #1165087 merged by jenkins-bot:

[mediawiki/core@REL1_39] SECURITY: apisandbox: Fix reflected XSS when invalid 'format' is provided

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

Change #1165134 had a related patch set uploaded (by Reedy; author: Bartosz Dziewoński):

[mediawiki/core@REL1_42] SECURITY: apisandbox: Fix reflected XSS when invalid 'format' is provided

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

Change #1165074 merged by jenkins-bot:

[mediawiki/core@REL1_43] SECURITY: apisandbox: Fix reflected XSS when invalid 'format' is provided

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

Change #1165115 merged by jenkins-bot:

[mediawiki/core@master] SECURITY: apisandbox: Fix reflected XSS when invalid 'format' is provided

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

Change #1165100 merged by jenkins-bot:

[mediawiki/core@REL1_44] SECURITY: apisandbox: Fix reflected XSS when invalid 'format' is provided

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

Change #1165134 merged by jenkins-bot:

[mediawiki/core@REL1_42] SECURITY: apisandbox: Fix reflected XSS when invalid 'format' is provided

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

sbassett changed Author Affiliation from N/A to Wikimedia Communities.Jul 8 2025, 8:33 PM
sbassett removed a project: Patch-For-Review.
sbassett changed the visibility from "Custom Policy" to "Public (No Login Required)".
sbassett changed the edit policy from "Custom Policy" to "All Users".
sbassett changed Risk Rating from N/A to Low.