Page MenuHomePhabricator

Multiple i18n XSSs in the watchlist editing dialog
Closed, ResolvedPublicSecurity

Description

The following system messages are inserted as raw HTML by the watchlist editing dialog in MW Core:

  • watchlistlabels-editwatchlist-dialog-intro-noitems
  • watchlistlabels-editwatchlist-dialog-intro
  • watchlistlabels-editwatchlist-dialog-intro-unassign-noitemlabels
  • watchlistlabels-editwatchlist-dialog-intro-unassign-noitems
  • watchlistlabels-editwatchlist-dialog-intro-unassign
  • watchlistedit-unwatch-confirmation
  • watchlistedit-unwatch-confirmation-empty
  • watchlistlabels-editwatchlist-dialog-intro-more

Reproduction steps

I'm only going to provide reproduction steps for a few messages here since all of them are inserted the same way.

  1. Add $wgUseXssLanguage = true; and $wgEnableWatchlistLabels = true; to your LocalSettings.php
  2. Go to /wiki/Special:EditWatchlist?uselang=x-xss
  3. Click on any of the three buttons

image.png (1,157×297 px, 107 KB)

Cause

The dialogBody variable is inserted as HTML using v-html.
https://github.com/wikimedia/mediawiki/blob/e9264452290c25575e1a0b66a26e0b94787b4209/resources/src/mediawiki.special.watchlistedit/EditWatchlistDialog.vue#L27-L29

All of the messages except for one are retrieved using mw.msg( ... ), which is a shorthand syntax for mw.message( ... ).text(), which does not escape the message contents.
https://github.com/wikimedia/mediawiki/blob/e9264452290c25575e1a0b66a26e0b94787b4209/resources/src/mediawiki.special.watchlistedit/EditWatchlistDialog.vue#L115-L140

Since the messages aren't escaped, the parameters aren't either. This is why the watchlistlabels-editwatchlist-dialog-intro-more message is also affected:
https://github.com/wikimedia/mediawiki/blob/e9264452290c25575e1a0b66a26e0b94787b4209/resources/src/mediawiki.special.watchlistedit/EditWatchlistDialog.vue#L161-L165
Fortunately, page titles, which are also passed as parameters, cannot contain angle brackets, so it is likely not possible to abuse this for stored self-XSS.

Additional information

