CVE-2026-29793:一枚藏在WebSocket里的NoSQL注入

admin 2026-03-12 22:50:02 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: CVE-2026-29793是FeathersJS框架@feathersjs/mongodb适配器中的一处NoSQL注入漏洞。因WebSocket传输未校验id参数类型,攻击者可传入操作符对象绕过验证,造成未授权数据读写。影响版本5.0.0至5.0.41。建议立即升级至5.0.42,或在服务端钩子中增加类型校验。文章揭示了多协议框架中传输层隐式信任差异带来的风险。 综合评分: 95 文章分类: 漏洞分析,漏洞预警,WEB安全


cover_image

CVE-2026-29793:一枚藏在 WebSocket 里的 NoSQL 注入

原创

CVE-SEC CVE-SEC

CVE-SEC

2026年3月12日 10:00 四川

CVE-2026-29793:一枚藏在 WebSocket 里的 NoSQL 注入

披露时间:2026-03-10 CVSS 4.0:9.3 / CRITICAL 影响组件:@feathersjs/mongodb 5.0.0 – 5.0.41


背景

FeathersJS 是 Node.js 生态中一个相当流行的实时应用框架,支持同时以 REST 和 WebSocket(Socket.IO)两种协议暴露服务接口,后端数据库可以对接 MongoDB、PostgreSQL 等多种数据源。在使用 MongoDB 时,官方提供了 @feathersjs/mongodb 适配器作为标准数据访问层。

2026 年 3 月初,一名安全研究员向 FeathersJS 维护团队提交了一份协调披露报告,指出该适配器在处理 WebSocket 传输路径上存在一处输入验证缺失,可被未授权攻击者利用,直接读取、篡改或删除数据库中的任意记录。这就是本文要分析的 CVE-2026-29793。


漏洞是怎么来的

理解这个漏洞,需要先了解一个背景差异:REST 调用和 WebSocket 调用在参数类型上并不等价。

当你通过 HTTP REST 请求访问一个资源时,id 来自 URL 路径,例如 GET /users/abc123,路由层天然将其解析为字符串,后端收到的永远是字符串类型。

而通过 Socket.IO 调用时,参数是以 JSON 序列化的 JavaScript 值直接传递的,客户端可以传入任意合法的 JSON 结构,包括嵌套对象。换句话说,攻击者完全可以把 id 设成 {"$ne": null} 这样的东西传给服务端。

问题就出在 @feathersjs/mongodb 适配器的 getObjectId() 函数上。在 5.0.41 及之前版本,它的处理逻辑简化来说是这样的:

async _get(id, params) {
  const query = { [this.id]: this.getObjectId(id) }
  const data = await this.Model.findOne(query)
  // ...
}

getObjectId() 只负责尝试把值转成 MongoDB ObjectId,但它没有拒绝传入对象类型的能力。当 id 是 {"$ne": null} 时,函数原样返回这个对象,最终构造出的 MongoDB 查询变成了:

db.collection('users').findOne({ _id: { $ne: null } })

在 MongoDB 的查询语义里,$ne: null 表示”字段值不为 null”,这个条件几乎匹配集合里的全部文档。结果就是:攻击者不需要知道任何合法的用户 ID,一条消息就能拿到数据库里的任意记录。


问题有多早

