SiYuanSQL漏洞|CVE-2026-29073复现&研究

admin 2026-03-26 16:10:10 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文深入研究并复现了SiYuan笔记在3.6.0版本之前存在的SQL注入漏洞(CVE-2026-29073)。该漏洞源于系统对SQL接口的管理员权限校验缺失,导致任何已登录用户(即使是读者权限)都能通过/api/search/fullTextSearchBlock接口执行任意SQL语句。文章详细分析了攻击者如何利用此漏洞进行数据泄露、篡改乃至摧毁服务,并提供了环境搭建方法与Python验证脚本。 综合评分: 85 文章分类: 漏洞分析,WEB安全,渗透测试,恶意软件,安全运营


cover_image

SiYuan SQL漏洞 | CVE-2026-29073复现&研究

原创

404号浪漫 404号浪漫

404号浪漫

2026年3月22日 21:53 北京

点击蓝字,关注我们

0x0 背景介绍

SiYuan(思源笔记)是一款开源的个人知识管理系统,支持细粒度的隐私控制。

在3.6.0之前的版本中,系统的SQL接口存在权限校验漏洞。虽然该接口检查了用户的登录状态(Basic Auth),但未对执行SQL的管理员权限进行二次验证。任何已登录的用户(即使仅拥有最低级别的读者权限)都可以通过该接口向数据库直接发送并执行任意SQL查询,导致系统内存储的笔记数据被泄露、篡改或彻底删除。

漏洞详情

| 漏洞类型 | 影响版本 | 利用复杂度 | CVE编号 | | — | — | — | — | | SQL注入 | <= 3.6.0 | 低 | CVE-2026-29073 |

攻击效果:

  • 伪执行任意SQL语句,从而造成数据泄露、篡改等。

0x1 环境搭建(Ubuntu24)

1.1-Ubuntu24+Docker搭建配置

  • ### 另存为install.sh运行
