Page MenuHomePhabricator

CVE-2025-67481: mw.message(…).parse() doesn't output safe HTML, but it's being used as if it does
Closed, ResolvedPublicSecurity

Description

An example:

  1. With a plain adminstrator account (no editsitejs/editsitecss rights), replace your MediaWiki:Summary-preview with this content:
[javascript:alert("XSS"); click here]
  1. Check "Show previews without reloading the page" in your preferences
  2. In the 2010 editor, edit any page.
  3. With a non-blank summary, click "Preview"
  4. Click on the "click here" below the edit summary box

What's going on here:

> mw.messages.set('m', '[javascript:alert("XSS"); click here]'); mw.message('m').parse();
"<a href=\"javascript:alert(&quot;XSS&quot;);\">click here</a>"

Another example: (not an XSS, but privacy loss)

> mw.messages.set('m', '<span style="background:url(http://example.com/tracker.jpg)">x</span>'); mw.message('m').parse();
"<span style=\"background:url(http://example.com/tracker.jpg)\">x</span>"

So it looks like parse()isn't really even trying to produce secure output, and, indeed, I can find nothing at Manual:Messages_API which claims that the output of parse() is safe.

But people have been using it as just that anyway. So effectively there are many raw-ish HTML messages that aren't marked as such.

So either jqueryMsg needs to implement a full sanitizer, or a whole lot of messages, across many extensions, need to be marked as raw HTML (and the manual updated).

There's also something funky going on with how parameters are processed:

mw.messages.set('m', '[[x]] $1'); mw.message('m', '<span onmouseover="alert(\'XSS\')">x</span>').parse()
"<a title=\"x\" href=\"/wiki/x\">x</a> &lt;span onmouseover=\"alert('XSS')\"&gt;x&lt;/span&gt;"

mw.messages.set('m', 'x $1'); mw.message('m', '<span onmouseover="alert(\'XSS\')">x</span>').parse()
"x <span onmouseover=\"alert('XSS')\">x</span>"

So it looks like sometimes parameters aren't escaped properly. This one is certainly more scary-looking, and might be exploitable by any unprivileged user, but I can't find a working example.

Event Timeline

sbassett moved this task from Incoming to Back Orders on the Security-Team board.

The issue with 'javascript:' URLs is already known: T86738: mw.message.parse() accepts javascript: protocol in wikilinks (CVE-2020-25814)

I wrote a patch for that a few years ago, I thought it was fixed…

I think all of the cases listed here are fixed by T86738 and T115888 (September 2020, released in T256335: Tracking bug for MediaWiki 1.31.9/1.34.3/1.35.0).

Testing locally with the examples given, they all seem to be escaped or not parsed:

> mw.messages.set('m', '[javascript:alert("XSS"); click here]'); mw.message('m').parse();
'[javascript:alert("XSS"); click here]'

> mw.messages.set('m', '[[x]] $1'); mw.message('m', '<span onmouseover="alert(\'XSS\')">x</span>').parse()
`<a title="x" href="/wiki/x">x</a> &lt;span onmouseover="alert('XSS')"&gt;x&lt;/span&gt;`

> mw.messages.set('m', 'x $1'); mw.message('m', '<span onmouseover="alert(\'XSS\')">x</span>').parse()
'x &lt;span onmouseover=&quot;alert(&#039;XSS&#039;)&quot;&gt;x&lt;/span&gt;'

> mw.messages.set('m', '<span style="background:url(http://example.com/tracker.jpg)">x</span>'); mw.message('m').parse();
'&lt;span style="background:url(http://example.com/tracker.jpg)"&gt;x&lt;/span&gt;'

Thanks, @matmarex. If @suffusion_of_yellow could also confirm, or at least additionally confirm the above tests from @matmarex, we can resolve and make this task public.

I can still load external images, using CSS escapes:

> mw.messages.set('m', '<span style="background:\\75rl(http://example.com/tracker.jpg)">x</span>'); mw.message('m').parse();
"<span style=\"background:\\75rl(http://example.com/tracker.jpg)\">x</span>"

Before I try other tricks, has anyone built any testcases for the other CSS sanitizers (PHP parser, Parsoid, TemplateStyles, etc.)?

I can still load external images, using CSS escapes:

Ok. Not sure how close to a 1:1 expectation is reasonable for mw.parse() and the output of parsoid / php parser. I honestly have no idea if that's something the Parsoid team cares about or if any of that is on their current team roadmap.

Before I try other tricks, has anyone built any testcases for the other CSS sanitizers (PHP parser, Parsoid, TemplateStyles, etc.)?

Possibly? The parsoid / parser php code has several thousand tests controlled via bin/parserTests.php. The Parsoid team might be able to provide some help in locating any potentially helpful, CSS-related tests.

This is not the legacy PHP wikitext parser, or the new Parsoid wikitext parser, but instead a completely separate parser written in client-side JavaScript. There's no chance that the client-side parser is going to pass all of the proper PHP parser tests. And the issue here is that client-side JS can't really access all of the HTML and CSS sanitizer mechanisms implemented server-side in PHP.

The real solution is probably to replace the client-side parser with API requests to the server, or pre-bundle the "parsed" results of messages in ResourceLoader or something like that. Having an independent partial implementation of wikitext in client-side JS isn't really sustainable.

