Page MenuHomePhabricator

CVE-2025-61636: Codex Special:Block vulnerable to message key XSS
Closed, ResolvedPublicSecurity

Description

Summary

The Codex Special:Block is vulnerable to message key XSS (per checking with the x-xss language)

Background

  • The x-xss language allows finding messages which are not properly escaped in MediaWiki interfaces
  • Codex Special:Block is a re-designed interface of the Special:Block page that is enabled when $wgUseCodexSpecialBlock = true;
  • When using the x-xss language on Codex Special:Block, there is a popup indicating that the ipbsubmit message is not being properly escaped

Screenshots

image.png (980×1 px, 219 KB)

Acceptance criteria

  • Codex Special:Block is not vulnerable to message key XSS

Event Timeline

I think this might be an issue with CodexHTMLForm. Looking into it.

I think this might be an issue with CodexHTMLForm. Looking into it.

Relevant code block? That's the only place I'm seeing ipbsubmit set right now, and then it's immediately passed to setSubmitTextMsg, which CodexHTMLForm does not override. So we'd maybe want to change setSubmitTextMsg to escape messages (or maybe have some default arg option) or introduce a function like setSubmitEscapedMsg.

Just to note, I did test with the old Special:Block and didn't see the submit message causing this problem.

Just to note, I did test with the old Special:Block and didn't see the submit message causing this problem.

That's correct. because the form displayFormat is ooui when codex is disabled. If you change the form displayFormat to always be codex here on SpecialBlock you will get the xss regardless, even if the new Vue special page is being not loaded.

Just to note, I did test with the old Special:Block and didn't see the submit message causing this problem.

That's correct. because the form displayFormat is ooui when codex is disabled. If you change the form displayFormat to always be codex here on SpecialBlock you will get the xss regardless, even if the new Vue special page is being not loaded.

Interesting. Hopefully that helps you narrow the issue down.

This comment was removed by dmaza.

Proposed fix

+1 from me!

Proposed fix

Feedback (not tested locally yet):

  • Inside the getInputCodex function, can the buttonLabel logic be wrapped in the escape function in one line instead, like this:
$buttonLabel = htmlspecialchars( $this->buttonLabel ?: $this->getDefault() );
  • Then in buildCodexComponent, no change is needed:
$buttonHtml = Html::rawElement(
			'button', $attribs, $buttonLabel
		);

Proposed fix

Feedback (not tested locally yet):

  • Inside the getInputCodex function, can the buttonLabel logic be wrapped in the escape function in one line instead, like this:
$buttonLabel = htmlspecialchars( $this->buttonLabel ?: $this->getDefault() );
  • Then in buildCodexComponent, no change is needed:
$buttonHtml = Html::rawElement(
			'button', $attribs, $buttonLabel
		);

Thank you for your review. Unfortunately buildCodexComponent also gets called from CodexHTMLForm.php so we have to escape the label in that context and not before. What you are suggesting wouldn't fix this particular issue, unless I'm overlooking something.

Another alternative would be to use Html::element instead of rawElement and remove the call to htmlspecialchars

@dmaza - I think your patch in T394396#10831178 is fine. Generally, it's a best practice to escape any data as close to the relevant data sink as possible, as you've done. This typically makes the code easier to read and audit, especially for anyone who isn't immediately familiar with said code. We should try to get this security-deployed during this Monday's window (2025-05-19).

I don't understand the almost duplicate lines of code in both functions. Would it be safe to wrap the logic once in each function to ensure the values are escaped? So maybe this:

  • getInputCodex
$buttonLabel = htmlspecialchars( $this->buttonLabel ?: $this->getDefault() );
  • buildCodexComponent
$buttonHtml = Html::rawElement(
            'button', $attribs, htmlspecialchars( $buttonLabel )
);

I tested this locally and added ?uselang=x-xss&usecodex=true to the URL, and noticed no popup alert. What else should I look for? What does "success" look like?

I don't understand the almost duplicate lines of code in both functions. Would it be safe to wrap the logic once in each function to ensure the values are escaped? So maybe this:

  • getInputCodex
$buttonLabel = htmlspecialchars( $this->buttonLabel ?: $this->getDefault() );
  • buildCodexComponent
$buttonHtml = Html::rawElement(
            'button', $attribs, htmlspecialchars( $buttonLabel )
);

I tested this locally and added ?uselang=x-xss&usecodex=true to the URL, and noticed no popup alert. What else should I look for? What does "success" look like?

@lwatson I don't think the lines in @dmaza's patch are duplicative - my understanding is that the htmlspecialchars call needed to be moved from the getInputCodex() method to buildCodexComponent() because some things call buildCodexComponent() directly (like CodexHTMLForm), so the escaping needs to happen there. Your suggested code for getInputCodex would only work when that's not the case and getInputCodex() is called. We also want to make sure we're only calling htmlspecialchars once.

