All vulnerabilities
GHSA-f53x-3gqx-xq76

yeswiki/yeswiki: Stored XSS via `filtertags` action parameters (bypasses raw-HTML protection)

yeswiki/yeswiki
CVSS
n/aHigh
ExploitationNo known exploitationNot in CISA KEV, which does not prove it is unexploitedCISA KEV
Exploit odds (EPSS)No scoreNo FIRST EPSS score in this snapshot yet
Fixed in4.7.0Fixed versionGitHub
Published9 Oct 2026Updated 9 Oct 2026GitHub

What fixes it

  • 4.7.0yeswiki/yeswiki, composerGitHubcomposer 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)

  1. 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)})()>"}}
  1. 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.png
  • 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)

Text from GitHub Advisory Database

Affected versions 1

  • yeswiki/yeswiki, composer< 4.7.0GitHub