#!/bin/bash
# ==========================================# 思源笔记 (SiYuan Note) 一键部署脚本 (纯净版)# 版本: v3.6.0 (含权限校验漏洞版本,仅用于安全研究)
set&nbsp;-e
# --- 配置区域 (可自定义) ---PROJECT_DIR="$HOME/siyuan-note"CONTAINER_NAME="siyuan-note"IMAGE_VERSION="b3log/siyuan:v3.6.0"HOST_PORT="6806"CONTAINER_PORT="6806"TIMEZONE="Asia/Shanghai"
# 默认密码DEFAULT_PASSWORD="MySuperSecretRootPassword2026!"# 运行用户 ID (通常不需要改,除非宿主机用户 ID 不是 1000)RUN_UID=1000RUN_GID=1000
echo&nbsp;"=============================================="echo&nbsp;" &nbsp;思源笔记 (SiYuan Note) 一键部署脚本"echo&nbsp;" &nbsp;目标版本:&nbsp;${IMAGE_VERSION}"echo&nbsp;" &nbsp;(注:此版本存在 CVE-2026-29073 SQL 注入漏洞)"echo&nbsp;"=============================================="
# 阶段 0: 检查依赖echo&nbsp;"[*] 阶段 0/5:检查环境依赖..."if&nbsp;!&nbsp;command&nbsp;-v docker &> /dev/null;&nbsp;then&nbsp; &nbsp;&nbsp;echo&nbsp;"[x] 未检测到 Docker,请先安装 Docker"&nbsp; &nbsp;&nbsp;exit&nbsp;1fiif&nbsp;!&nbsp;command&nbsp;-v docker compose &> /dev/null && ! docker compose version &> /dev/null;&nbsp;then&nbsp; &nbsp;&nbsp;if&nbsp;command&nbsp;-v docker-compose &> /dev/null;&nbsp;then&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;alias&nbsp;docker compose="docker-compose"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;echo&nbsp;"[*] 检测到旧版 docker-compose,已兼容处理"&nbsp; &nbsp;&nbsp;else&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;echo&nbsp;"[x] 未检测到 Docker Compose,请先安装"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;exit&nbsp;1&nbsp; &nbsp;&nbsp;fifiecho&nbsp;"[+] Docker 环境检查通过"
# 阶段 1: 创建目录echo&nbsp;"[*] 阶段 1/5:创建工作目录..."mkdir&nbsp;-p&nbsp;"${PROJECT_DIR}"/{workspace,backups}cd&nbsp;"${PROJECT_DIR}"&nbsp;|| {&nbsp;echo&nbsp;"[x] 进入目录失败";&nbsp;exit&nbsp;1; }echo&nbsp;"[+] 工作目录:&nbsp;$(pwd)"
# 阶段 2: 生成配置文件echo&nbsp;"[*] 阶段 2/5:生成配置文件 (.env & docker-compose.yml)..."
# 生成 .envcat&nbsp;> .env&nbsp;<<EOF# 思源笔记访问密码/auth code&nbsp;SIYUAN_PASSWORD=${DEFAULT_PASSWORD}# 运行用户 IDPUID=${RUN_UID}PGID=${RUN_GID}EOF
# 生成 docker-compose.ymlcat&nbsp;> docker-compose.yml <<EOFversion: '3'
services:&nbsp; siyuan:&nbsp; &nbsp; image: ${IMAGE_VERSION}&nbsp; &nbsp; container_name: ${CONTAINER_NAME}&nbsp; &nbsp; restart: unless-stopped
&nbsp; &nbsp; ports:&nbsp; &nbsp; &nbsp; - "${HOST_PORT}:${CONTAINER_PORT}"
&nbsp; &nbsp; volumes:&nbsp; &nbsp; &nbsp; - ./workspace:/siyuan/workspace&nbsp; &nbsp; &nbsp; - ./backups:/siyuan/backups
&nbsp; &nbsp; environment:&nbsp; &nbsp; &nbsp; - TZ=${TIMEZONE}&nbsp; &nbsp; &nbsp; - PUID=\${PUID}&nbsp; &nbsp; &nbsp; - PGID=\${PGID}&nbsp; &nbsp; &nbsp; - SIYUAN_ACCESS_AUTH_CODE=\${SIYUAN_PASSWORD}
&nbsp; &nbsp; # 资源限制 (可选)&nbsp; &nbsp; deploy:&nbsp; &nbsp; &nbsp; resources:&nbsp; &nbsp; &nbsp; &nbsp; limits:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; memory: 1G&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cpus: '1.0'EOF
echo&nbsp;"[+] 配置文件生成完毕"
# 阶段 3: 修正权限 (关键步骤)echo&nbsp;"[*] 阶段 3/5:修正目录权限..."# 确保当前用户拥有目录所有权,避免容器内 UID 1000 无法写入chown&nbsp;-R $(id&nbsp;-u):$(id&nbsp;-g) ./workspace ./backupschmod&nbsp;-R 755 ./workspace ./backupsecho&nbsp;"[+] 权限设置完成 (所有者:&nbsp;$(whoami))"
# 阶段 4: 启动服务echo&nbsp;"[*] 阶段 4/5:启动 Docker 容器..."docker compose up -d
echo&nbsp;"[*] 等待服务初始化 (约 15 秒)..."for&nbsp;i&nbsp;in&nbsp;{1..6};&nbsp;do&nbsp; &nbsp;&nbsp;echo&nbsp;-n&nbsp;"."&nbsp; &nbsp;&nbsp;sleep&nbsp;2.5doneecho&nbsp;""
# 阶段 5: 健康检查echo&nbsp;"[*] 阶段 5/5:检查服务状态..."
# 检查容器是否运行if&nbsp;[&nbsp;"$(docker inspect -f '{{.State.Running}}' ${CONTAINER_NAME} 2>/dev/null)"&nbsp;!=&nbsp;"true"&nbsp;];&nbsp;then&nbsp; &nbsp;&nbsp;echo&nbsp;"[x] 容器启动失败!请查看日志:"&nbsp; &nbsp; docker logs --tail&nbsp;20&nbsp;${CONTAINER_NAME}&nbsp; &nbsp;&nbsp;exit&nbsp;1fi
# 检查日志中是否包含成功标志MAX_WAIT=30COUNT=0while&nbsp;[&nbsp;$COUNT&nbsp;-lt&nbsp;$MAX_WAIT&nbsp;];&nbsp;do&nbsp; &nbsp;&nbsp;if&nbsp;docker logs&nbsp;${CONTAINER_NAME}&nbsp;2>&1 | grep -q&nbsp;"kernel booted";&nbsp;then&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break&nbsp; &nbsp;&nbsp;fi&nbsp; &nbsp;&nbsp;sleep&nbsp;1&nbsp; &nbsp; COUNT=$((COUNT+1))done
if&nbsp;[&nbsp;$COUNT&nbsp;-ge&nbsp;$MAX_WAIT&nbsp;];&nbsp;then&nbsp; &nbsp;&nbsp;echo&nbsp;"[!] 警告:未在&nbsp;${MAX_WAIT}&nbsp;秒内检测到 'kernel booted',但容器正在运行。"&nbsp; &nbsp;&nbsp;echo&nbsp;" &nbsp; &nbsp;可能是首次启动索引重建较慢,请稍后检查日志。"else&nbsp; &nbsp;&nbsp;echo&nbsp;"[+] 服务内核启动成功!"fi
# 获取本地 IPLOCAL_IP=$(hostname -I | awk&nbsp;'{print $1}')
echo&nbsp;"=============================================="echo&nbsp;" &nbsp;思源笔记部署完成!"echo&nbsp;"=============================================="echo&nbsp;" &nbsp;访问地址:"echo&nbsp;" &nbsp; &nbsp; 局域网: http://${LOCAL_IP}:${HOST_PORT}"echo&nbsp;" &nbsp; &nbsp; 本地: &nbsp; http://localhost:${HOST_PORT}"echo&nbsp;""echo&nbsp;" &nbsp;初始密码/auth code:"echo&nbsp;" &nbsp; &nbsp;&nbsp;${DEFAULT_PASSWORD}"echo&nbsp;" &nbsp; &nbsp; (提示:请编辑 .env 文件修改密码并重启容器以保障安全)"echo&nbsp;""echo&nbsp;" &nbsp;数据位置:"echo&nbsp;" &nbsp; &nbsp;&nbsp;${PROJECT_DIR}/workspace"echo&nbsp;" &nbsp; &nbsp;&nbsp;${PROJECT_DIR}/backups"echo&nbsp;""echo&nbsp;" &nbsp;⚠️ &nbsp;安全提醒:"echo&nbsp;" &nbsp; &nbsp; 此版本 (v3.6.0) 存在 CVE-2026-29073 漏洞"echo&nbsp;" &nbsp; &nbsp; 普通用户可通过 /api/search/fullTextSearchBlock 接口执行任意 SQL"echo&nbsp;" &nbsp; &nbsp; 请仅在隔离环境用于安全测试,勿用于生产环境!"echo&nbsp;""echo&nbsp;" &nbsp;常用命令:"echo&nbsp;" &nbsp; &nbsp; 查看日志:docker logs -f&nbsp;${CONTAINER_NAME}"echo&nbsp;" &nbsp; &nbsp; 重启服务:docker compose restart"echo&nbsp;" &nbsp; &nbsp; 停止服务:docker compose down"echo&nbsp;"=============================================="

