Semantic MediaWiki's Special:FacetedSearch cstate hidden inputs enable reflected XSS (residual of CVE-2025-10354)
📋 Description
Summary
Special:FacetedSearch cstate hidden inputs enable reflected XSS (residual of CVE-2025-10354)
Details
Affected versions and vulnerable location
- Confirmed present on latest shipped release tag available in the local clone:
SemanticMediaWiki/[email protected]. - Confirmed present on default branch
masterat HEAD18f418b4cdf2875e67a741349179a22c1573f61c.
Vulnerable sink (default-branch representation):
src/MediaWiki/Specials/FacetedSearch/HtmlBuilder.php:131-133- Builds
$hiddenby concatenating unescaped request-controlledcstate[$key]values into an HTML attribute context (value="...").
- Builds
templates/FacetedSearch/search.mustache:25- Inserts the constructed fragment via
{{{hidden}}}(no HTML escaping at this boundary).
- Inserts the constructed fragment via
Reachability trace (verified from source)
- HTTP entrypoint:
GETtoSpecial:FacetedSearchdispatches intoSMW\MediaWiki\Specials\SpecialFacetedSearch::execute().
- Request decoding boundary:
SpecialFacetedSearch::execute()constructsUrlArgsfrom$request->getValues()and callsParametersProcessor::checkRequest($request).
- Checksum gate:
ParametersProcessor::checkRequest()clearscstateonly whenfiltered != 1andgetInt('csum', 0) != crc32(getVal('q', '')).
- Decoder -> HTML assembly:
HtmlBuilder::buildHTML()iteratesforeach ( $urlArgs->getArray( 'cstate' ) as $key => $value )and concatenates each into$hiddenwithout escaping.HtmlBuilder::buildHTML()passes$hiddeninto the template variablehidden.
- HTML injection sink:
templates/FacetedSearch/search.mustacherenders{{{hidden}}}into the<form>, so the concatenated markup is inserted as raw HTML.
PoC
Reproduction steps (source-derived)
- Choose a
qvalue. - Compute
csumascrc32(q). - Send a request that includes:
q=<chosen>csum=<crc32(q)>- at least one
cstate[<key>]=<payload>entry
Example request shape:
/index.php/Special:FacetedSearch?q=Text&csum=<crc32(Text)>&cstate[0]=x%22%20autofocus%20onfocus%3Dalert(1)%20x%22
Impact
Attacker model
- Any remote attacker who can send HTTP requests to
Special:FacetedSearch(or the localized alias mapped to the sameSpecialFacetedSearchclass) can supply attacker-controlled query parameters. - Preconditions:
- The attacker must make
cstatesurviveParametersProcessor::checkRequest(), either by settingcsumtocrc32(q)(whenfiltered != 1), or by settingfiltered=1. - The attacker must supply
cstate[<key>]values containing characters that break out of the HTMLvalue="..."attribute context (for example an injected"to terminate the attribute value).
- The attacker must make
Severity and CVSS reasoning
Proposed severity: MEDIUM.
Proposed CVSS v3.1 vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N.
Rationale:
- AV:N: delivered over the network via query parameters.
- AC:L: requires only setting
q,csum, and at least onecstateentry. - PR:N: no authentication required for the request path in this code.
- UI:R: the victim must load the crafted URL.
- S:C: reflected XSS executes in the wiki origin and can affect other users depending on deployment and browser behavior.
Why this is a residual of CVE-2025-10354
- The CVE-2025-10354 hardening shipped by escaping the
qparameter before emitting it into thevalue="{{q}}"attribute. - Commit
3d675ceupdates only theqrendering to usehtmlspecialchars( $urlArgs->get( 'q', '' ) )and does not touch the adjacentcstate->$hiddenconstruction loop. - As a result,
cstateremains an unescaped input source that flows into the same raw template injection point ({{{hidden}}}), creating a distinct reflected-XSS lane.
Output (from code inspection)
Given the payload idea where cstate[0] starts with x" ... x", HtmlBuilder.php constructs the hidden fragment by concatenation:
<input name="cstate[0]" type="hidden" value="x" autofocus onfocus=alert(1) x">
Because search.mustache injects the fragment via {{{hidden}}}, the attacker-controlled markup participates in normal HTML parsing in the response body.
Suggested fix
- Escape both the
cstatekey and value when constructing$hidden. - Minimal code change in
src/MediaWiki/Specials/FacetedSearch/HtmlBuilder.php:
foreach ( $urlArgs->getArray( 'cstate' ) as $key => $value ) {
$safeKey = htmlspecialchars( (string)$key, ENT_QUOTES, 'UTF-8' );
$safeValue = htmlspecialchars( (string)$value, ENT_QUOTES, 'UTF-8' );
$hidden .= '<input name="cstate[' . $safeKey . ']" type="hidden" value="' . $safeValue . '">';
}
This keeps the raw {{{hidden}}} template insertion safe by ensuring the concatenated HTML fragment itself is attribute-escaped.
How I found it and a note on tooling
I anchored on the published CVE-2025-10354 patch by verifying in the checked-out repository that commit 3d675ce changes only the q rendering in HtmlBuilder.php to use htmlspecialchars.
Then I traced the reachable request path from SpecialFacetedSearch::execute() through ParametersProcessor::checkRequest() (checksum gate for whether cstate survives) into HtmlBuilder::buildHTML() where $hidden is constructed from cstate without escaping and injected into templates/FacetedSearch/search.mustache via {{{hidden}}}.
(End of file)
AI tooling
I used AI assistance for the code audit and for drafting this report. I manually verified the finding against the project's source at the location cited above before reporting it, and the severity and impact assessment are my own.
🎯 Affected products1
- composer/mediawiki/semantic-media-wiki:>= 4.2.0, <= 7.2.0