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字节是随机内存,只要某处出现\0,BSON就把它当成「字段结束符」继续往下解析;于是攻击者就能通过返回包里的「字段名」把内存内容带出来。
网络层回包
MongoDB 居然把这段「超长子消息」原样发回给攻击者(因为协议头里长度就是 8192)。攻击者收包后,把「字段名」字段打印出来,从而实现了零认证内存泄露。
这问题之所以这么严重也是因为 MongoDB 各项安全机制缺漏的问题:
- 无需登录:zlib 解压发生在认证之前。
- 跨平台:bug 在 C++ 层,和操作系统、存储引擎无关。
总结
服务端误把 buffer.capacity() 当解压后长度写回消息头,于是把未初始化的尾部内存随回复一起发出。
其实看着之后也会发现,这个问题就是开发 “粗心大意” 导致的,但也因为这样子,才导致了 MongoDB 会有如此稳定可轻易复现,并能够大面积使用的高危漏洞。
所以世界就是个草台班子。
RoLingG | 博客
评论(0)