M

[MongoDB] MongoDB-CVE-2025-14847漏洞

RoLingG 其他 2026-01-01

MongoDB-CVE-2025-14847漏洞

出自:mongobleed。这个问题也是彩虹六号 · 围攻 2025 年 12 月 27 号服务器被黑的原因之一。

本质

MongoDB 在 3.6–8.2 的 zlib 解压路径里,把「分配的缓冲区长度」当成「实际解压后的数据长度」回传给上层。攻击者只要发一个压缩 BSON 报文,把 uncompressedSize 故意填得很大,就能让服务端返回一大片未初始化内存。

影响面

  • 所有默认开启 compression=zlib 的实例(含 Atlas 部分旧集群、Docker 官方镜像、自建副本集/分片)。
  • 无需认证,只要 TCP 能连 27017 就能打;若开了 --bindIp 限制或前端有代理,可缓解但难根除。
  • 泄露内容取决于内存布局:WT 缓存页、connection 对象、/proc 文件系统缓存、日志缓冲、甚至前任用户的查询结果。

修复方案

  • 最干脆:升级到对应补丁版(或 8.3/9.0 最新稳定版)。(首选)
  • 若暂时无法重启,可热关闭 zlib
    db.adminCommand({setParameter:1, "net.compression.compressors":"snappy,zstd"})
    然后重启节点去掉 zlib
  • 前端加网络层 ACL,禁止非业务 IP 访问 27017;Atlas 用户可在 Network Access 里收紧源地址。
  • 容器场景用 127.0.0.1-only 或 sidecar mesh,避免把 27017 映射到宿主机。

简单来说

服务端把「我有多大杯子」当成「我有多少水」告诉了客户端,于是攻击者只要递一只「大杯子」,就能让服务端把杯里残留的「别人的水」倒出来。

正常发送报文都是:

客户端→服务器:

OP_COMPRESSED{ … uncompressedSize: 真实长度 187, compressor: zlib, compressedPayload: … }

服务端收到后:

  • 按 uncompressedSize 分配 187 字节 buffer;
  • 调 zlib 解压,返回 实际 长度 187;
  • 把 187 字节交给 BSON 解码。

正常发送,正常解压解码,一切正常。

但利用该漏洞,攻击者改字段:

uncompressedSize 写成 8192 字节大小,实际只压缩了 20 字节垃圾数据。
服务端仍按 8192 字节大小 分配,但 zlib 只往开头写了 20 字节就返回;剩下的 8 172 字节完全是 malloc 时残留的内存(WT 缓存、日志、别的连接的请求……)。

关键 bug 代码示例:

// mongo/util/net/compression_utils.cpp
outMsg.setData(buffer, buffer.capacity());   // ❌ 把容量当长度

正确应该是:

outMsg.setData(buffer, uncompressedSize);    // ✔️ zlib 返回的真实长度

BSON 解码阶段

消息层把 8192 字节交给 next 阶段,BSON 迭代器开始扫「field name」。

  • 20 字节是攻击者构造的合法 BSON,很快遇到 \0 结束。
  • 后面 8172 字节是随机内存,只要某处出现 \0BSON 就把它当成「字段结束符」继续往下解析;于是攻击者就能通过返回包里的「字段名」把内存内容带出来。

网络层回包

MongoDB 居然把这段「超长子消息」原样发回给攻击者(因为协议头里长度就是 8192)。攻击者收包后,把「字段名」字段打印出来,从而实现了零认证内存泄露。

这问题之所以这么严重也是因为 MongoDB 各项安全机制缺漏的问题:

  • 无需登录:zlib 解压发生在认证之前。
  • 跨平台:bug 在 C++ 层,和操作系统、存储引擎无关。

总结

服务端误把 buffer.capacity() 当解压后长度写回消息头,于是把未初始化的尾部内存随回复一起发出。

其实看着之后也会发现,这个问题就是开发 “粗心大意” 导致的,但也因为这样子,才导致了 MongoDB 会有如此稳定可轻易复现,并能够大面积使用的高危漏洞。

所以世界就是个草台班子。

PREV
[还债] 高精度加法和高精度乘法
NEXT
[Go] 纯粹的优化神经if-else

评论(0)

发布评论