0x2 漏洞复现

2.1-脚本验证

  • ## python验证
https://github.com/Kai-One001/cve-/blob/main/SiYuan-CVE-2026-29073.py

2.2-手动验证 2.1 场景 A:数据泄露 (SELECT)

2.2 场景 B:内容破坏 (INSERT)

2.3 场景 C:服务可用性被摧毁 (DROP TABLE)

  • 原理概念
{"method":2,"query":"DROP TABLE assets","page":1,"pageSize":10}
  • 后果: assets表被删除,导致图片上传、附件管理等功能彻底失效,系统日志报错刷屏。
{"method":2,"query":"DELETE FROM blocks","page":1,"pageSize":10}
  • 后果: 执行后,前端所有笔记内容瞬间消失,且无法通过常规手段恢复(除非有本地备份)。

2.4 场景 D:跨表敏感数据读取(任意表”伪装为块”泄露)

此前 PoC 仅演示了对blocks表的直接读取。结合源码可以发现,/api/search/fullTextSearchBlock在SQL模式下,会把任意 SQL 结果强行映射为Block结构(scanBlockRows 依次 Scan 21 个字段),然后再返回给前端。

这意味着:只要攻击者构造出一条”列数量和类型上可以被Block结构接收”的 SELECT,就可以把任意业务表的数据(例如用户信息表、附件元数据表、权限/分享策略表中的敏感字段)注入到某些块字段中(如 contentmemoalias 等),从而”披着块的皮”返回到接口响应里,实现跨表敏感数据泄露。

