{"schema":"threatline.record/2","record":{"id":"GHSA-f53x-3gqx-xq76","aliases":["GHSA-f53x-3gqx-xq76"],"summary":"Stored XSS via `filtertags` action parameters (bypasses raw-HTML protection)","details":"**Target**: YesWiki `v4.6.7` (commit 07120ed89, latest release at time of testing, 2026-10-05)\r\n**Class**: CWE-79 Stored Cross-Site Scripting\r\n**Found by**: autonomous variant analysis over the v4.6.6→v4.6.7 GHSA fix set (theo AI agent); details and live repros below.\r\n\r\n## Summary\r\n\r\nThe `{{filtertags ...}}` action echoes attacker-controlled token values into the page HTML\r\nwithout output encoding. The injection rides inside the action's *attribute syntax*, which the\r\nwakka formatter processes before (and therefore instead of) its `allow_raw_html` gate — so a\r\nuser who cannot write raw HTML can still store arbitrary markup that executes for every page\r\nviewer.\r\n\r\n## Details\r\n\r\n**Sink** — `tools/tags/actions/filtertags.php:44`:\r\n\r\n```php\r\necho '<button ... data-filter=\"' . sanitizeEntity(_convert($tagname, YW_CHARSET, true)) . '\">'\r\n   . $tagname . '</button>';\r\n```\r\n\r\n`$data-filter` is sanitized; the button's inner text (`$tagname`) is echoed raw. Raw echoes\r\nalso include `$param['title']` (:42) and the `tagbadges` value (~:60).\r\n\r\n**Path** — page body `{{filtertags filter1=\"P\"}}\"}}` →\r\n`YesWiki::GetParameter()` parses `(name)=\"(value)\"` verbatim (`includes/YesWiki.php:800-807`) →\r\n`get_filtertags_parameters_recursive()` (`tools/tags/libs/tags.functions.php:73-90`) stores the\r\ntoken unencoded → raw echo. Because the payload rides the action-attribute path it **bypasses\r\nthe wakka formatter's `allow_raw_html` check** (`formatters/wakka.php:277-283`), which is the\r\ncontrol that would otherwise refuse markup from an editor.\r\n\r\n**Preconditions** — page default ACLs on a fresh install are `read=*`, `write=*`; we stored the\r\npayload **anonymously** (the only anti-automation friction is the `hashcash_value` token, which\r\nthe page's own JS fetches from `tools/security/wp-hashcash-getkey.php` without auth).\r\n\r\n## PoC (executed on a live v4.6.7 instance)\r\n\r\n1. Anonymous POST of a page whose body contains (parser-gates force this exact shape — see notes):\r\n\r\n```\r\n{{filtertags filter1=\"<img src=1 onerror=(function(){d=document.createElement(`div`);d.style.position=`fixed`;d.style.top=`0`;d.style.left=`0`;d.style.right=`0`;d.style.background=`#c00`;d.style.color=`#fff`;d.style.fontSize=`42px`;d.style.fontFamily=`sans-serif`;d.style.padding=`35px`;d.style.zIndex=99999;d.textContent=`XSS!@`+location.host;document.body.appendChild(d)})()>\"}}\r\n```\r\n\r\n2. Any browser visiting the page renders a fixed red banner across the whole title bar with\r\n   `XSS!@<your host>` — script executed in the *victim's* session. An `alert(document.cookie)`\r\n   variant was likewise executed (screenshot in the evidence pack).\r\n\r\nMinimal form, executed earlier: `{{filtertags filter1=\"B : <img src=x onerror=alert(1)>\"}}`.\r\n\r\nTwo parser-gate constraints we worked around live (recorded because both are the *intended*\r\nfilters):\r\n\r\n- `get_filtertags_parameters_recursive()` splits token values on `,` and rejects values with\r\n  more than one `:` — payloads must be comma- and colon-free.\r\n- The value is echoed into HTML unquoted, so the payload must also avoid whitespace and `\"`\r\n  inside it.\r\n\r\n## Impact\r\n\r\nAny visitor who can create/edit a page (by default: *any anonymous visitor*) can store\r\nJavaScript that executes for every subsequent viewer of that page — session theft,\r\ndefacement, drive-by admin actions. The value of this report over \"the formatter gate just\r\nneeds users\" is that the action-parameter path **defeats `allow_raw_html=0`**, the strict\r\ninstall posture.\r\n\r\n## Suggested fix\r\n\r\nOutput-encode `$tagname` (and `$param['title']`, `tagbadges`) at the echo sites in\r\n`tools/tags/actions/filtertags.php`, same discipline as the `data-filter` attr\r\n(`sanitizeEntity(...)` / `htmlentities(..., ENT_QUOTES)`). Note the same action already had a\r\nGHSA fix (SQL side) in v4.6.7 — the output side was not covered.\r\n\r\n## Evidence files\r\n\r\n- `EVIDENCE.md` (this lane's pack: install/posture, live DOM dump, both screenshots,\r\n  repro environment, refuted-sibling record)\r\n- `yeswiki-filtertags-xss-page.png`, `yeswiki-imagefield-svg-xss-executed.png`\r\n- `yeswiki-v467-two-executed-xss-v2.mp4` (55s X-PoC cut: fresh re-recorded from the live stack — banner execution visible on-page with burned-in captions; prior 23s cut kept as `yeswiki-v467-two-executed-xss.mp4`)","categories":["Unclassified"],"product":"yeswiki/yeswiki","vendor":"","ecosystem":null,"affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/YesWiki/yeswiki/security/advisories/GHSA-f53x-3gqx-xq76","source_modified_at":"2026-10-09T11:15:29Z","fetched_at":"2026-10-27T16:27:21.584480Z","package":{"ecosystem":"composer","name":"yeswiki/yeswiki"},"range":"< 4.7.0"}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/YesWiki/yeswiki/security/advisories/GHSA-f53x-3gqx-xq76","source_modified_at":"2026-10-09T11:15:29Z","fetched_at":"2026-10-27T16:27:21.584480Z","package":{"ecosystem":"composer","name":"yeswiki/yeswiki"},"version":"4.7.0"}],"cvss":null,"severity":"High","kev":{"listed":false},"epss":{"probability":null,"percentile":null,"score_date":null},"published_at":"2026-10-09T11:15:29Z","modified_at":"2026-10-09T11:15:29Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/YesWiki/yeswiki/security/advisories/GHSA-f53x-3gqx-xq76","fetched_at":"2026-10-27T16:27:21.584480Z","source_modified_at":"2026-10-09T11:15:29Z"}],"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":null,"confidence":"unknown","reviewed":false},"change_timestamp":"2026-10-09T11:15:29Z","content_hash":"19bb22c01aa2e5a63a5525b9f1aea5c9d46e112893bfdc2bc7bf8e6510af9bb7"}}
