{"id":"CVE-2026-74802","aliases":["GHSA-3cc2-h3v6-rqpq"],"title":"SiYuan: Cross-Site WebSocket Hijacking on the admin-only network proxy endpoint (`/ws/network/proxy`) via explicit `CheckOrigin: true` bypass","summary":"SiYuan: Cross-Site WebSocket Hijacking on the admin-only network proxy endpoint (`/ws/network/proxy`) via explicit `CheckOrigin: true` bypass","severity":"low","cwe":["CWE-346","CWE-352","CWE-918"],"vendor":"siyuan-note","product":"github.com/siyuan-note/siyuan/kernel","ecosystem":"go","affected":["github.com/siyuan-note/siyuan/kernel < 0.0.0-20260803045322-cb67e0b4fab5"],"patched":["github.com/siyuan-note/siyuan/kernel 0.0.0-20260803045322-cb67e0b4fab5"],"published":"2026-10-02","updated":"2026-10-02","sourceUpdated":"2026-10-02T22:46:19Z","source":"GHSA","sourceUrl":"https://github.com/advisories/GHSA-3cc2-h3v6-rqpq","references":[{"url":"https://github.com/siyuan-note/siyuan/security/advisories/GHSA-3cc2-h3v6-rqpq"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-74802"},{"url":"https://github.com/siyuan-note/siyuan/commit/cb67e0b4fab57c9c5f458c1fd0df5ecf4417b696"},{"url":"https://github.com/siyuan-note/siyuan/releases/tag/v3.8.0"},{"url":"https://www.vulncheck.com/advisories/siyuan-cross-site-websocket-hijacking-via-network-proxy"},{"url":"https://github.com/advisories/GHSA-3cc2-h3v6-rqpq"}],"tags":["ghsa","go"],"epss":0.00164,"epssPercentile":0.05018,"ingestedAt":"2026-10-02T23:34:57.398Z","slug":"CVE-2026-74802","body":"## Overview\n\n**High**\n\n## Package\ngomod `github.com/siyuan-note/siyuan/kernel`\n\n## Affected versions\n3.7.3\n\n## Patched versions\n*(none yet — leave blank until a fix is released)*\n\n## Description\n\n### Summary\n`/ws/network/proxy` is an admin-only WebSocket forward-proxy endpoint (target URL and headers fully attacker-specifiable via query parameters). Its `websocket.Upgrader` explicitly overrides `CheckOrigin` to unconditionally return `true` — disabling the origin validation that the `gorilla/websocket` library otherwise enforces **by default**. WebSocket handshake requests are not subject to CORS preflight at all (unlike `fetch`/XHR), so origin validation for WebSocket endpoints has to be done deliberately by the server; here it has been deliberately turned *off* instead. Combined with the endpoint's own query-parameter-driven proxy target, this is a textbook Cross-Site WebSocket Hijacking (CSWSH) primitive on a capability that amounts to an authenticated network pivot through the SiYuan kernel process.\n\n### Details\n\n```go\n// kernel/api/network.go:501\nupgrader := websocket.Upgrader{\n    CheckOrigin: func(r *http.Request) bool { return true },\n}\nclientConn, upgradeErr := upgrader.Upgrade(c.Writer, c.Request, upgradeHeaders)\n```\n\nRoute registration (admin-role-gated):\n```go\n// kernel/api/router.go:614\nginServer.Handle(\"GET\", \"/ws/network/proxy\", model.CheckAuth, model.CheckAdminRole, wsProxy)\n```\n\nThe proxy target is fully attacker-controllable via query parameters, decoded and dialed directly:\n```go\n// kernel/api/network.go:348\nfunc parseForwardProxyParams(c *gin.Context) (parsedURL *url.URL, headers *http.Header, timeout time.Duration, err error) {\n    uParam := c.Query(\"u\")\n    ...\n    uBytes, decErr := base64.RawURLEncoding.DecodeString(uParam)\n    ...\n    parsedURL, err = url.ParseRequestURI(string(uBytes))\n    ...\n    hParam := c.Query(\"h\")   // optional forwarded headers, also base64-encoded\n```\n\nA malicious webpage can construct, entirely from JavaScript with no special access:\n```js\nnew WebSocket(\"ws://127.0.0.1:6806/ws/network/proxy?u=\" + base64url(attackerChosenTargetURL));\n```\nWebSocket handshake requests are GET requests carrying ambient cookies exactly like any other cross-site navigation, and are not covered by CORS preflight protections at all, this is a distinct attack surface from ordinary `fetch`/XHR-based CSRF, and easy to overlook precisely because the usual CORS mental model doesn't apply to it. Whether this is currently exploitable in a given browser depends on the same session-cookie `SameSite` configuration already covered by a separate report on this repository (no explicit `SameSite` is set on the session cookie), but even where that provides incidental protection today, the explicit `CheckOrigin: func(r *http.Request) bool { return true }` override removes a defense-in-depth layer that would otherwise exist automatically from the WebSocket library's own safe default, and is worth fixing independently of the cookie-attribute question.\n\n### Impact\nIf reachable (dependent on browser/cookie-attribute behavior at time of exploitation, as above), a malicious website visited by a user with an active, admin-privileged SiYuan session could open a WebSocket connection to this endpoint and direct the SiYuan kernel process to proxy arbitrary network traffic to an attacker-chosen target, effectively an authenticated SSRF/network-pivot primitive, using the victim's own machine and any network position it has (e.g., internal/localhost-only services on the victim's LAN that aren't reachable from the public internet), entirely via a drive-by visit to an unrelated website while SiYuan happens to be running.\n\n### PoC\nNo live browser PoC was run for this report, this is a code-level confirmation that the `CheckOrigin` override exists and unconditionally returns `true`, combined with tracing the fully attacker-controlled proxy-target construction. I also checked whether the other WebSocket-adjacent endpoints (`/ws/plugin/rpc`, `/ws/broadcast`) share this issue: they use a different WebSocket library (`gws`, not `gorilla/websocket`) with a different upgrade code path I have not independently verified for its own origin-checking defaults, flagging this as worth a follow-up check by your team rather than claiming it applies there too.\n\n```\n## Affected products\n\n| Field | Value |\n|---|---|\n| Ecosystem | **Go** |\n| Package name | `github.com/siyuan-note/siyuan/kernel` |\n| Affected versions | `<= 3.7.3` (confirmed present in 3.7.3; maintainers should confirm lower bound) |\n| Patched versions | *(none yet — leave blank until a fix is released)* |\n\n## Severity\n\n| Field | Value |\n|---|---|\n| Vector string | `CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:C/C:N/I:N/A:N` |\n| Score | **5.5 (Medium)**, reflecting that real-world exploitability depends on the co-occurring session-cookie `SameSite` question (also separately reported) and requires a victim with an active admin session to visit an attacker-controlled page (`AC:H`, `UI:R`); I'd expect this to be scored higher by your team if you determine the cookie/browser-behavior precondition is reliably met, since the underlying capability (network pivot through the kernel process) is significant. |\n\n## Weaknesses (CWE)\n\n- **CWE-346** — Origin Validation Error (primary — this is the textbook CWE for CSWSH)\n- **CWE-352** — Cross-Site Request Forgery (the broader category this specific WebSocket variant falls under)\n- **CWE-918** — Server-Side Request Forgery (secondary — the resulting capability once a connection is hijacked)\n-\n```\n\n## Suggested Fix\nReplace `CheckOrigin: func(r *http.Request) bool { return true }` with a real check — validate the `Origin` header against the expected local/loopback origin (or the configured workspace's own address), mirroring how `IsLoopbackCallback`-style validation is already done correctly elsewhere in this codebase (e.g. the MCP OAuth client's loopback-callback check). Also worth auditing the `gws`-based WebSocket endpoints (`/ws/plugin/rpc`, `/ws/broadcast`) for their own origin-validation defaults, since I did not verify those independently.\n\n## Affected packages\n\n- `github.com/siyuan-note/siyuan/kernel < 0.0.0-20260803045322-cb67e0b4fab5`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `github.com/siyuan-note/siyuan/kernel 0.0.0-20260803045322-cb67e0b4fab5`","depth":"sunlit","depthScore":14,"depthScoreParts":{"impact":13.8,"likelihood":0,"exploitation":0,"ransomware":0},"changes":[]}