watchlistlabels-editwatchlist-dialog-intro-nolabels, which is the only message that was parsed before this change, cannot actually be parsed because jqueryMsg doesn't support the {{#Special:...}} syntax in messages.
https://github.com/wikimedia/mediawiki/blob/e9264452290c25575e1a0b66a26e0b94787b4209/languages/i18n/en.json#L2222
It will instead log a warning: mediawiki.jqueryMsg: watchlistlabels-editwatchlist-dialog-intro-nolabels: Unknown operation "#special"
(see https://www.mediawiki.org/wiki/Manual:Messages_API#Feature_support_in_JavaScript for more information)
This regression was introduced in https://gerrit.wikimedia.org/r/c/mediawiki/core/+/1235609 and will be deployed through this week's train.
(Filed T417604)

Core version: 1.46.0-alpha (2c7f5b7)

Details

Risk Rating
Medium
Author Affiliation
Wikimedia Communities
Related Changes in Gerrit:

Event Timeline

Thanks for fixing this!

This patch looks good to me.

How should this be applied? If it's pushed to Gerrit now it'll make the .16 branch cut in a couple of hours.

This will need to be deployed as a private security patch by the Security-Team.
Assuming this code was added after 1.45 was cut and is only present on master, I believe it can be pushed to gerrit at some point after it is deployed (at least that's what was done for similar vulnerabilities in WMF-deployed extensions in the past when the vulnerable code was only present on master), but the Security-Team will need to decide that.

Oh yes, of course. Sorry, not sure why I was thinking it could be short-circuited!

Note that my patch might conflict with at least two pending changes on gerrit (https://gerrit.wikimedia.org/r/c/mediawiki/core/+/1239821 and https://gerrit.wikimedia.org/r/c/mediawiki/core/+/1239980), but I can rebase it once both are merged.

sbassett added a project: SecTeam-Processed.
sbassett added subscribers: mmartorana, sbassett.

@mmartorana to review, possibly deploy this Thursday, 2026-02-19.

sbassett changed the task status from Open to In Progress.Feb 17 2026, 4:47 PM
sbassett triaged this task as Medium priority.

Updated patch, rebased on 3ac4bdc4a3d71a7515fea3f37bacd1f8ad2491b1, for 1.46.0-wmf.17 (next week's train):

If this will be deployed this week, the non-rebased patch for wmf.16 still needs to be used for now:

I've reviewed both patches and they look good to me 👍

sbassett added a parent task: Restricted Task.Feb 23 2026, 11:13 PM

FYI: I've also added the rebased patch for .17 to /srv/patches/next on deployment.

FYI: I've also added the rebased patch for .17 to /srv/patches/next on deployment.

The affected file was first added in 6ce3a1c (as LabelDialog.vue) and should therefore not be present on any release branches, so do we want to wait for the security release or push it through gerrit before that (though ideally after the vulnerable JS is no longer cached)?

SecurityPatchBot changed the task status from In Progress to Open.Feb 24 2026, 12:52 AM
SecurityPatchBot raised the priority of this task from Medium to Unbreak Now!.
Patch is blocking upcoming release

Patch 06-T417603.patch is currently failing to apply for the most recent code in the mainline branch of core. This is blocking MediaWiki release 1.46.0-wmf.17(T413808)


If the patch needs to be rebased

A new version of the patch can be placed at the right location in the deployment server with the following Scap command:

REVISED_PATCH=<path_to_revised_patch>
scap update-patch --message-body 'Rebase to solve merge conflicts' /srv/patches/next/core/06-T417603.patch "$REVISED_PATCH"

If the patch has been made public

The patch can be dropped in the deployment server with the following Scap command:

scap remove-patch --message-body 'Dropping patch already made public' /srv/patches/next/core/06-T417603.patch

I've rebased that patch:

Patch is blocking upcoming release

Patch 06-T417603.patch is currently failing to apply for the most recent code in the mainline branch of core. This is blocking MediaWiki release 1.46.0-wmf.18(T413809)


If the patch needs to be rebased

A new version of the patch can be placed at the right location in the deployment server with the following Scap command:

REVISED_PATCH=<path_to_revised_patch>
scap update-patch --message-body 'Rebase to solve merge conflicts' /srv/patches/next/core/06-T417603.patch "$REVISED_PATCH"

If the patch has been made public

The patch can be dropped in the deployment server with the following Scap command:

scap remove-patch --message-body 'Dropping patch already made public' /srv/patches/next/core/06-T417603.patch
Patch is blocking this week's MediaWiki train!

Patch 06-T417603.patch is currently failing to apply for version 1.46.0-wmf.17 of core. MW train cannot move forward until the patch is fixed (T413808)
Please note you can disregard any existing previous messages in this task from SecurityPatchBot concerning version 1.46.0-wmf.17. To unblock the train, run one of the commands in this message


If the patch needs to be rebased

A new version of the patch can be placed at the right location in the deployment server with the following Scap command:

REVISED_PATCH=<path_to_revised_patch>
scap update-patch --message-body 'Rebase to solve merge conflicts' /srv/patches/1.46.0-wmf.17/core/06-T417603.patch "$REVISED_PATCH"

If the patch has been made public

The patch can be dropped in the deployment server with the following Scap command:

scap remove-patch --message-body 'Dropping patch already made public' /srv/patches/1.46.0-wmf.17/core/06-T417603.patch

I've rebased that patch:

Applied to /srv/patches/next/

The affected file was first added in 6ce3a1c (as LabelDialog.vue) and should therefore not be present on any release branches, so do we want to wait for the security release or push it through gerrit before that (though ideally after the vulnerable JS is no longer cached)?

It's fine to push it up to gerrit whenever. Which might be a good idea, since it apparently already needed another rebase?

Sure, I'll upload it then. (I don't think the rebase was necessary as the file wasn't changed since; not sure why the security patch bot complained)

Change #1243142 had a related patch set uploaded (by SomeRandomDeveloper; author: SomeRandomDeveloper):

[mediawiki/core@master] SECURITY: Always parse messages in EditWatchlistDialog

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

wmf.17 was branched yesterday, so this will replace the security patch in wmf.18.

Change #1243142 merged by jenkins-bot:

[mediawiki/core@master] SECURITY: Always parse messages in EditWatchlistDialog

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

wmf.18 has been deployed to all wikis, so I assume this can be marked as resolved now.

Reedy removed a parent task: Restricted Task.Mar 25 2026, 7:01 PM
sbassett changed the visibility from "Custom Policy" to "Public (No Login Required)".Apr 6 2026, 9:20 PM
sbassett changed the edit policy from "Custom Policy" to "All Users".
sbassett changed Risk Rating from N/A to Medium.
sbassett moved this task from Watching to Our Part Is Done on the Security-Team board.