BTC
ETH
SOL
BNB
GOLD
XRP
DOGE
ADA
Back to home
Security

[HIGH] Security Advisory: SiYuan vulnerable to reflected XSS via SVG namespace prefix bypass in SanitizeSVG (getDynamicIcon, unauthenticated) (github.com/siyuan-note/siyuan/kernel)

SiYuan, a self-hosted note-taking app built on Go, ships with a critical unauthenticated reflected XSS vulnerability in its dynamic icon endpoint.

SiYuan, a self-hosted note-taking app built on Go, ships with a critical unauthenticated reflected XSS vulnerability in its dynamic icon endpoint. Attackers on the same local network can trick users into loading a malicious URL, executing JavaScript in the browser at the SiYuan server’s origin. This grants access to the victim’s session cookies, allowing full read/write on notes, exports, and settings. The flaw persists in versions from v3.6.0 onward despite a partial fix.

SiYuan listens on port 6806 by default. Its /api/icon/getDynamicIcon endpoint serves user-controlled SVG content without authentication. In v3.6.0, developers added SanitizeSVG to strip dangerous tags like script and iframe. But it fails against namespace tricks: browsers parse <x:script xmlns:x="http://www.w3.org/2000/svg"> as valid SVG script because the prefix maps to the SVG namespace.

Parser Bypass Exposed

The kernel uses github.com/88250/lute/html, a Go HTML5 parser, to scrub the SVG. It stores the full qualified tag name—including prefix—in Node.Data. A check like strings.ToLower(c.Data) == "script" sees "x:script" and lets it pass. The response carries Content-Type: image/svg+xml with no CSP header. Browsers treat it as XML, resolve the namespace, and run the script.

Tested with the exact library version SiYuan uses:

html.Parse input: <x:script xmlns:x="http://www.w3.org/2000/svg">alert(1)</x:script>
Node.Data result: "x:script" (not "script")
Removed by check: false
Rendered output: <x:script xmlns:x="http://www.w3.org/2000/svg">alert(1)</x:script>

This hits every blocked tag: x:iframe, x:object, x:foreignObject, even animations. The fix is simple—strip prefixes before checking:

localName := tag
if i := strings.LastIndex(tag, ":"); i >= 0 {
    localName = tag[i+1:]
}
if localName == "script" || localName == "iframe" || ... {
    // remove
}

SiYuan’s code inserts content param via fmt.Sprintf into <text>%s</text> with zero encoding. No excuses—raw injection.

Proof-of-Concept and Attack Path

Hit this unauthenticated GET:

GET /api/icon/getDynamicIcon?type=8&color=red&content=%3C%2Ftext%3E%3Cx%3Ascript%20xmlns%3Ax%3D%22http%3A%2F%2Fwww.w3.org%2000/svg%22%3Ealert(document.domain)%3C%2Fx%3Ascript%3E%3Ctext%3E HTTP/1.1
Host: 127.0.0.1:6806

Decoded payload: </text><x:script xmlns:x="http://www.w3.org/2000/svg">alert(document.domain)</x:script><text>. Load the SVG URL in a browser—boom, alert pops at 127.0.0.1:6806.

Real attack: Craft the URL, phishing-link it via email/Slack/Discord. Victim clicks, script runs same-origin. SiYuan sets Access-Control-Allow-Origin: *, so the JS fetches any API like /api/note/getOutline or /api/storage/asset using victim cookies. Dump all Markdown notes, tweak configs, export ZIPs. No auth needed post-click.

Why This Bites and What to Do

SiYuan targets power users self-hosting for privacy—Obsidian-like but with block-based editing and sync. Default local bind, but many expose via Tailscale, ngrok, or port-forward for mobile access. LAN attackers (rogue WiFi, compromised IoT) pwn sessions instantly. Over 10k GitHub stars, active community; this hits thousands of instances.

Patch now: Devs must implement prefix stripping, add CSP (script-src 'none' for SVGs), encode inputs. Users: Update kernel ASAP (watch github.com/siyuan-note/siyuan), firewall 6806, avoid exposing publicly. Run behind reverse proxy with auth. Test your setup—curl the PoC, check for alerts.

This isn’t novel—namespace games fool parsers since SVG’s inception (2001 spec). But SiYuan’s half-fix ignored it, classic oversight. Proves self-hosted apps need battle-tested libs like bluemonday, not custom hacks. If you’re on v3.x, assume owned on shared nets. Secure it, or regret it.

April 1, 2026 · 3 min · 7 views · Source: GitHub Security

Related