@dmaza the code LGTM

@AnneT Thanks

@lwatson What Anne Said

@dmaza - I think your patch in T394396#10831178 is fine. Generally, it's a best practice to escape any data as close to the relevant data sink as possible, as you've done. This typically makes the code easier to read and audit, especially for anyone who isn't immediately familiar with said code. We should try to get this security-deployed during this Monday's window (2025-05-19).

@sbassett I'm not typically around during that window. Do you need me or someone else to be around to test it after deployment?

Thank you both @dmaza and @AnneT! I noticed my mistake was misreading the diff file.

@sbassett I'm not typically around during that window. Do you need me or someone else to be around to test it after deployment?

It would be nice, yes. For a patch like this, it probably isn't essential though, unless there are reasonable concerns about it breaking the UI in some unintended way.

sbassett changed the task status from Open to In Progress.May 19 2025, 9:44 PM
sbassett triaged this task as Low priority.
sbassett moved this task from Security Patch To Deploy to Watching on the Security-Team board.
sbassett changed Risk Rating from N/A to Low.
sbassett removed a project: Patch-For-Review.
sbassett added a parent task: Restricted Task.Jul 8 2025, 8:30 PM
Reedy renamed this task from Codex Special:Block vulnerable to message key XSS to CVE-2025-61636: Codex Special:Block vulnerable to message key XSS.Sep 29 2025, 1:21 PM

Change #1193170 had a related patch set uploaded (by Reedy; author: Dmaza):

[mediawiki/core@REL1_43] SECURITY: Escape rawElement $content

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

I think this is a duplicate of T402313 which has a separate fix that does not double escape messages.

As I mentioned on gerrit already, as far as I can see, $this->buttonLabel seems to either be parsed (L63), escaped (L69) or intentionally raw HTML (L72). It shouldn't be escaped in buildCodexComponent. Instead, it should be marked as exec_html and escaped by the caller, which https://gerrit.wikimedia.org/r/c/mediawiki/core/+/1193175 (the fix for T402313) does already.

Change #1193170 merged by jenkins-bot:

[mediawiki/core@REL1_43] SECURITY: Escape rawElement $content

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

Change #1193194 had a related patch set uploaded (by Reedy; author: Dmaza):

[mediawiki/core@REL1_44] SECURITY: Escape rawElement $content

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

Change #1193194 merged by jenkins-bot:

[mediawiki/core@REL1_44] SECURITY: Escape rawElement $content

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

Change #1193216 had a related patch set uploaded (by Reedy; author: Dmaza):

[mediawiki/core@master] SECURITY: Escape rawElement $content

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

Change #1193216 merged by jenkins-bot:

[mediawiki/core@master] SECURITY: Escape rawElement $content

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

As I mentioned on gerrit already, as far as I can see, $this->buttonLabel seems to either be parsed (L63), escaped (L69) or intentionally raw HTML (L72). It shouldn't be escaped in buildCodexComponent. Instead, it should be marked as exec_html and escaped by the caller, which https://gerrit.wikimedia.org/r/c/mediawiki/core/+/1193175 (the fix for T402313) does already.

I also came here to say basically this. The current fix doesn't seem like the correct approach to me.

@Reedy - should we open this task back up to address the double-escape or just file a new bug? This should just need a revert and an explanation in the commit msg, correct?

I am going to create a revert patch. My reasoning:

  • The parameter is now marked as @param-taint $buttonLabel exec_html since the fix for T402313
  • All callers outside of HTMLButtonField escape the label already: https://codesearch.wmcloud.org/search/?q=HTMLButtonField%3A%3AbuildCodexComponent&files=&excludeFiles=&repos= (this was done to fix T402313, which is the same issue as reported here)
  • There is another method call in HTMLButtonField, which passes the buttonLabel property to the function. This property is assigned in the following places:
    • L63: Parsed message
    • L67: String literal with a unicode character
    • L69: Escaped string
    • L72: Intentionally raw HTML string
    • L126: $this->getDefault(), which will be escaped again in that line after this patch is reverted

I'll double check this in my local installation again and then create a revert.

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

[mediawiki/core@master] Revert "SECURITY: Escape rawElement $content"

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

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

[mediawiki/core@REL1_44] Revert "SECURITY: Escape rawElement $content"

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

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

[mediawiki/core@REL1_43] Revert "SECURITY: Escape rawElement $content"

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

Change #1193501 merged by jenkins-bot:

[mediawiki/core@master] Revert "SECURITY: Escape rawElement $content"

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

Change #1193508 merged by jenkins-bot:

[mediawiki/core@REL1_43] Revert "SECURITY: Escape rawElement $content"

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

Change #1193507 merged by jenkins-bot:

[mediawiki/core@REL1_44] Revert "SECURITY: Escape rawElement $content"

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

sbassett moved this task from Watching to Our Part Is Done on the Security-Team board.
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".