有意思的是,类似的担忧早在 2015 年就出现在 FeathersJS 的 GitHub Issue 讨论(#113)中,当时有开发者指出框架对 Socket.IO 畸形消息的处理太过宽松,容易导致服务崩溃,建议在传输层增加参数合法性验证。这个建议在后来的版本迭代中始终没有被系统性地落实到 id 参数的类型校验上,直到这次 CVE 才触发了真正的修复。

值得单独提一下 v5.0.41 版本的情况。该版本的变更日志写着”all client methods require valid ids”,看起来像是修复了这个问题,但实际上这个校验加在了 FeathersJS 官方 JavaScript 客户端一侧,而不是服务端适配器一侧。任何直接操作 Socket.IO 协议的攻击者,完全不会经过这层客户端校验,漏洞依然存在。


时间线

| 时间 | 事件 | | — | — | | 2024-08-12 | FeathersJS v5.0.0 发布,漏洞代码路径随 @feathersjs/mongodb 适配器引入 | | 2026-02-19 | v5.0.41 发布,客户端侧新增 id 合法性校验,服务端适配器层缺陷未修复 | | 2026-03-04 | 安全研究员私下报告漏洞,维护者 David Luecke 当日提交修复 PR #3664(commit 681a3bf),v5.0.42 发布 | | 2026-03-10 | GitHub Security Advisory GHSA-p9xr-7p9p-gpqx 公开,CVE-2026-29793 正式分配并同步至 NVD |


影响范围

受影响的包是 @feathersjs/mongodb,版本区间 5.0.0 至 5.0.41,覆盖整个 v5 主干的绝大部分生命周期。

需要三个条件同时满足才能触发:

  1. 应用使用 @feathersjs/mongodb 5.0.0–5.0.41 作为数据库适配器;
  2. 应用注册了 @feathersjs/socketio,通过 Socket.IO 对外暴露服务方法;
  3. 没有在 before 钩子中对 id 参数额外做类型校验。

仅使用 HTTP REST 传输而不启用 Socket.IO 的部署不受此漏洞影响。

从 npm API 统计来看,@feathersjs/mongodb 在 2026 年 2 月初至 3 月初约一个月内的下载量约为 20,156 次,日均 650 次左右,使用规模在中等体量的 Node.js 生态组件中属于活跃水平。


攻击链全过程

攻击者只需要能访问目标服务的 Socket.IO 端点,不需要任何账户和凭证。完整的攻击路径如下:

攻击者
  |
  | 1. 发起 HTTP 请求完成 Socket.IO 握手
  v
Socket.IO 传输层
  |
  | 2. 升级为 WebSocket 连接
  v
@feathersjs/socketio 传输适配器
  |
  | 3. 接收事件,映射到服务方法调用
  |    socket.emit('get', 'users', {$ne: null}, {})
  v
FeathersJS Service Layer(before hooks 无类型校验)
  |
  | 4. id 参数原样透传
  v
@feathersjs/mongodb MongoDbAdapter._get()
  |
  | 5. getObjectId({$ne: null}) 未抛异常,原样返回对象
  v
MongoDB Node.js Driver
  |
  | 6. 执行查询 db.users.findOne({ _id: { $ne: null } })
  v
攻击者拿到未授权的用户数据

除了 get 读取,removepatchupdate 也可以用同样的方式传入操作符对象,分别对应批量删除和批量篡改。如果服务开启了 multi: true,一次 remove 调用就能清空整张集合表。

触发注入的操作符不止 $ne: null 一种,$gt: ""$exists: true$regex: "^" 等均可绕过 id 校验,修复版本的测试用例也专门覆盖了 $regex 这类变种。


修复细节

v5.0.42 的修复直接、干净。PR #3664(commit 681a3bf)在 adapter.ts 的方法入口处增加了三行类型断言:

if (typeof id !== 'string' && typeof id !== 'number' && !(id instanceof ObjectId)) {
  throw new BadRequest(`Invalid id '${JSON.stringify(id)}'`)
}

合法的 id 类型被限定为字符串、数字或 MongoDB ObjectId 实例,任何传入的普通对象一律以 400 BadRequest 拒绝,不会进入查询构造逻辑。

与此同时,PR #3665 并行修复了 _patch 方法中 data 参数的操作符注入问题(攻击者可传入 $set$unset 等更新操作符)。这两个 PR 共同构成了本次安全加固专项的完整修复。

需要说明的是,这次修复只覆盖了 id 参数这条路径。find 方法中通过 query 参数传入 MongoDB 操作符的问题(例如 { password: { $regex: "^admin" } })属于独立的攻击面,需要通过 Feathers schema 校验或 sanitizeQuery 钩子单独处理,不在本次修复范围内。


如何判断自己是否受影响

在项目目录下运行:

npm list @feathersjs/mongodb

如果输出的版本号在 5.0.0 到 5.0.41 之间,且应用同时启用了 Socket.IO 传输,则需要立即处理。


修复与缓解

直接升级是唯一推荐的解法:

npm install @feathersjs/mongodb@latest

如果因为某些原因无法立即升级,可以在所有涉及 id 参数的服务方法 before 钩子里加入类型校验,作为临时缓解:

const { BadRequest } = require('@feathersjs/errors')
const { ObjectId } = require('mongodb')

const validateId = async (context) => {
  const { id } = context
  if (id !== null && id !== undefined) {
    if (typeof id !== 'string' && typeof id !== 'number' && !(id instanceof ObjectId)) {
      throw new BadRequest('Invalid id parameter type')
    }
  }
  return context
}

在网络层,如果业务上不需要对外开放 Socket.IO,建议通过防火墙或反向代理限制 WebSocket 端口的公网访问范围。


检测思路

从流量侧,监控 WebSocket 帧载荷中是否出现 "$ne""$gt""$exists""$regex" 等 MongoDB 操作符字符串是最直接的方式。Socket.IO v4 的事件帧以 42[ 开头,可以作为流量过滤的辅助标志。

从应用侧,在 FeathersJS 的 before 钩子里记录 id 参数为对象类型的调用,可以有效捕捉异常请求。


一点延伸思考

这个漏洞有一个值得关注的模式:同一套业务逻辑,通过 REST 调用是安全的,通过 WebSocket 调用就出了问题。根因在于两条传输路径在参数类型上的隐式差异,而适配器层对这个差异毫无意识。

这种”传输协议隐式信任差异”在多协议框架里并不罕见。REST 路由层对路径参数的字符串化是隐性的类型约束,开发者已经习以为常,但当同一框架支持 WebSocket 时,这层隐性约束就消失了,如果适配器没有显式补上,盲区就出现了。

从防御角度来看,凡是同时支持 REST 和 WebSocket 的应用层框架,都应该在服务端适配器层对所有外部传入的参数做显式的类型校验,不能依赖传输协议的隐性行为来保证安全性。


参考资料

  • GitHub Security Advisory GHSA-p9xr-7p9p-gpqx:https://github.com/feathersjs/feathers/security/advisories/GHSA-p9xr-7p9p-gpqx
  • NVD CVE-2026-29793:https://nvd.nist.gov/vuln/detail/CVE-2026-29793
  • 修复 PR #3664:https://github.com/feathersjs/feathers/pull/3664
  • 修复 PR #3665:https://github.com/feathersjs/feathers/pull/3665
  • FeathersJS v5.0.42 Release Notes:https://github.com/feathersjs/feathers/releases/tag/v5.0.42
  • CWE-943:https://cwe.mitre.org/data/definitions/943.html


免责声明:

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

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

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

本文转载自:CVE-SEC CVE-SEC CVE-SEC《CVE-2026-29793:一枚藏在 WebSocket 里的 NoSQL 注入》

评论:0   参与:  0