{"schema":"threatline.record/2","record":{"id":"GHSA-4fr3-ghqj-28wx","aliases":["GHSA-4fr3-ghqj-28wx"],"summary":"Reflected XSS in Special:SMWAdmin ID lookup via unescaped id parameter","details":"## Summary\n\nThe administrative ID lookup form on `Special:SMWAdmin` reflects the request-controlled `id` parameter into an HTML warning box without escaping. When the ID does not match any stored entity, the value is rendered through a message that uses Semantic MediaWiki's plain-text message mode (equivalent to MediaWiki's `Message::text()`), which substitutes parameters verbatim into HTML. An attacker who lures an authenticated administrator (a user holding the `smw-admin` right) into opening a crafted link achieves reflected cross-site scripting in the administrator's session, same-origin with the wiki. No CSRF token is required in the link because the handler validates the token against a value that the special page overwrites with the current user's own token before routing.\n\n## Root Cause\n\nThe empty-references branch of the ID lookup builds a warning box from a message whose single parameter is the raw, request-derived `id` string. The message is emitted with the default plain-text type, which performs no HTML escaping of parameters, and the resulting string is then passed to a raw-HTML sink. The sibling branch in the same method deliberately uses the parse message type, and other nearby form fields use the escaped type, so the security invariant (escape untrusted data before raw-HTML emission) is understood elsewhere in the file but is missing on this one branch.\n\n```text\nAffected component: Semantic MediaWiki Special:SMWAdmin ID lookup task handler\nVulnerable method: SMW\\MediaWiki\\Specials\\Admin\\Supplement\\EntityLookupTaskHandler::createMessageFromRows\nSink location: src/MediaWiki/Specials/Admin/Supplement/EntityLookupTaskHandler.php:283-285\nMessage default type: src/Localizer/MessageLocalizerTrait.php:35-42 (defaults to plain-text mode)\nPlain-text handler: src/Setup.php:276-286 (wfMessage(...)->text(), no escaping)\nAuto-satisfied token: src/MediaWiki/Specials/SpecialAdmin.php:80-84\n```\n\n## Impact\n\nAn attacker with no account and no privileges crafts a single GET URL. When an administrator holding the `smw-admin` right opens it, attacker-controlled markup executes as script in the administrator's browser in the wiki's origin. From that context the script can read and act with the administrator's session (for example issue authenticated requests, exfiltrate the session-scoped CSRF token, or perform privileged wiki actions the administrator is authorized for). The privileged-victim requirement narrows who can be targeted but does not reduce attacker control over the payload. Impact beyond in-session actions (such as full account takeover) is a plausible consequence of executing script in a privileged session and is not claimed here as demonstrated.\n\n## Proof of Concept\n\n```text\nPreconditions: a wiki with Semantic MediaWiki installed (Special:SMWAdmin enabled by\ndefault). The victim is a logged-in user holding the smw-admin right. No CSRF token or\nprior state is required in the link.\n```\n\n1. Construct the following URL against the target wiki:\n\n```text\nhttps://VICTIM-WIKI/index.php/Special:SMWAdmin/lookup?id=%3Cimg%20src%3Dx%20onerror%3Dalert(document.domain)%3E\n```\n\n2. Deliver it to a logged-in `smw-admin` administrator and have them open it.\n\n3. The ID matches no stored entity, so the empty-references branch renders the warning box. The response body contains the payload unescaped:\n\n```text\n<div class=\"...warningbox...\">The search was unable to match \"<img src=x onerror=alert(document.domain)>\" to a table entry.</div>\n```\n\nThe `<img>` element is parsed as live markup and its `onerror` handler executes, demonstrating script execution in the administrator's origin.\n\n## Attack Chain\n\n1. **Exposure:** `Special:SMWAdmin` ships in the default admin task set and its `/lookup` subpage is enabled by default; `execute()` throws a permissions error for non-administrators, so the rendered page (and payload) is reached only in the context of an `smw-admin` victim, matching the reflected-XSS threat model.\n2. **Control:** The attacker fully controls the `id` request value. `WebRequest::getText('id')` only normalizes line endings and trims; it does not strip angle brackets, so `<` and `>` survive.\n3. **Path:** The `/lookup` subpage sets the action to `lookup`, routing to the ID lookup handler. A non-numeric payload avoids the numeric fast path and flows through the sortkey branch; `addQuotes` and a `str_replace` that only rewrites `_` and `*` neutralize SQL but leave HTML metacharacters intact. No entity matches, so the empty-references else branch is reached deterministically.\n4. **Guard:** The only request-time guard is the edit-token check, which is auto-satisfied: the special page overwrites `wpEditToken` on the request object with the current user's own token before routing, so a link with no token passes. The vulnerable branch emits the message with the default plain-text type, which does not escape parameters, and the sink emits the content as raw HTML.\n5. **Primitive:** Attacker-controlled markup is written verbatim into a raw-HTML warning box that is added to the page output.\n6. **Result:** Reflected cross-site scripting executing in the authenticated administrator's browser session, same-origin with the wiki.\n\n## Bypass Evidence\n\nNo prior fix exists for this sink: the published Semantic MediaWiki cross-site scripting advisories (including CVE-2026-77606 for `Special:Ask` plain table headers, and advisories for `Special:Ask` sep and cursor token, `Special:SearchByProperty`, `Special:URIResolver`, the query debug formatter, `Special:FacetedSearch`, `Special:Search` extended profiles, and multiple `Special:Browse` stored issues) all target other endpoints. None references `Special:SMWAdmin`, the ID lookup form, or the entity-lookup task handler, so there is no existing remediation to bypass and no closed report to reconsider. This is an independently remediable path in the same weakness family as CVE-2026-77606.\n\nDisproof attempts, all refuted against source and released tags 7.3.0 and 7.2.1: (1) The edit-token check does not block a bare GET link, because `SpecialAdmin::execute` sets `wpEditToken` to the current user's own token on the request object before the handler validates it (SpecialAdmin.php:80-84), verified byte-identical at 7.2.1. (2) `WebRequest::getText` does not sanitize HTML (line-ending normalization and trim only). (3) The SQL sortkey transforms (`addQuotes` and `str_replace(['_','*'],[' ','%'])`) affect only SQL quoting and the `_`/`*` characters; angle brackets pass through. (4) The reflected `id` is still the raw payload at the sink; the by-reference reset to an empty string occurs after the warning box is built. (5) The safe sibling branch uses the parse message type, but the attacker forces the empty-references else branch by supplying an ID that matches nothing. (6) The message default type is plain-text: no message localizer is set on the task handler, so the trait falls through to the plain-text handler `wfMessage(...)->text()`, which substitutes parameters raw. (7) All downstream sinks (the form paragraph renderer, the output formatter, and the core warning-box helper) emit content as raw HTML; only element attributes are escaped.\n\n## Affected Versions\n\n- `Ecosystem: composer (Packagist)`\n- `Package: mediawiki/semantic-media-wiki`\n- `Confirmed affected range: <= 7.3.0`\n- `Latest release checked: 7.3.0 (Packagist)`\n- `Fix status: not fixed`\n\nThe vulnerable sink and the auto-satisfied token behavior were verified byte-identical at released tags 7.3.0 (latest stable) and 7.2.1, and are also present on the unreleased development head. No fix exists on any branch. The introduced (lower) boundary is not established: released artifacts older than the inspected history could not be examined, so the range is left open at the low end rather than asserting an unverified introduction point. The upper bound `<= 7.3.0` is verified against the latest released stable artifact.\n\n## Suggested Fix\n\nEscape the untrusted `id` value before it reaches the raw-HTML warning box in the empty-references branch: emit the `smw-admin-iddispose-no-references` message with the escaped message type (mirroring the sibling branch that uses the parse type), or apply HTML escaping to the parameter before constructing the box. Audit the surrounding form-building code for any other message emitted with the default plain-text type that carries request-derived data into a raw-HTML sink. As an interim mitigation, operators can restrict the `smw-admin` right to a minimal set of trusted administrators and caution them against following untrusted `Special:SMWAdmin` links, though this does not remove the underlying flaw.\n\n---\n\nReported by zx (GitHub: @manus-pi).","categories":["Applications & CMS"],"product":"mediawiki/semantic-media-wiki","vendor":"","ecosystem":null,"affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/SemanticMediaWiki/SemanticMediaWiki/security/advisories/GHSA-4fr3-ghqj-28wx","source_modified_at":"2026-09-28T18:42:04Z","fetched_at":"2026-10-27T16:27:21.584480Z","package":{"ecosystem":"composer","name":"mediawiki/semantic-media-wiki"},"range":">= 3.1.0, < 7.3.1"}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/SemanticMediaWiki/SemanticMediaWiki/security/advisories/GHSA-4fr3-ghqj-28wx","source_modified_at":"2026-09-28T18:42:04Z","fetched_at":"2026-10-27T16:27:21.584480Z","package":{"ecosystem":"composer","name":"mediawiki/semantic-media-wiki"},"version":"7.3.1"}],"cvss":6.1,"severity":"Medium","kev":{"listed":false},"epss":{"probability":null,"percentile":null,"score_date":null},"published_at":"2026-09-28T18:42:04Z","modified_at":"2026-09-28T18:42:04Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/SemanticMediaWiki/SemanticMediaWiki/security/advisories/GHSA-4fr3-ghqj-28wx","fetched_at":"2026-10-27T16:27:21.584480Z","source_modified_at":"2026-09-28T18:42:04Z"}],"sources":["GitHub Advisory Database"],"references":[],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":"product","confidence":"heuristic","reviewed":false},"change_timestamp":"2026-09-28T18:42:04Z","content_hash":"f5419f21e6d14b751ea43ec42816aa04d5d57605a29a1fcfd0ce7257dbf6fce2"}}