That said, trying to extract tests from TemplateStyles, wikimedia/css-sanitizer, etc to improve the coverage of the client-side JS wikitext parser is certainly a good idea.

The real solution is probably to replace the client-side parser with API requests to the server, or pre-bundle the "parsed" results of messages in ResourceLoader or something like that. Having an independent partial implementation of wikitext in client-side JS isn't really sustainable.

That said, trying to extract tests from TemplateStyles, wikimedia/css-sanitizer, etc to improve the coverage of the client-side JS wikitext parser is certainly a good idea.

Would it be easier to deprecate mw.parse instead of trying to improve it, but not really being able to get it to function similarly to Parsoid/PHP Parser? And then rely upon mw.html.escape, Message.escaped, et al as the preferred means of client-side sanitization? I'd agree that mw.parse is confusingly-named and likely gives false assurances to developers.

Having an independent partial implementation of wikitext in client-side JS isn't really sustainable.

It is a very limited subset of wikitext (as documented here). I think it's feasible to continue to maintain it. We can be very blunt in applying additional limitations, e.g. we could forbid the style attribute entirely instead of trying to sanitize it (which would resolve the last unresolved known issue noted in T251032#7483536).

The real solution is probably to replace the client-side parser with API requests to the server, or pre-bundle the "parsed" results of messages in ResourceLoader or something like that.

This can be done sometimes (e.g. https://codesearch.wmcloud.org/search/?q=parsedMessages&i=fosho), and is often a good idea, but many messages have parameters, which can't be passed when the message is used in this way.

Would it be easier to deprecate mw.parse instead of trying to improve it, but not really being able to get it to function similarly to Parsoid/PHP Parser? And then rely upon mw.html.escape, Message.escaped, et al as the preferred means of client-side sanitization? I'd agree that mw.parse is confusingly-named and likely gives false assurances to developers.

I don't think that is feasible, it's a very widely used feature: https://codesearch.wmcloud.org/things/?q=\.(parse|parseDom)\(\)&files=js

Requiring developers to manually construct messages that have e.g. link targets as parameters would probably make things worse. It would definitely be worse for MediaWiki translators, who would have to deal with more "lego" messages, e.g. a separate message for an input field label and a link used within that label. Making developers do that would likely lead to more XSS issues when concatenating HTML strings, or replacing placeholders within HTML strings, inevitably goes wrong.

We can be very blunt in applying additional limitations, e.g. we could forbid the style attribute entirely instead of trying to sanitize it (which would resolve the last unresolved known issue noted in T251032#7483536).

I figured I should put my money where my mouth is, so here's the patch to do that:


(1.43 and 1.39 required minor tweaks to apply due to code style changes)

I think this will finally resolve this task.

We can be very blunt in applying additional limitations, e.g. we could forbid the style attribute entirely instead of trying to sanitize it (which would resolve the last unresolved known issue noted in T251032#7483536).

I figured I should put my money where my mouth is, so here's the patch to do that:

CR+2, I think we can get this one deployed during today's (2025-10-06) security window.

sbassett added a parent task: Restricted Task.Oct 8 2025, 12:09 AM
sbassett changed the task status from Open to In Progress.Oct 20 2025, 9:16 PM
sbassett assigned this task to matmarex.
Reedy renamed this task from mw.message(…).parse() doesn't output safe HTML, but it's being used as if it does to CVE-2025-67481: mw.message(…).parse() doesn't output safe HTML, but it's being used as if it does.Dec 8 2025, 5:55 PM

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

[mediawiki/core@REL1_39] SECURITY: Disallow 'style' attribute in client-side messages (jqueryMsg)

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

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

[mediawiki/core@REL1_43] SECURITY: Disallow 'style' attribute in client-side messages (jqueryMsg)

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

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

[mediawiki/core@REL1_44] SECURITY: Disallow 'style' attribute in client-side messages (jqueryMsg)

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

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

[mediawiki/core@REL1_45] SECURITY: Disallow 'style' attribute in client-side messages (jqueryMsg)

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

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

[mediawiki/core@master] SECURITY: Disallow 'style' attribute in client-side messages (jqueryMsg)

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

Change #1217312 merged by jenkins-bot:

[mediawiki/core@REL1_44] SECURITY: Disallow 'style' attribute in client-side messages (jqueryMsg)

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

Change #1217300 merged by jenkins-bot:

[mediawiki/core@REL1_43] SECURITY: Disallow 'style' attribute in client-side messages (jqueryMsg)

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

Change #1217287 merged by jenkins-bot:

[mediawiki/core@REL1_39] SECURITY: Disallow 'style' attribute in client-side messages (jqueryMsg)

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

Change #1217329 merged by jenkins-bot:

[mediawiki/core@REL1_45] SECURITY: Disallow 'style' attribute in client-side messages (jqueryMsg)

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

Change #1217338 merged by jenkins-bot:

[mediawiki/core@master] SECURITY: Disallow 'style' attribute in client-side messages (jqueryMsg)

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

Any objections from the security team/others to making this public? [xref T405790#11788879]

sbassett changed Author Affiliation from N/A to Wikimedia Communities.Apr 6 2026, 8:41 PM
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 High.