GHSA-cx86-7xwp-w9wf: XSS
Reflected XSS via a forged cursor pagination token
Failure mode
Special:Ask accepts a cursor query parameter for keyset pagination (added in 7.0.0). The token is decoded by CursorEncoder, which is an unsigned base64url-encoded JSON blob, so its contents are fully attacker-controlled. When the cursor's sort anchor does not match the request's sort= / order=, QueryCreator::applyCursorIfRequested() builds an error message by interpolating the attacker-controlled sortprop / sortorder values into a raw string via Query::addErrors().
Unlike SMW's message-key errors, this raw string bypasses the message layer's sanitisation (ProcessingErrorMsgHandler::normalizeAndDecodeMessages() passes a non-key, non-encoded string through unchanged). ErrorWidget::queryError() then assembles the errors and passes them to Html::errorBox(), whose first argument is emitted as raw HTML. The result is a reflected cross-site scripting vulnerability: a crafted cursor token containing markup in sortprop (or sortorder) executes script in the victim's browser on the wiki origin.
No authentication or special permission is required. The payload is delivered via a crafted link, e.g. Special:Ask?q=...&p[cursor]=<forged token> where the token decodes to {"v":1,"sortprop":"<script>...</script>"}. Confirmed executing in a browser; the responses carry no Content-Security-Policy, so inline script is not blocked.
Remediation
- Output-encode the attacker-controlled sortprop / sortorder values before they are interpolated into the error text in QueryCreator, so no raw user input enters the error stream. - Defense in depth: the format=debug output path that reflected the same error text was hardened separately in GHSA-q5fm-9mx6-44f4.
Maintenance note
Query error strings are rendered as raw HTML by ErrorWidget::queryError() via Html::errorBox(). Any error added through Query::addErrors() that embeds user-controlled text must be output-encoded (or use a message key); pagination and validation error text is attacker-influenced and must not be treated as trusted.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
composer/mediawiki/semantic-media-wikito a version that resolves this vulnerability.Fixed in 7.2.0 - Configuration
In QueryCreator, output-encode the attacker-controlled cursor-derived values `sort_prop` and `sort_order` before they are interpolated into the error text added via `Query::addErrors()`, so no raw user input enters the error stream.
QueryCreator Output encoding for error text interpolations (sort_prop/sort_order) = Encode attacker-controlled sort_prop/sort_order before interpolating into error string via Query::addErrors() - Configuration
Ensure any error string added via `Query::addErrors()` that embeds user-controlled text is output-encoded (or uses a message key). Pagination/validation error text is attacker-influenced and must not be treated as trusted, because ErrorWidget::queryError() renders Query errors as raw HTML via Html::errorBox().
ErrorWidget::queryError() / Html::errorBox() Rendering of Query errors = Do not render attacker-controlled error strings as raw HTML (use output-encoding or message keys) - Compensating control
Add/restore a defense-in-depth content security policy so inline scripts are blocked (noting the issue occurred because responses had no Content-Security-Policy and inline script was not blocked).
Event History
Frequently Asked Questions
Does an attacker need an account or special permissions?
No. Exploitation does not require authentication or special permissions.
What must occur for the payload to execute?
A victim must visit a crafted Special:Ask request containing a forged cursor parameter. The cursor must contain attacker-controlled sort values that do not match the request's sort and order parameters.
What can successful exploitation affect?
The crafted markup executes in the victim's browser under the wiki's origin. The reported impact includes low confidentiality and integrity effects.