Page MenuHomePhabricator

CVE-2025-62694: Stored XSS through a system message in WikiLove
Closed, ResolvedPublicSecurity

Description

The WikiLove extension is vulnerable to stored XSS through the wikilove-what-is-this-link system message.

Reproduction

  1. Edit MediaWiki:Wikilove-what-is-this-link to javascript:alert(document.domain)
  2. Go to Special:Preferences and enable Enable showing appreciation for other users with the WikiLove tab under the Editing tab
  3. Go to the talk page of a different user and click on the heart icon
  4. Middle click on the "What is this?" link
  5. If the new tab has opened in the background, open it

image.png (239×442 px, 12 KB)

Cause

The message is inserted into the href attribute of an HTML element without any validation:
https://gerrit.wikimedia.org/g/mediawiki/extensions/WikiLove/+/5835f41c7995b45feed96850d64e3ecc9596adb3/resources/ext.wikiLove.startup/WikiLoveDialog.vue#27

Additional information

Details

Author Affiliation
Wikimedia Communities
Related Changes in Gerrit:

Event Timeline

I don't think it's contextually perfect here, but a quick fix should just involve adding an .escaped(), no?

<a target="_blank" :href="$i18n( 'wikilove-what-is-this-link' ).escaped()">
    {{ $i18n( 'wikilove-what-is-this' ) }}
</a>

I don't think it's contextually perfect here, but a quick fix should just involve adding an .escaped(), no?

<a target="_blank" :href="$i18n( 'wikilove-what-is-this-link' ).escaped()">
    {{ $i18n( 'wikilove-what-is-this' ) }}
</a>

Escaping javascript:alert(document.domain) does not change anything since there are no characters that would be escaped in the payload. Since this is a URL, this will probably need custom logic similar to Skin::makeInternalOrExternalUrl (source) in PHP.

Escaping javascript:alert(document.domain) does not change anything since there are no characters that would be escaped in the payload. Since this is a URL, this will probably need custom logic similar to Skin::makeInternalOrExternalUrl (source) in PHP.

Yeah, I'd hate to reinvent the wheel and try to port that over to JS. We've used DOMPurify in VE and other places in the past for this kind of thing. Their basic sanitize() function is very context-aware IME and is effective in sanitizing various malicious href attribute patterns (online demo). The downside is that it's a somewhat-bloated upstream dependency that would need to be bundled with the extension.

sbassett changed the task status from Open to In Progress.Aug 11 2025, 4:48 PM
sbassett assigned this task to Catrope.
sbassett triaged this task as Medium priority.
sbassett moved this task from Incoming to In Progress on the Security-Team board.
sbassett added a project: SecTeam-Processed.

@Catrope let me know if you need a hand with this one. I have familiarity with the code here.

Proposed patch:

Proposed patch:

+1, I've tested this locally and can't reproduce the vulnerability anymore (and the normal link still works fine for me).

The above patch was deployed during today's (2025-08-25) security window.

Reedy added a subscriber: gerritbot.

(This was merged on master and backported to REL1_43 and REL1_44, but the task wasn't mentioned in the commit message, so there are no gerritbot notifications here)

This needs to be backported to REL1_39 too (it's in a different file in that version):
https://github.com/wikimedia/mediawiki-extensions-WikiLove/blob/7e34f9b1fe947f1136450657b0ea2482bc7ef9c4/resources/ext.wikiLove.core.js#L91

Since callbacks seem to not be supported by ResourceLoader in 1.39 (correct me if I'm wrong), I've made a separate patch that is mostly a band-aid fix and only supports HTTPS and HTTP. However, given that 1.39 will be EOL in December and I assume almost nobody has a reason to edit this message and insert a non-HTTP link, it should be fine to fix it this way:

Since callbacks seem to not be supported by ResourceLoader in 1.39 (correct me if I'm wrong), I've made a separate patch that is mostly a band-aid fix and only supports HTTPS and HTTP. However, given that 1.39 will be EOL in December and I assume almost nobody has a reason to edit this message and insert a non-HTTP link, it should be fine to fix it this way:

@sbassett could we have somebody review this backport patch and then push it through gerrit?

Since callbacks seem to not be supported by ResourceLoader in 1.39 (correct me if I'm wrong), I've made a separate patch that is mostly a band-aid fix and only supports HTTPS and HTTP. However, given that 1.39 will be EOL in December and I assume almost nobody has a reason to edit this message and insert a non-HTTP link, it should be fine to fix it this way:

@sbassett could we have somebody review this backport patch and then push it through gerrit?

CR+1, I can push it up to gerrit for REL1_39 in a minute.

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

[mediawiki/extensions/WikiLove@REL1_39] SECURITY: Sanitize URL to prevent stored i18n XSS

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

Change #1191439 merged by jenkins-bot:

[mediawiki/extensions/WikiLove@REL1_39] SECURITY: Sanitize URL to prevent stored i18n XSS

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

Since callbacks seem to not be supported by ResourceLoader in 1.39 (correct me if I'm wrong), I've made a separate patch that is mostly a band-aid fix and only supports HTTPS and HTTP. However, given that 1.39 will be EOL in December and I assume almost nobody has a reason to edit this message and insert a non-HTTP link, it should be fine to fix it this way:

@sbassett could we have somebody review this backport patch and then push it through gerrit?

CR+1, I can push it up to gerrit for REL1_39 in a minute.

Thanks!

Mstyles renamed this task from Stored XSS through a system message in WikiLove to CVE-2025-62694: Stored XSS through a system message in WikiLove.Oct 21 2025, 5:46 AM
Mstyles changed the visibility from "Custom Policy" to "Public (No Login Required)".
Mstyles changed the edit policy from "Custom Policy" to "All Users".