聚合管道注入必然发生,因用户输入若直接拼入$match、$project等阶段,攻击者可用{"$ne": null}绕过条件、{"$regex": ".*"}全表扫描或{"$where": "sleep(5000)"}拖垮服务。

聚合管道注入不是“会不会发生”的问题,而是“用错方式就必然触发”的风险——aggregate() 本身无害,但一旦把用户输入直接拼进 $match、$project 或 $where 阶段,攻击者就能用 {"$ne": null} 绕过条件、用 {"$regex": ".*"} 全表扫描、甚至用 {"$where": "sleep(5000)"} 拖垮服务。
为什么 aggregate() 比 find() 更危险
聚合管道天然支持多阶段、嵌套表达式和动态字段引用,而多数驱动(如 Node.js 的 mongodb 客户端、Spring Data MongoDB 的 Aggregation)默认不拦截或校验传入的 stage 对象结构。这意味着:
- 前端传一个
{sortField: "$createdAt", sortOrder: -1},后端若直接构造成{$sort: {[sortField]: sortOrder}},攻击者把sortField改成"$updatedAt;{ $eval: 'db.getSiblingDB(\"admin\").runCommand({listDatabases:1})' }"就可能触发高危操作(尤其在旧版本或未禁用$where/$eval时) -
$lookup阶段若允许用户控制from字段名,可能被篡改为任意集合名,导致跨集合数据泄露 - Spring Data MongoDB 的
Aggregation.match(Criteria.where(...))是安全的,但若绕过Criteria直接用Aggregation.stage(new Document("$match", userSuppliedDoc)),就等于主动打开注入门
必须禁用的高危操作符与配置项
不是所有操作符都该被允许出现在用户可控的聚合阶段中。生产环境应从服务端和数据库两层封堵:
- 在 MongoDB 启动配置中显式禁用:
setParameter: { enableJavaScript: false }(关闭$where、$mapReduce、$function等 JS 执行能力) - 通过
mongod配置文件设置:security.javascriptEnabled: false(比启动参数更稳定,重启不失效) - 删除或限制
dbAdmin角色,确保业务用户无法执行db.runCommand({setParameter:1, ...})动态开启 JS - 检查副本集所有节点的
mongod.conf,确认没有遗留javascriptEnabled: true—— 单个节点开启即全集失守
如何安全地透传用户排序/分组字段
业务常需支持“按 X 字段排序”“按 Y 字段分组”,不能一刀切禁止,而要收口校验。关键不是“拦住什么”,而是“只放行什么”:
- 建立白名单字段映射表,例如:
const allowedSortFields = { createdAt: 1, updatedAt: 1, score: 1, status: 1 };,用户传sort=score→ 转为{$sort: {score: -1}};传sort=$ne直接 400 拒绝 - 对分组字段(
$group的_id),禁止用户直接填字段名,改用预定义枚举:groupBy: "region"→ 映射为{$group: {_id: "$region", count: {$sum: 1}}} - Node.js 中使用
lodash.isPlainObject()+JSON.stringify()双重检测输入是否为“纯 JSON 对象”,排除原型污染和函数字段;Java 中用ObjectMapper.readTree()后遍历节点类型,拒绝任何JsonNodeType.FUNCTION或含$开头键名的子对象
副本集环境下特别容易忽略的点
副本集本身不放大注入风险,但会掩盖问题:primary 节点被注入后执行恶意聚合,secondary 会忠实地同步 oplog 并重放——你以为只是查数据,其实已在所有节点上执行了 $out 写入或 $merge 覆盖。更隐蔽的是,监控工具(如 mongostat 或云厂商性能面板)通常只看 query 类型耗时,而聚合操作归类为 command,异常慢查询可能被漏报。











