Mar 8, 2026·5 min read·3 visits
Unauthenticated arbitrary file read in SiYuan via the `/export` endpoint using double-encoded traversal sequences (`%252e%252e`). This chains with CORS misconfigurations to allow remote attackers to steal credentials from local instances. Fixed in version 3.5.10.
A critical path traversal vulnerability exists in the `/export` endpoint of the SiYuan kernel (versions prior to 3.5.10). By utilizing double URL-encoded traversal sequences, unauthenticated attackers can bypass path sanitization mechanisms to read arbitrary files from the host filesystem. This flaw is compounded by a permissive Cross-Origin Resource Sharing (CORS) policy and an insecure localhost privilege escalation mechanism, allowing malicious websites to exfiltrate sensitive configuration data—such as API tokens and authentication codes—from a victim's local instance via drive-by attacks.
The SiYuan note-taking application (kernel) contains a critical security flaw in its file serving logic located at the /export endpoint. This endpoint is designed to allow users to export data, but it fails to properly sanitize user-supplied file paths before retrieving content from the filesystem. Specifically, the application logic manually decodes the URL path using url.PathUnescape, enabling attackers to inject directory traversal sequences that resolve outside the intended export directory.
This vulnerability is classified as Path Traversal (CWE-22) and is exacerbated by two additional design flaws: Improper Authentication (CWE-287) and Permissive Cross-Origin Resource Sharing (CWE-942). The application automatically grants administrative privileges to requests originating from localhost that target the /export path. Consequently, a remote attacker can trick a victim into visiting a malicious website which then sends cross-origin requests to the victim's local SiYuan instance (e.g., http://127.0.0.1:6806), effectively bypassing authentication and reading sensitive files without the user's knowledge.
The vulnerability arises from the sequence in which the application processes incoming URL paths in serve.go. When a request is made to /export/, the application strips the prefix and then performs an explicit URL decoding step on the remaining path string. Standard Go web servers and frameworks typically handle URL decoding automatically, but by manually invoking url.PathUnescape on the path, the application introduces a double-decoding vulnerability.
An attacker can supply a double-encoded dot-dot-slash sequence: %252e%252e. The initial layer of the web stack decodes %25 to %, resulting in the string %2e%2e passing to the application logic. The vulnerability code then calls url.PathUnescape("%2e%2e"), which transforms the string into ... When this result is passed to filepath.Join, it resolves the path relative to the exportBaseDir, allowing the attacker to traverse up the directory tree.
Compounding this is the logic in session.go, which implements an insecure trust model for local requests. The code explicitly checks if the request origin is localhost and if the path starts with /export/. If these conditions are met, the request is assigned RoleAdministrator, bypassing any token-based authentication checks. This creates a trusted code path that requires no credentials, making the traversal exploit accessible to unauthenticated attackers.
The following analysis highlights the vulnerable logic within the SiYuan kernel. The primary failure occurs in serve.go where user input is blindly decoded and joined with a base path.
serve.go)// The application extracts the path suffix
filePath := strings.TrimPrefix(c.Request.URL.Path, "/export/")
// CRITICAL: Manual unescaping allows double-encoded payloads to become valid '..'
decodedPath, err := url.PathUnescape(filePath)
if err != nil {
// error handling
}
// The path is joined without verifying it remains inside exportBaseDir
fullPath := filepath.Join(exportBaseDir, decodedPath)
// The file is served to the user
c.File(fullPath)session.go)Simultaneously, session.go removes the authentication barrier for these specific requests:
if localhost {
// If the request comes from localhost and targets /export/...
if strings.HasPrefix(c.Request.RequestURI, "/assets/") ||
strings.HasPrefix(c.Request.RequestURI, "/export/") {
// ... grant full Administrator privileges immediately.
c.Set(RoleContextKey, RoleAdministrator)
c.Next()
return
}
}The fix involves validating that the resolved fullPath still shares the exportBaseDir as a prefix after the filepath.Join operation, or removing the manual url.PathUnescape if the framework already handles it correctly. In version 3.5.10, the path handling logic was updated to prevent traversal.
Exploitation involves sending a crafted HTTP GET request containing double-encoded traversal sequences. Because the application logic grants implicit trust to localhost requests on this endpoint, the attack is particularly dangerous in client-side scenarios (e.g., a user running SiYuan locally).
conf.json or system files like /etc/passwd. The traversal sequence .. is encoded as %252e%252e.GET /export/%252e%252e/%252e%252e/conf/conf.jsonfetch to request this URL. Because SiYuan sets Access-Control-Allow-Origin: *, the browser permits the malicious page to read the response.GET /export/%252e%252e/%252e%252e/conf/conf.json HTTP/1.1
Host: 127.0.0.1:6806
Accept: */*A successful response will return the JSON configuration containing the api.token and accessAuthCode, which allow the attacker to authenticate fully to the application API.
The impact of this vulnerability is critical, rated at CVSS 9.3. The primary consequence is the total loss of confidentiality regarding the application's configuration and the host filesystem.
Data Leakage: The most immediate target is conf/conf.json, which stores the api.token. Possession of this token grants the attacker full administrative control over the SiYuan instance. They can then invoke any API endpoint, potentially leading to Remote Code Execution (RCE) via administrative features (e.g., installing plugins or modifying system settings).
System Compromise: Beyond application files, the traversal allows reading system files (e.g., /etc/passwd, ~/.ssh/id_rsa) if the process has sufficient permissions. This effectively turns the note-taking app into a gateway for local system compromise.
Attack Vector: The ability to exploit this via a web browser (CSRF/CORS) means that users are vulnerable simply by visiting a malicious website while SiYuan is running in the background.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
SiYuan Kernel SiYuan | < 3.5.10 | 3.5.10 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-22 |
| Vulnerability Type | Path Traversal |
| CVSS Score | 9.3 (Critical) |
| Attack Vector | Network |
| Attack Complexity | Low |
| Privileges Required | None |
The software uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the software does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.