---
id: GHSA-9rcc-pmj8-ffhr
title: >-
  Semantic MediaWiki's Special:FacetedSearch cstate hidden inputs enable
  reflected XSS (residual of CVE-2025-10354)
summary: >-
  Semantic MediaWiki's Special:FacetedSearch cstate hidden inputs enable
  reflected XSS (residual of CVE-2025-10354)
severity: medium
cvss: 6.1
cwe:
  - CWE-79
vendor: mediawiki
product: mediawiki/semantic-media-wiki
ecosystem: composer
affected:
  - 'mediawiki/semantic-media-wiki >= 4.2.0, <= 7.2.0'
patched:
  - mediawiki/semantic-media-wiki 7.2.1
published: '2026-09-18'
updated: '2026-09-18'
sourceUpdated: '2026-09-18T16:59:20Z'
source: GHSA
sourceUrl: 'https://github.com/advisories/GHSA-9rcc-pmj8-ffhr'
references:
  - url: >-
      https://github.com/SemanticMediaWiki/SemanticMediaWiki/security/advisories/GHSA-9rcc-pmj8-ffhr
  - url: 'https://github.com/SemanticMediaWiki/SemanticMediaWiki/releases/tag/7.2.1'
  - url: 'https://github.com/advisories/GHSA-9rcc-pmj8-ffhr'
tags:
  - ghsa
  - composer
ingestedAt: '2026-09-18T17:46:41.532Z'
---

## Overview

### 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/SemanticMediaWiki@7.2.0`.
- Confirmed present on default branch `master` at HEAD `18f418b4cdf2875e67a741349179a22c1573f61c`.

Vulnerable sink (default-branch representation):

- `src/MediaWiki/Specials/FacetedSearch/HtmlBuilder.php:131-133`
  - Builds `$hidden` by concatenating unescaped request-controlled `cstate[$key]` values into an HTML attribute context (`value="..."`).
- `templates/FacetedSearch/search.mustache:25`
  - Inserts the constructed fragment via `{{{hidden}}}` (no HTML escaping at this boundary).

#### Reachability trace (verified from source)

1. HTTP entrypoint:
   - `GET` to `Special:FacetedSearch` dispatches into `SMW\MediaWiki\Specials\SpecialFacetedSearch::execute()`.
2. Request decoding boundary:
   - `SpecialFacetedSearch::execute()` constructs `UrlArgs` from `$request->getValues()` and calls `ParametersProcessor::checkRequest($request)`.
3. Checksum gate:
   - `ParametersProcessor::checkRequest()` clears `cstate` only when `filtered != 1` and `getInt('csum', 0) != crc32(getVal('q', ''))`.
4. Decoder -> HTML assembly:
   - `HtmlBuilder::buildHTML()` iterates `foreach ( $urlArgs->getArray( 'cstate' ) as $key => $value )` and concatenates each into `$hidden` without escaping.
   - `HtmlBuilder::buildHTML()` passes `$hidden` into the template variable `hidden`.
5. HTML injection sink:
   - `templates/FacetedSearch/search.mustache` renders `{{{hidden}}}` into the `<form>`, so the concatenated markup is inserted as raw HTML.

### PoC

#### Reproduction steps (source-derived)

1. Choose a `q` value.
2. Compute `csum` as `crc32(q)`.
3. Send a request that includes:
   - `q=<chosen>`
   - `csum=<crc32(q)>`
   - at least one `cstate[<key>]=<payload>` entry

Example request shape:

```text
/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 same `SpecialFacetedSearch` class) can supply attacker-controlled query parameters.
- Preconditions:
  - The attacker must make `cstate` survive `ParametersProcessor::checkRequest()`, either by setting `csum` to `crc32(q)` (when `filtered != 1`), or by setting `filtered=1`.
  - The attacker must supply `cstate[<key>]` values containing characters that break out of the HTML `value="..."` attribute context (for example an injected `"` to terminate the attribute value).

#### 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 one `cstate` entry.
- 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 `q` parameter before emitting it into the `value="{{q}}"` attribute.
- Commit `3d675ce` updates only the `q` rendering to use `htmlspecialchars( $urlArgs->get( 'q', '' ) )` and does not touch the adjacent `cstate` -> `$hidden` construction loop.
- As a result, `cstate` remains 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:

```html
<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 `cstate` key and value when constructing `$hidden`.
- Minimal code change in `src/MediaWiki/Specials/FacetedSearch/HtmlBuilder.php`:

```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 packages

- `mediawiki/semantic-media-wiki >= 4.2.0, <= 7.2.0`

## Remediation

Upgrade to a patched release:

- `mediawiki/semantic-media-wiki 7.2.1`
