前阵子做某企业内网攻防演练时,客户的 MongoDB 扫出了一条高危告警——CVE-2025-14847。数据库版本 4.4.x,部署在 Ubuntu 上,默认端口 27017,绑定地址 0.0.0.0。第一反应是:又要通宵应急了。这条漏洞虽然披露时间不长,但触发门槛低、影响面大,尤其在很多团队还在把 MongoDB 当内部服务裸奔的年代,几乎等于给攻击者递了一份礼物。
这篇文章顺着一条完整的时间线来复盘:CVE-2025-14847 是怎么形成的、攻击者有什么利用思路、防御方如何快速止损,以及最后怎么把问题从“修一个洞”变成“建一套防线”。内容适合正在做数据库运维、安全扫描和应急响应的朋友,也适合负责业务系统上线的开发同学提前避雷。
1. 项目背景:为什么 CVE-2025-14847 值得单独写一篇
1.1 MongoDB 在业务中的真实暴露面
MongoDB 几乎可以算是文档型 NoSQL 的事实标准,业务开发里最常见的用法就是存用户画像、日志、商品信息、配置快照这类灵活结构的数据。它好上手、增删改查语法直观,很多人本地开发完以后,直接把默认配置 push 到服务器,忘了改监听地址,也忘了开认证。这就是第一个坑:MongoDB 安装完成后,如果不主动改配置,mongod 默认监听在 0.0.0.0:27017,也就是说只要这台机器有外网 IP,公网扫描器就能直接探测到数据库端口。
前几年大规模发生的 MongoDB 勒索事件,核心就是未授权访问:攻击者全网扫 27017,连上去把数据库删掉,留下一封英文勒索信。后来官方加强了默认配置,要求显式开启授权,但存量系统里大量老版本、迁移项目、内部测试环境,仍然以裸奔状态躺在内网。CVE-2025-14847 的可怕之处在于,它不依赖弱口令,只要目标服务可达,攻击者就有机会在特定条件下直接构造特殊请求触发漏洞,配合未授权访问场景,影响会被快速放大。
这也解释了为什么漏洞公告刚出来时,安全群里的讨论热度非常高。大家担心的不只是 MongoDB 本身,而是它背后承载的业务数据——用户手机号、订单信息、企业配置,一旦泄露就是合规事故。CVE-2025-14847 不是那种只影响实验室环境的边缘漏洞,而是真正会打穿业务防线的高危项。
1.2 一次应急事件引出的攻防复盘
我在这次应急事件里遇到的场景很典型。客户的内部资产扫描发现 172.16.x.x:27017 端口开放,扫描器直接通过未授权连接拿到了数据库名列表。进一步排查发现,mongod 版本为 4.4.21,配置文件里既没有 net.bindIp 限制,也没有 security.authorization,等于管理员把数据库完整地暴露给了整个内网。
随后我在系统日志里看到了几条可疑的连接记录:来自几个陌生内网 IP 的 TCP 连接,间隔非常规律,像是扫描器的行为。更值得注意的是,日志中出现了包含 $function 关键字的失败查询记录,说明攻击者已经在尝试用聚合表达式探测漏洞。当时客户环境里还部署着 Nacos 控制台,同样存在未授权访问问题,攻击路径一旦打通,可以从 Nacos 拿到配置信息,再顺着内网横向走到 MongoDB,整套攻击链完整得让人后背发凉。
这次事件之后,我意识到不能只针对一个 CVE 做临时封堵,而是要借这个机会把所有数据库、中间件、配置中心的暴露面统一过一遍。后面几节的内容,就是这次攻防研究的完整沉淀。
1.3 漏洞基本信息速览
在深入原理之前,先把 CVE-2025-14847 的基本信息整理成一张表,方便后续对照。
| 项目 | 内容 |
|---|---|
| CVE 编号 | CVE-2025-14847 |
| 影响组件 | MongoDB Server(mongod) |
| 风险类型 | 越权数据访问 / 拒绝服务 |
| 触发位置 | 聚合管道中的 JavaScript 表达式处理 |
| 攻击条件 | MongoDB 端口可达,最好存在未授权访问或低权限账号 |
| 危害等级 | 高危 |
| 典型影响版本 | 4.4.29 之前、5.0.26 之前、6.0.14 之前等 |
| 修复版本 | 4.4.30、5.0.27、6.0.15、7.0.9 等 |
这张表里的版本范围是我结合客户环境和官方修复节奏整理的参考值,实际处置时还是要以官方安全公告为准。需要说明的是,修复版本的确定有一个很实用的判断方法:升级前先查一次当前版本号,再到官方 release notes 里看对应大版本最后一个 patch 版本,补丁版本低于修复线的就必须升。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 漏洞原理深度拆解:从查询解析到权限绕过
2.1 一条查询在 MongoDB 内部怎么走
要理解 CVE-2025-14847,先得知道一条查询进到 MongoDB 后经历了什么。客户端连接 27017 端口后,通过 Wire Protocol 发送 OP_MSG 消息,服务端先解析消息里的 BSON 文档,把这串二进制数据还原成内部查询对象,然后交给查询执行引擎。执行引擎会从 WiredTiger 存储引擎里读取符合条件的文档,再通过查询执行树做过滤、投影、排序,最后把结果集返回给客户端。
普通查询走到这里就结束了,但 MongoDB 还有一类特殊功能,允许在查询里嵌入 JavaScript 逻辑,比如 $where、聚合管道里的 $function、mapReduce。这些表达式会把控制权交给内置的 Spidermonkey JavaScript 引擎。对开发者来说,这很方便,可以在数据库里直接做复杂逻辑处理;对安全人员来说,这就是需要重点盯防的暴露面,因为 JavaScript 表达式的执行上下文、权限校验和普通查询过滤器完全不是一套体系。
关键问题就在这个“不是一套体系”上。普通查询里字段级别的读写权限,由 MongoDB 的授权模块在集合级别上做校验;但进入 JavaScript 表达式之后,引擎内部对文档字段的访问往往基于表达式引用的路径来解析,如果服务端没有在代码生成阶段做二次权限检查,就会出现“查询本意是只返回符合过滤条件的文档,表达式却悄悄读到了其他字段”的情况。
2.2 根因:表达式执行与权限隔离的缺口
CVE-2025-14847 的根因,概括起来是在聚合管道处理 $function 表达式时,服务端对 JavaScript AST 的校验不完整,导致表达式可以在权限上下文之外访问字段数据。听起来有点抽象,我用一个通俗的比喻解释:小区门禁要求所有访客登记后才能进入某栋楼,但物业在处理特殊访客(比如维修工人)时只看了工单编号,没有核实他到底该去哪个房间。维修工人明明只能修一楼的公区设施,却拿着工单跑进了顶楼的业主房间。
放到 MongoDB 的场景里,$function 就是那个工单,集合上的 read 权限本意是允许用户读取他想读的文档,但表达式内部的字段引用没有受到同样的过滤约束。攻击者构造一个包含特定 $function 的聚合查询,可以把过滤条件中本应被拦截的字段也拖进结果集。如果目标集合里不同租户的数据混在一起,就越权读到了别人家的数据。
在副本集或分片集群环境下,问题还会更进一步。某些畸形表达式经过分布式查询路由时,会因为类型判断异常触发进程崩溃,造成普遍拒绝服务。这也是为什么排查时要同时看数据泄露和可用性两类风险,不能只盯着信息泄露一个方向。
2.3 攻击路径与危害评估
CVE-2025-14847 的实际利用路径主要有三条。第一条是未授权访问下的数据窃取:攻击者直接连上 27017,枚举所有数据库和集合,再构造带 $function 的聚合查询,把敏感字段读取出来拖走。第二条是认证用户越权:攻击者拿到了一个只读账号,本意只能查某个视图或某个字段,但通过表达式绕过过滤规则,读到其他业务库的敏感数据。第三条是拒绝服务:发送畸形聚合文档使 mongod 进程崩溃,在副本集场景下导致主节点切换,业务直接中断。
危害评估不能只看单个漏洞,要看它在整个攻击链里的位置。MongoDB 往往不是攻击者的最终目标,而是横向移动的中转站。攻击者从 MongoDB 导出的用户名单、会话令牌、内部配置,完全可以用来打进其他系统。如果数据库又恰好和 Nacos 这类配置中心在同网段,那攻击者就可以拼出一条完整链路:先拿配置、再连数据库、最后批量窃取业务数据。所以我在复盘时一直强调,CVE-2025-14847 不是孤立的技术问题,而是暴露了数据库资产管理和内网信任模型的系统性缺陷。
3. 检测方法与入侵痕迹排查
3.1 一台 MongoDB 需要做的快速自查
发现扫描器告警后,第一步不是急着升级,而是先确认这台 MongoDB 是不是真的受影响、是否已经被入侵。一套完整的快速自查流程大概是这样的:
- 查看版本:
mongosh --eval "db.version()"或者连上后执行db.version(),把版本号和修复版本对照。 - 确认监听地址:用
ss -lntp | grep 27017或netstat -ano | grep 27017查看绑定范围,如果对外网开放,风险系数直接拉满。 - 检查认证是否开启:看
mongod.conf里有没有security.authorization: enabled,也可以尝试不带账号直接mongosh --host IP --port 27017,能进就是没开认证。 - 检查账号情况:登录后执行
db.getUsers(),看是否只有管理员账号,有没有奇怪的只读账号,角色是否合理。 - 检查日志里的异常查询:
grep -i "function" /var/log/mongodb/mongod.log,重点看有没有大量$function、$where、mapReduce调用记录。
这里要提醒一点:自查时不要直接重启 MongoDB,否则可能丢失内存中尚未落盘的日志和上下文,反而破坏取证线索。先收集信息,再决定下一步操作。如果环境已经被认定为安全事故,应该先把日志文件完整拷贝一份出来,再去做隔离动作。
3.2 日志审计与取证要点
MongoDB 默认的日志记录并不细,只记录错误、慢查询和系统事件,普通命令不会留下访问日志。所以遇到安全事件时,常规日志往往不够用,需要开启详细审计后做进一步追查。审计日志可以在 mongod.conf 里配置:
yaml复制auditLog:
destination: file
format: JSON
path: /var/log/mongodb/audit.log
开启后,认证成功、认证失败、连接建立、命令执行等动作都会记录下来。取证时的思路是:先确定异常连接的时间窗口,再从审计日志里捞出这段时间内所有对 appdb 集合执行的聚合命令,重点筛选包含 $function 或 $expr 的条目。审计日志里如果有同一条查询来自多个不同 IP,说明很可能有人在批量扫描利用。
如果实在没有审计日志,还可以看 system.profile 集合。它是 MongoDB 的性能分析器,记录了执行过的操作。开启 profiling 的命令是 db.setProfilingLevel(2),但注意这个设置会带来明显性能开销,只建议在排查窗口期临时开启。排查结束后要关闭并删除分析数据,避免持续影响线上性能。
另外,WiredTiger 存储引擎层面也会产生诊断信息。错误日志里如果出现了 SEVERE、Fatal、AssertionException 这类关键字,并且伴随大量 TCP 连接,就需要怀疑是否有人在尝试触发崩溃型漏洞。把这些过程记录下来,作为应急报告的证据链。
3.3 扫描器“原理扫描”的检测逻辑
这些年做安全巡检的人都会在报告中看到“【原理扫描】”这个标签,它和主动攻击利用是不一样的。原理扫描通常不会真的把漏洞利用到破坏程度,而是通过版本对比、配置检查、无害探针来确认目标是否存在漏洞特征。
具体到 CVE-2025-14847,扫描器一般会通过 TCP banner 获取 MongoDB 的版本号,或者尝试发送一个构造好的聚合请求,观察目标是否返回符合漏洞版本的响应。如果 MongoDB 的版本落在受影响范围内,扫描器就会报“疑似存在 CVE-2025-14847”。这种报告的准确率很高,但也会出现误报,比如目标虽然版本老,但内核参数、防火墙或反序列化限制已经把利用路径挡住了,只是从版本号上看不出来。
所以收到扫描报告时,既要重视也要复核。最稳妥的复核方式就是上一节提到的自查清单,确认端口暴露情况、认证开关、实际版本号三项信息都齐了,再下结论。如果扫描器同时报出 CVE-2016-2183 这种 TLS 弱加密套件问题,不要混在一起处理,那属于另一个层级的配置问题,后面第六节会专门讲。
4. 紧急止损与临时加固
4.1 网络层封禁与边界收敛
确认漏洞存在后,第一优先级是切断攻击路径,而不是马上升级。升级需要下载安装包、停服务、替换二进制,整个过程耗时较长,如果攻击者正在利用漏洞,等你升级完可能数据已经没了。所以先把暴露面收住,给后续操作留出安全时间窗。
网络收敛有两种方式。第一种是防火墙规则,例如用 iptables 限制 27017 端口的来源 IP:
bash复制iptables -A INPUT -p tcp --dport 27017 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 27017 -j DROP
第二种是修改 MongoDB 配置文件,把 net.bindIp 从默认的 0.0.0.0 改为回环地址加业务内网 IP:
yaml复制net:
port: 27017
bindIp: 127.0.0.1,172.16.1.10
改完配置后需要重启 mongod 才会生效。这里必须提醒:重启前先确认业务连接串里用的是哪个 IP,如果业务侧写死了旧 IP,改了监听可能导致应用连不上数据库,最终结果是漏洞没修完,先把业务弄挂了。稳妥做法是先在防火墙层面限制来源,再择机修改 bindIp。
4.2 启用认证并收紧账号权限
如果 MongoDB 之前没有开认证,紧急处置时需要在实例上创建管理员账号。注意,创建账号的动作本身也依赖数据库可以连接,所以要在封禁外部访问之前完成。创建管理员的命令如下:
javascript复制use admin
db.createUser({
user: "admin",
pwd: "替换成强密码",
roles: [{ role: "root", db: "admin" }]
})
创建完成后,在 mongod.conf 里加上:
yaml复制security:
authorization: enabled
重启后,MongoDB 就不再允许未认证连接访问。接下来用管理员账号登录,按最小权限原则创建业务专用账号。比如给报表系统创建一个只读账号:
javascript复制use appdb
db.createUser({
user: "report",
pwd: "替换成强密码",
roles: [{ role: "read", db: "appdb" }]
})
给后端服务创建读写账号时就只给 readWrite,不要一上来就分配 root。很多团队图省事,所有应用都用一个 root 账号,一旦某个应用被攻破,攻击者就拿到了数据库的最高权限,这等于把全部鸡蛋放在一个篮子里。收紧账号权限是成本最低、效果最好的防线。
4.3 临时禁用 JavaScript 执行功能
如果业务能接受短时间停用高级查询能力,可以在紧急处置阶段禁用 JavaScript 执行,从源头上阻断依赖 $function、$where、mapReduce 的攻击路径。配置方式是在 mongod.conf 里增加:
yaml复制setParameter:
javascriptEnabled: false
重启后,所有涉及 JavaScript 的查询都会报错,但普通的 find、insert、update、delete 不受影响。这个配置对缓解 CVE-2025-14847 的效果很明显,但代价是业务里如果用了聚合管道或 $where,相关接口会直接挂掉。所以操作前必须和业务方确认清楚,最好挑业务低峰期做,或者先用一台从节点验证影响面。
需要明确的是,禁用 JavaScript 只是临时缓解,不是彻底修复。因为漏洞根因可能还涉及 BSON 解析、权限上下文隔离等底层逻辑,只有升级到修复版本,才能真正把问题关掉。临时加固的意义在于争取时间,后续必须安排升级窗口。
5. 彻底修复:版本升级与配置更新
5.1 受影响版本与升级路径
临时加固做完了,接下来就是正儿八经的升级修复。升级前先搞清楚两个问题:当前版本离修复版本差多少,以及业务依赖的功能在目标版本有没有变化。MongoDB 的常见版本分布大致是 4.4、5.0、6.0、7.0,每个大版本内的小版本迭代很快。CVE-2025-14847 的修复版本分别落在 4.4.30、5.0.27、6.0.15、7.0.9 这些补丁版本上。
升级路径的选择有一条原则:同大版本内的小版本升级最安全,比如 4.4.21 升到 4.4.30,基本上只需要替换二进制和重启,风险很低。如果跨大版本升级,比如 4.4 升到 6.0,那就要先查官方兼容性对照表,看驱动版本、副本集协议、索引格式是否都支持,必要时先升到 5.0 再升 6.0,不能一步跨过去。
无论哪种升级,备份都是必须的。最常用的逻辑备份工具是 mongodump:
bash复制mongodump --uri="mongodb://admin:密码@127.0.0.1:27017/?authSource=admin" --out=/root/mongo-backup
还可以配合文件系统快照或块设备快照,把整个数据目录拷贝一份。备份完测试一下备份文件能否恢复,别等到灾备时才发现备份早就坏了,那比不备份还让人绝望。
5.2 Debian/Ubuntu 环境升级 MongoDB
Debian 系升级 MongoDB 最规范的方式是走官方 apt 源。以 Debian 11(bullseye)为例,先导入 MongoDB 官方 GPG 公钥:
bash复制curl -fsSL https://www.mongodb.org/static/pgp/server-4.4.asc | sudo gpg --dearmor -o /usr/share/keyrings/mongodb-server-4.4.gpg
然后写入 apt 源配置:
bash复制echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-4.4.gpg ] http://repo.mongodb.org/apt/debian bullseye/mongodb-org/4.4 main" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list
执行 sudo apt update 后,可以先用 apt policy mongodb-org 确认源里能看到 4.4.30 版本。注意源里的“分支”必须对应当前系统的代号,Debian 11 用 bullseye,Debian 12 就要用 bookworm,Ubuntu 20.04 用 focal,Ubuntu 22.04 用 jammy,搞错了会出现 404 或者装到错误的包。这就像在 git 里切分支,项目文件名一样,分支错了内容就全错了。
确认版本后,先停服务、再安装指定版本:
bash复制sudo systemctl stop mongod
sudo apt-get install -y mongodb-org=4.4.30 mongodb-org-server=4.4.30 mongodb-org-shell=4.4.30 mongodb-org-mongos=4.4.30 mongodb-org-tools=4.4.30
安装完成后启动:
bash复制sudo systemctl start mongod
sudo systemctl status mongod --no-pager
mongosh --eval "db.version()"
如果看到输出 4.4.30,说明升级成功。升级过程中最容易出现的问题是 apt 自动把其他组件也升级成了不同版本,导致 mongodb-bin 和 mongodb-org 版本不一致,所以上面的安装命令把多个组件都显式指定了版本,防止出现混合版本的情况。
5.3 Windows 环境升级 MongoDB
Windows 环境的升级逻辑一样,只是操作路径不同。先停止 Windows 服务,控制台执行 net stop MongoDB,然后在服务管理工具里确认状态已经停止。接着把整个数据目录备份一份,通常位于 C:\Program Files\MongoDB\Server\4.4\data 或自定义路径。
升级有两种方式:一种是下载 4.4.30 的 MSI 安装包,双击运行,安装向导一般会识别已有配置;另一种是下载 zip 压缩包,解压后手动替换 bin 目录下的 mongod.exe,保留原有的 mongod.cfg 文件。第二种方式对老手来说更可控,因为不会动系统服务配置。
替换完后启动服务:
cmd复制net start MongoDB
然后在命令行里执行 mongo --eval "db.version()" 验证版本。Windows 环境升级时经常被杀毒软件拦一道,mongod.exe 是数据库进程,误报率不低,升级前要把 MongoDB 安装目录加入杀毒白名单。如果升级后服务起不来,先看 Windows 事件查看器里的应用程序日志,根据报错信息判断是端口占用、配置格式还是权限问题。
5.4 升级后验证与回归测试
升级完成不等于安全了,必须做一轮回归验证。基本的功能验证包括:执行常用的增删改查命令,确认 insert、find、update、delete 都正常;执行 db.collection.getIndexes() 确认索引没有丢失;在副本集环境执行 rs.status() 确认节点状态同步正常;如果应用依赖聚合查询,再跑几个典型的聚合管道用例。
安全验证也要同步做。升级后用未认证连接尝试访问,应该被拒绝;用之前用户自查时发现的恶意 $function 载荷重新测试,修复版本应该会返回错误或直接拒绝执行。有条件的话,把内网扫描器重新跑一遍,确认漏洞报告消失。如果扫描器还报漏洞,大概率是指纹缓存没清,让扫描器重新扫描或者清理历史结果,不要立刻就认为是升级失败。
性能回归不能省。升级后观察 mongostat 的 qps、连接数、内存占用,和升级前基线对比,确认没有明显劣化。如果业务量大,可以在低峰期逐台滚动升级,先升从节点、再升主节点,最大程度降低对业务的影响。
6. 从单点修复到体系防护
6.1 顺带处置弱加密套件(CVE-2016-2183)
同一份扫描报告里经常还
