yeswiki/yeswiki: Stored XSS via `filtertags` action parameters (bypasses raw-HTML protection)
What fixes it
- 4.7.0yeswiki/yeswiki, composerGitHub
composer require "yeswiki/yeswiki:^4.7.0"
A fixed version on one release line is not the upgrade target for every line. Check your exact version and any vendor backports.
Description
Target: YesWiki v4.6.7 (commit 07120ed89, latest release at time of testing, 2026-10-05) Class: CWE-79 Stored Cross-Site Scripting Found by: autonomous variant analysis over the v4.6.6→v4.6.7 GHSA fix set (theo AI agent); details and live repros below.
Summary
The {{filtertags ...}} action echoes attacker-controlled token values into the page HTML without output encoding. The injection rides inside the action's *attribute syntax*, which the wakka formatter processes before (and therefore instead of) its allow_raw_html gate — so a user who cannot write raw HTML can still store arbitrary markup that executes for every page viewer.
Details
Sink — tools/tags/actions/filtertags.php:44:
echo '<button ... data-filter="' . sanitizeEntity(_convert($tagname, YW_CHARSET, true)) . '">' . $tagname . '</button>';
$data-filter is sanitized; the button's inner text ($tagname) is echoed raw. Raw echoes also include $param['title'] (:42) and the tagbadges value (~:60).
Path — page body {{filtertags filter1="P"}}"}} → YesWiki::GetParameter() parses (name)="(value)" verbatim (includes/YesWiki.php:800-807) → get_filtertags_parameters_recursive() (tools/tags/libs/tags.functions.php:73-90) stores the token unencoded → raw echo. Because the payload rides the action-attribute path it **bypasses the wakka formatter's allow_raw_html check** (formatters/wakka.php:277-283), which is the control that would otherwise refuse markup from an editor.
Preconditions — page default ACLs on a fresh install are read=*, write=*; we stored the payload anonymously (the only anti-automation friction is the hashcash_value token, which the page's own JS fetches from tools/security/wp-hashcash-getkey.php without auth).
PoC (executed on a live v4.6.7 instance)
- Anonymous POST of a page whose body contains (parser-gates force this exact shape — see notes):
{{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)})()>"}}- Any browser visiting the page renders a fixed red banner across the whole title bar with
XSS!@<your host> — script executed in the *victim's* session. An alert(document.cookie) variant was likewise executed (screenshot in the evidence pack).
Minimal form, executed earlier: {{filtertags filter1="B : <img src=x onerror=alert(1)>"}}.
Two parser-gate constraints we worked around live (recorded because both are the *intended* filters):
get_filtertags_parameters_recursive()splits token values on,and rejects values with
more than one : — payloads must be comma- and colon-free.
- The value is echoed into HTML unquoted, so the payload must also avoid whitespace and
"
inside it.
Impact
Any visitor who can create/edit a page (by default: *any anonymous visitor*) can store JavaScript that executes for every subsequent viewer of that page — session theft, defacement, drive-by admin actions. The value of this report over "the formatter gate just needs users" is that the action-parameter path **defeats allow_raw_html=0**, the strict install posture.
Suggested fix
Output-encode $tagname (and $param['title'], tagbadges) at the echo sites in tools/tags/actions/filtertags.php, same discipline as the data-filter attr (sanitizeEntity(...) / htmlentities(..., ENT_QUOTES)). Note the same action already had a GHSA fix (SQL side) in v4.6.7 — the output side was not covered.
Evidence files
EVIDENCE.md(this lane's pack: install/posture, live DOM dump, both screenshots,
repro environment, refuted-sibling record)
yeswiki-filtertags-xss-page.png,yeswiki-imagefield-svg-xss-executed.pngyeswiki-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 asyeswiki-v467-two-executed-xss.mp4)
Text from GitHub Advisory Database
Affected versions 1
- yeswiki/yeswiki, composer< 4.7.0GitHub