Skip to content

Commit b6df971

Browse files
1 parent e570221 commit b6df971

1 file changed

Lines changed: 65 additions & 0 deletions

File tree

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-5hc8-qmg8-pw27",
4+
"modified": "2026-03-10T23:49:23Z",
5+
"published": "2026-03-10T23:49:23Z",
6+
"aliases": [
7+
"CVE-2026-31807"
8+
],
9+
"summary": "SiYuan has a SVG Sanitizer Bypass via `<animate>` Element — Unauthenticated XSS",
10+
"details": "# SVG Sanitizer Bypass via `<animate>` Element — Unauthenticated XSS\n\n## Summary\n\nSiYuan's SVG sanitizer (`SanitizeSVG`) blocks dangerous elements (`<script>`, `<iframe>`, `<foreignobject>`) and removes `on*` event handlers and `javascript:` in `href` attributes. However, it does NOT block SVG animation elements (`<animate>`, `<set>`) which can dynamically set attributes to dangerous values at runtime, bypassing the static sanitization. This allows an attacker to inject executable JavaScript into the unauthenticated `/api/icon/getDynamicIcon` endpoint (type=8), creating a reflected XSS.\n\nThis is a bypass of the fix for CVE-2026-29183 (fixed in v3.5.9).\n\n## Affected Component\n\n- **File:** `kernel/util/misc.go`\n- **Function:** `SanitizeSVG()` (lines 234-319)\n- **Endpoint:** `GET /api/icon/getDynamicIcon?type=8&content=...` (unauthenticated)\n- **Version:** SiYuan <= 3.5.9\n\n## Root Cause\n\nThe sanitizer checks attributes on elements at **parse time**. SVG `<animate>` and `<set>` elements modify attributes **at runtime** — these elements are not in the sanitizer's blocklist.\n\n### Sanitizer's blocklist (line 250)\n\n```go\nif tag == \"script\" || tag == \"iframe\" || tag == \"object\" || tag == \"embed\" || tag == \"foreignobject\" {\n n.RemoveChild(c)\n // ...\n}\n```\n\nMissing from blocklist: `animate`, `set`, `animateTransform`, `animateMotion`\n\n### Attribute check (lines 264-267)\n\n```go\n// Only checks static attributes\nif strings.HasPrefix(key, \"on\") {\n continue\n}\n```\n\nThe `<animate>` element's `values` attribute contains the payload (`javascript:...`), but the sanitizer only checks for `on*` prefix, `href`, or `xlink:href` keys. The `values`, `to`, `from`, `attributeName` attributes are all passed through.\n\n## Proof of Concept\n\n### Vector 1: `<animate>` sets `href` to `javascript:`\n\n```\nGET /api/icon/getDynamicIcon?type=8&content=</text><a><animate attributeName=\"href\" values=\"javascript:alert(document.domain)\" begin=\"0s\" fill=\"freeze\"/><text x=\"50%25\" y=\"80%25\" fill=\"red\" style=\"font-size:60px\">Click me</text></a><text>&color=blue\n```\n\nAfter template rendering, the SVG contains:\n```xml\n<svg ...>\n <text ...></text>\n <a>\n <animate attributeName=\"href\" values=\"javascript:alert(document.domain)\" begin=\"0s\" fill=\"freeze\"/>\n <text x=\"50%\" y=\"80%\" fill=\"red\" style=\"font-size:60px\">Click me</text>\n </a>\n <text></text>\n</svg>\n```\n\nThe sanitizer passes this through because:\n1. `<animate>` is not in the element blocklist\n2. `attributeName=\"href\"` — key is `attributename`, doesn't start with `on`, not `href` itself\n3. `values=\"javascript:...\"` — key is `values`, not `href`\n\nWhen the SVG is rendered in the browser (navigating directly to the URL), `<animate>` sets the parent `<a>` element's `href` to `javascript:alert(document.domain)`. Clicking \"Click me\" triggers the JavaScript.\n\n### Vector 2: `<set>` modifies event handlers\n\n```\nGET /api/icon/getDynamicIcon?type=8&content=</text><set attributeName=\"onmouseover\" to=\"alert(document.domain)\"/><text>&color=blue\n```\n\nThe `<set>` element dynamically adds an `onmouseover` event handler to the parent element at runtime.\n\n## Attack Scenario\n\n1. Attacker crafts a malicious `getDynamicIcon` URL with XSS payload\n2. Attacker sends the URL to a victim who has an active SiYuan session\n3. Victim clicks/navigates to the URL\n4. SVG renders with Content-Type `image/svg+xml` — browser renders as standalone SVG document\n5. JavaScript executes in the SiYuan server's origin\n6. Attacker steals session cookies, API tokens, or makes authenticated API calls to read/modify notes\n\n## Impact\n\n- **Severity:** CRITICAL (CVSS ~9.1)\n- **Type:** CWE-79 (Improper Neutralization of Input During Web Page Generation)\n- Unauthenticated reflected XSS via SVG injection\n- Executes in the SiYuan application origin, giving full access to authenticated APIs\n- Can chain to: data exfiltration, note modification, configuration theft (API tokens, auth codes)\n- Bypasses the fix for CVE-2026-29183\n\n## Suggested Fix\n\nAdd animation elements to the sanitizer blocklist:\n\n```go\n// In SanitizeSVG, line 250:\nif tag == \"script\" || tag == \"iframe\" || tag == \"object\" || tag == \"embed\" ||\n tag == \"foreignobject\" || tag == \"animate\" || tag == \"set\" ||\n tag == \"animatetransform\" || tag == \"animatemotion\" {\n n.RemoveChild(c)\n c = next\n continue\n}\n```\n\nOr additionally check the `values`, `to`, and `from` attributes for `javascript:` patterns:\n\n```go\nif key == \"values\" || key == \"to\" || key == \"from\" {\n if strings.Contains(val, \"javascript:\") {\n continue\n }\n}\n```\n\nAlso consider checking `attributeName` — if it targets `href`, `xlink:href`, or any `on*` attribute, the animation element should be removed entirely.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V4",
14+
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:N/SC:H/SI:H/SA:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "Go",
21+
"name": "github.com/siyuan-note/siyuan/kernel"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "0.0.0-20260310025236-297bd526708f"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "WEB",
41+
"url": "https://github.com/siyuan-note/siyuan/security/advisories/GHSA-5hc8-qmg8-pw27"
42+
},
43+
{
44+
"type": "ADVISORY",
45+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-31807"
46+
},
47+
{
48+
"type": "PACKAGE",
49+
"url": "https://github.com/siyuan-note/siyuan"
50+
},
51+
{
52+
"type": "WEB",
53+
"url": "https://github.com/siyuan-note/siyuan/releases/tag/v3.5.10"
54+
}
55+
],
56+
"database_specific": {
57+
"cwe_ids": [
58+
"CWE-79"
59+
],
60+
"severity": "MODERATE",
61+
"github_reviewed": true,
62+
"github_reviewed_at": "2026-03-10T23:49:23Z",
63+
"nvd_published_at": "2026-03-10T21:16:50Z"
64+
}
65+
}

0 commit comments

Comments
 (0)