$WHERE和深层嵌套文档触发OOM,因JS引擎递归遍历全量文档致栈溢出与堆失控,且MongoDB默认不限制嵌套深度,须在应用层或代理层强制校验深度并禁用高危操作符。

为什么 $where 和深层嵌套文档会触发 OOM
MongoDB 解析器对内嵌文档深度没有硬性限制,但 JS 引擎(如 SpiderMonkey)在执行 $where、$function 或聚合管道中 $eval 时,会将整个匹配文档加载进内存并递归遍历——一旦攻击者提交形如 {"a":{"b":{"c":{"d":{...}}}}} 的 200 层嵌套文档,JS 引擎栈帧爆炸 + V8/SpiderMonkey 堆分配失控,直接触发 OOM kill。
更隐蔽的是:即使不用 JS 表达式,某些驱动(如早期 PyMongo)在反序列化超深文档时,也会因递归解析 dict 层级导致 Python 解释器栈溢出或内存耗尽。
- 默认不校验嵌套深度,服务端无拦截
-
$expr中用$map/$reduce处理嵌套数组时,若输入本身是恶意嵌套结构,照样崩 - mongod 日志里只报
Out of memory或Segmentation fault,不提示来源字段
在应用层强制限制 BSON 文档嵌套深度
不能依赖 MongoDB 自身——它不提供全局嵌套层数开关。必须在入参解析阶段掐断。
- PyMongo 场景:用
bson.decode_all()前,先用bson.decode()+ 自定义RawBSONDocument解析器,在回调中计数document类型的嵌套层级,超 8 层直接抛ValueError - Node.js 场景:替换默认
Buffer解析逻辑,在readElement阶段维护一个深度计数器,遇到0x03(嵌入文档类型)就++depth,超过阈值立即throw new Error("Deep nesting detected") - Java 场景:继承
BsonDocumentCodec,重写decode(),用DecoderContext的getDepth()实时监控,maxDepth=6是较安全的起点
注意:不要用正则或字符串扫描判断嵌套——BSON 是二进制格式,{/} 不一定对应结构边界。
禁用高危操作符并约束聚合管道复杂度
$where 是最危险的入口,但很多团队只禁了它,却忘了 $function(4.4+)、$accumulator(自定义 JS 累加器)和 $expr 中的深层引用。
- 在 mongod 启动参数中显式关闭:
--javascriptEnabled=false(影响$where/$function) - 应用层白名单聚合阶段:只允许
$match、$project、$sort;禁止$map、$reduce、$filter接收用户可控字段路径 - 对
$lookup的pipeline字段做静态 AST 分析,拒绝含$$ROOT多层访问(如$$ROOT.a.b.c.d)的表达式
示例:以下聚合会被拦截 —— {"$addFields":{"x":{"$reduce":{"input":"$deep.nested.arr","initialValue":0,"in":{"$add":["$$value",{"$size":["$$this.grand.children"]}]}}}}},因为 $$this.grand.children 暗示至少 3 层嵌套访问。
用 WireGuard 或代理层做 BSON 结构预检
核心数据库前加一层轻量代理(如用 Rust 写的 mongoproxy),在 TCP 层截获原始 BSON 请求包,解析 header + length 字段后,用有限状态机扫描文档结构——比应用层 JSON 解析快一个数量级,且不依赖驱动实现。
- 检查每个
0x03元素后的 int32 length 是否合理(例如 >2MB 的单文档直接丢弃) - 统计
0x03出现频次与嵌套缩进关系,用栈模拟深度,超max_depth=7立即 reset 连接 - 对
insert/update/aggregate命令分别设不同阈值:写入类操作比查询类更严格
这招能防住绕过应用层校验的直连攻击,但要注意:mongos 分片集群下,代理需部署在每个 shard 前,不能只放在 mongos 前。
真正难防的是那些把嵌套藏在加密字段、Base64 编码值里,等进到业务逻辑才解密还原的攻击——这种得靠运行时行为分析,不在 BSON 解析层面能解决。










