siyuan-note/siyuan: javascript: URI in the check-auth redirect parameter executes script in the kernel origin after login
What fixes it
No source in this snapshot names a fixed version.
Description
Summary
The unauthenticated authorization page served at /check-auth accepts a to query parameter as the post-login destination and assigns it to window.location.href without any scheme validation. A crafted to value using the javascript: scheme therefore executes as script in the kernel origin as soon as the victim completes authentication on the crafted URL. The behavior was verified end to end on SiYuan v3.8.6 (the latest release at the time of reporting) and is present in every release since v2.9.0.
Details
The page source app/stage/auth.html reads the redirect target at line 459 with toPath = url.searchParams.get("to") || "/". Two sinks assign this value to the browser location after a successful login: line 638, inside the handler for the fetch("/api/system/loginAuth") response when response.code is 0, and line 722, inside the onmessage handler for the loginAuth WebSocket push, which performs the same window.location.href = toPath assignment. The /check-auth route is registered without an authentication check in kernel/server/serve.go, so an attacker can send a victim a link of the form http://<host>:6806/check-auth?to=javascript:... and the unvalidated value is embedded in the page that victim then authenticates on. No stored content or renderer injection is involved, which distinguishes this from the previously published cross-site scripting advisories for SiYuan (GHSA-mjf3-jwmf-r6wf, GHSA-jjq3-3942-x99r, GHSA-jf56-jrhq-j2qp, GHSA-5prr-vgxq-69g9, GHSA-7h8j-qw37-w46g, GHSA-v6wf-r2gr-rrgf, GHSA-rvcf-q4h8-w6c9, GHSA-qcw6-qm34-28h8 and related), all of which are stored XSS in content renderers; none of them involves the auth page or the to parameter. Source-level regression dating across release tags shows the sink is absent in v2.8.0, first appears in v2.9.0 at line 509 as window.location.href = url.searchParams.get("to") || "/", and persists through v3.8.6, where the refactored toPath variable adds the second WebSocket-triggered sink.
Proof of concept
Send the victim a link pointing at the kernel with a javascript: URI in the to parameter, for example http://<host>:6806/check-auth?to=javascript:document.title%3D%22poc%22%3Bvoid%200. The page is served without authentication, HTTP 200, and its script contains the unvalidated sink. The victim enters the valid access auth code in the page's own input and clicks the page's own submit button; loginAuth returns code 0 and the page's success handler executes window.location.href = toPath, which runs the javascript: URI in the kernel origin. In the verified run the document title of the still kernel-origin page changed to poc, and from that point script runs with the victim's authenticated session and can call the kernel's same-origin APIs.
Impact
An attacker who convinces a user to open a crafted check-auth link and authenticate on it gains arbitrary script execution in the authenticated kernel origin. The script inherits the victim's session and can invoke the kernel's same-origin APIs, which for an administrator include reading and modifying workspace content and changing kernel settings. Impact is bounded by the user-assisted precondition, since the attacker needs the victim to both open the crafted URL and complete a valid login on it.
Text from GitHub Advisory Database
Affected versions 1
- siyuan-note/siyuan, go application>= 2.9.0, <= 3.8.6GitHub