HTTP 流量示例(概念)

POST /api/search/fullTextSearchBlock HTTP/1.1Host:<目标主机>:<端口>Content-Type: application/jsonCookie: siyuan=<普通用户Session>
{&nbsp; &nbsp;&nbsp;"method": 2,&nbsp; &nbsp;&nbsp;"query":&nbsp;"<构造为:SELECT ... FROM 目标敏感表/视图,通过子查询/表达式填充成 Block 结构的列>",&nbsp; &nbsp;&nbsp;"page": 1,&nbsp; &nbsp;&nbsp;"pageSize": 10}
  • 不再局限于blocks内容本身,而是可以读取数据库中任意业务表的敏感信息,包括但不限于用户账号信息、共享/访问控制策略、附件/资源定位信息等
  • 由于返回结构仍然是blocks,这类泄露在日志/审计层面不易与普通搜索流量区分,增加排查难度

2.5 场景 E:解析降级导致的写操作执行(DELETE/UPDATE/INSERT/DROP)

searchBySQL 最终调用的是 sql.SelectBlocksRawStmt,其核心逻辑为:

•优先尝试用 sqlparser.Parse(stmt) 解析&nbsp;SQL;•若解析失败(err&nbsp;!=&nbsp;nil),则直接调用 selectBlocksRawStmt(stmt, limit),内部对 stmt 原样调用底层query(stmt)•对于解析成功但 AST 类型不是&nbsp;SELECT/UNION&nbsp;的情况,则直接&nbsp;return(不执行)。

结合这一逻辑,可以得出结论:

  • 若攻击者构造一条在 SQLite 中合法可执行,但当前 sqlparser 无法正确解析的 SQL 语句(例如使用特定方言/语法特性),就会触发”解析失败 → 降级直接执行”的路径;
  • 在该路径下,即使语句是 DELETE/UPDATE/INSERT/DROP 等写操作,底层数据库仍会实际执行,只是由于结果集无法映射为 Block 结构,接口可能返回空数组或不含数据。

HTTP 流量示例(结构模板)

POST /api/search/fullTextSearchBlock HTTP/1.1Host: <目标主机>:<端口>Content-Type: application/jsonCookie: siyuan=<普通用户Session>
{&nbsp; &nbsp;&nbsp;"method": 2,&nbsp; &nbsp;&nbsp;"query":&nbsp;"<利用 SQLite 支持但当前 sqlparser 难以解析的写操作 SQL,用于在测试环境中对测试表/测试记录做最小破坏验证>",&nbsp; &nbsp;&nbsp;"page": 1,&nbsp; &nbsp;&nbsp;"pageSize": 10}
  • 攻击者不再只是”理论上能写”,而是可以在一定条件下实际删改任意表中的记录、结构甚至整表;
  • 这类写操作难以从接口返回中直接感知,容易在无声无息中完成数据破坏或后门植入。

2.6 场景 F:持久化篡改与二次受害(影响其他用户)

在确认写操作可行后,攻击者可以针对 blocks 表本身发起更具”传播性”的篡改。例如:

//我测没有成功,可能疏忽哪里了•通过任意 SQL 改写指定文档、指定块的 content / memo /&nbsp;alias&nbsp;等字段,使其加入恶意内容(诱导链接、伪造提示信息等);•或修改块的元数据(如路径、标签、名称),干扰后续正常使用与检索。

由于 blocks 是前端渲染的核心数据源,上述篡改将具备以下特征:

  • •在后续任意访问该文档/块的场景中被呈现;
  • 对所有拥有访问权限的用户可见,而不仅限于当前用户。

HTTP 流量示例

POST /api/search/fullTextSearchBlock HTTP/1.1Host: <目标主机>:<端口>Content-Type: application/jsonCookie: siyuan=<普通用户Session>
{&nbsp; &nbsp;&nbsp;"method": 2,&nbsp; &nbsp;&nbsp;"query":&nbsp;"<针对 blocks 表的 UPDATE/INSERT 等写操作,用于在测试环境中对某个测试文档块内容进行特征化改写>",&nbsp; &nbsp;&nbsp;"page": 1,&nbsp; &nbsp;&nbsp;"pageSize": 10}

可能的建议:

•在测试环境创建一篇"测试文档",记录其某个块的&nbsp;id;•使用 SQL 模式对该块的内容或元数据做"打标签式"改写(例如增加明显的测试标记字符串);•使用:&nbsp; •同一账号;&nbsp; •另一个普通账号;&nbsp; •如有,公开访问码访问; 分别打开该文档,确认所有访问路径下都呈现了改写后的内容。
  • 攻击者可以对现有文档进行持久化篡改,影响范围覆盖所有合法访问者,形成典型的”二次受害”;
  • 若篡改内容包含钓鱼链接、伪造指引等,将进一步放大社会工程学攻击成功率;
  • 对于依赖笔记内容进行自动化处理/同步的场景,还可能引入下游系统的风险。

2.7 场景 G:复杂查询导致的性能退化与拒绝服务

  • 如果你只想增加危害,可以尝试DOS:
•利用&nbsp;SQL&nbsp;模式构造高复杂度查询(大范围全表扫描、多表&nbsp;JOIN、大量模糊匹配等),在未做资源/超时限制的情况下,可能显著拉高 CPU/IO/内存占用;•即使不删库,只通过不断触发该接口即可对服务形成"慢性拒绝服务"攻击;•可通过记录接口响应时间、服务器日志中的&nbsp;SQL&nbsp;耗时等指标来给出客观证据。

2.3-复现流量特征 (PCAP)

  • 获取认证后进行SQL查询

  • INSERT操作

  • 查询成功,不过没在WEB找到(另外响应体是正常的,我这看的是TCP流量,应该是HTTP)

#


0x3 漏洞原理分析

3.1- [路由守卫] 被遗忘的关卡:当搜索遇上 SQL

我这边追踪起点是Web框架的路由注册文件。在SiYuan的架构中,API接口的安全性很大程度上依赖于中间件链Middleware Chain的挂载。

首先,定位到 kernel/api/router.go。在这里,可以看到了两个看似功能相似,但不同的接口定义:

  • 受保护的 SQL 查询接口 (基准线):
// kernel/api/router.go Line 177ginServer.Handle("POST",&nbsp;"/api/query/sql", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly,&nbsp;SQL)
  • 要访问此接口,请求必须依次通过三道关卡:
•CheckAuth: 确认用户已登录。•CheckAdminRole: 关键! 强制要求用户必须是管理员。•CheckReadonly: 确保当前不是只读模式(防止写操作)。•只有同时满足这三个条件,SQL 函数才会被执行。
  • 失守的搜索接口 (漏洞点):
// kernel/api/router.go Line 188ginServer.Handle("POST",&nbsp;"/api/search/fullTextSearchBlock", model.CheckAuth, fullTextSearchBlock)
  • 这里出现了很经典的”防御缺口”。可能觉得这只是一个”搜索”功能,因此只挂载了CheckAuth
  • 不过,往往都有是开发的疏忽导致意想不到的结果,忽略了这个搜索功能,只要用户登录(哪怕是Reader角色),就能直入调用fullTextSearchBlock函数。
  • 这是第一道防线的崩塌:权限校验的粒度与内部功能的危险程度严重不匹配。

3.2- [逻辑黑洞] 危险的”方法”:信任边界的彻底瓦解

fullTextSearchBlock函数 (kernel/api/search.go) 后,我们继续追踪数据流向。代码解析了前端传来的参数,其中关注到这method字段。

// kernel/api/search.go Line 389-411func&nbsp;fullTextSearchBlock(c *gin.Context)&nbsp;{&nbsp; &nbsp;&nbsp;// ... 参数解析 ...&nbsp; &nbsp; page, pageSize, query, paths, boxes, types, method, orderBy, groupBy := parseSearchBlockArgs(arg)

&nbsp; &nbsp;&nbsp;// 核心调用:将用户控制的 method 和 query 直接传入模型层&nbsp; &nbsp; blocks, matchedBlockCount, matchedRootCount, pageCount, docMode :=&nbsp; &nbsp; &nbsp; &nbsp; model.FullTextSearchBlock(query, boxes, paths, types, method, orderBy, groupBy, page, pageSize)&nbsp; &nbsp;&nbsp;// ...
}

小知识课堂:

注意函数名前面的model.前缀。在Go语言中,这表示调用的是名为model 的包(Package)中的函数,被调用的函数名是 FullTextSearchBlock核心逻辑就是 model 包里(PS:IDE可以直接:鼠标按住 Ctrl (或 Cmd) 点击 model.FullTextSearchBlock跳转)

接着,我们深入到kernel/model/search.go中的FullTextSearchBlock实现。在这里,逻辑发生了分叉:

// kernel/model/search.go Line&nbsp;1205-1206switch&nbsp;method&nbsp;{case&nbsp;0,&nbsp;1:&nbsp; &nbsp;&nbsp;//&nbsp;正常的全文检索逻辑,安全&nbsp; &nbsp; blocks = searchByKeyword(...)&nbsp;case&nbsp;2:&nbsp;&nbsp; &nbsp;&nbsp;//&nbsp;当&nbsp;method=2 时,直接进入&nbsp;SQL&nbsp;执行模式&nbsp; &nbsp;&nbsp;blocks,&nbsp;matchedBlockCount,&nbsp;matchedRootCount&nbsp;=&nbsp;searchBySQL(query,&nbsp;beforeLen,&nbsp;page,&nbsp;pageSize)}
  • 这里的case 2是整个漏洞的逻辑核心。
  • 为了实现某种高级自定义搜索功能,允许传入原始SQL。但是,在这个分支内部,没有任何关于”当前用户是否有权执行SQL”的二次检查。
  • 盲目地信任了上游路由传来的请求,认为既然能进到这个函数,就是安全的。

3.3 [执行深渊] 裸奔的查询器:从字符串到数据库指令

最后,我们来到了漏洞爆发的终点——searchBySQL函数 (kernel/model/search.go) 及其调用的底层驱动。

// kernel/model/search.go Line 1460-1462func&nbsp;searchBySQL(stmt&nbsp;string, beforeLen, page, pageSize&nbsp;int)&nbsp;(ret []*Block, ...) {&nbsp; &nbsp; stmt = strings.TrimSpace(stmt)&nbsp; &nbsp;&nbsp;// 直接将用户输入的 stmt 传递给底层执行器&nbsp; &nbsp; blocks := sql.SelectBlocksRawStmt(stmt, page, pageSize)&nbsp; &nbsp;&nbsp;// ...}
  • 继续追踪到kernel/sql/block_query.go
109110111112113114115116117118119120121122123124// kernel/sql/block_query.go Line 566-569func&nbsp;SelectBlocksRawStmt(stmt&nbsp;string, page, limit&nbsp;int)&nbsp;(ret []*Block) {&nbsp; &nbsp; parsedStmt, err := sqlparser.Parse(stmt)&nbsp; &nbsp;&nbsp;if&nbsp;err !=&nbsp;nil&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 解析失败?没关系,降级处理,直接执行原始语句!&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;selectBlocksRawStmt(stmt, limit)&nbsp;&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;// ... (即使解析成功,后续逻辑也未做严格的只读限制)}
// kernel/sql/block_query.go Line 713-714func&nbsp;selectBlocksRawStmt(stmt&nbsp;string, limit&nbsp;int)&nbsp;(ret []*Block) {&nbsp; &nbsp;&nbsp;// 【最终爆发点】调用 Go 标准库 db.Query,执行任意 SQL&nbsp; &nbsp; rows, err := query(stmt)&nbsp;&nbsp; &nbsp;&nbsp;// ...}
  • kernel/sql/database.go中,query函数仅仅是Godatabase/sql 包的一层薄封装:
// kernel/sql/database.go Line 1327-1337func&nbsp;query(query&nbsp;string,&nbsp;args&nbsp;...interface{}) (*sql.Rows, error)&nbsp;{&nbsp; &nbsp;&nbsp;// ...&nbsp; &nbsp;&nbsp;return&nbsp;db.Query(query,&nbsp;args...)&nbsp;// 这里是 SQLite 执行的绝对入口}

至此,链路完全打通

  • 输入无过滤:用户的query 参数未经过任何白名单校验。
  • 解析可绕过:即使有sqlparser,代码逻辑在解析失败时选择了”降级执行”,这让攻击者可以通过构造特殊语法绕过解析检查。
  • 执行无限制:最终调用的db.Query是通用的SQL执行器,它不区分SELECT还是DROP

在 SQLite中,db.Query甚至可以执行 DELETE和UPDATE(尽管通常推荐用 Exec,但在某些驱动实现或特定上下文中,副作用依然会发生,或者攻击者利用db.Exec的类似调用路径)。

完整攻击链路总结:

A[攻击者: 携带Token 角色] -->|1. POST /api/search... method=2| B(路由层: router.go)B&nbsp;-->|2. 缺失 CheckAdminRole | C{逻辑层: search.go}C&nbsp;-->|3. switch case&nbsp;2&nbsp;| D[函数: searchBySQL]D&nbsp;-->|4. 透传原始 SQL | E[驱动层: block_query.go]E&nbsp;-->|5. 降级/直接执行 | F((SQLite 数据库))F&nbsp;-->|6. 执行 DELETE/DROP | G[ 数据毁灭/泄露]

0x4 修复建议

1、升级最新版本:将组件升级最新版本

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

2、临时防护措施:

  • 反向代理拦截:在Nginx/Apache层直接禁止访问该特定接口

  • 网络隔离:确保思源笔记实例仅监听127.0.0.1,严禁暴露在公网或非受信局域网。

/**发芽了发芽了**/


免责声明:本文仅用于安全研究目的,未经授权不得用于非法渗透测试活动。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:404号浪漫 404号浪漫 404号浪漫《SiYuan SQL漏洞 | CVE-2026-29073复现&研究》

评论:0   参与:  0