mongodb审计日志需手动启用,未开启则无越权记录可分析;启用后应配置json格式、过滤accessdenied事件并结合param.command与param.roles研判高危操作,同时注意角色为空、时间精度低及缺少客户端ip等问题。

审计日志格式不统一,mongod 默认不开启审计
MongoDB 的审计日志不是默认启用的,必须手动配置 auditLog 选项并指定输出路径和格式。没开审计,就根本不存在“越权访问记录”可分析——这是最常被忽略的前提。常见错误是直接去查 /var/log/mongodb/mongod.log,结果发现全是启动日志或慢查询,没有用户操作痕迹。
实操建议:
- 启动时加参数:--auditLog.destination=file --auditLog.format=JSON --auditLog.path=/var/log/mongodb/audit.log
- 或在 mongod.conf 中配:auditLog: { destination: "file", format: "JSON", path: "/var/log/mongodb/audit.log" }
- 注意:启用后会轻微影响写入性能,尤其高并发认证场景;JSON 格式比 BSON 更易解析,别选 format: BSON
用 jq 快速过滤 accessDenied 类型事件
审计日志本质是每行一条 JSON 记录,accessDenied 是识别越权的核心事件类型。但不能只搜字符串 “accessDenied”,因为合法拒绝(如权限不足的普通查询)和可疑越权(如非管理员调用 dropDatabase)需结合 param.command 和 param.roles 判断。
实操建议:
- 提取所有拒绝事件:jq 'select(.atype == "accessDenied")' /var/log/mongodb/audit.log
- 进一步筛选高危命令:jq 'select(.atype == "accessDenied" and (.param.command | contains(["dropDatabase", "dropCollection", "createUser", "grantRolesToUser"]))' audit.log
- 注意:.param 结构因 MongoDB 版本略有差异,4.4+ 才稳定有 .param.command;3.6 可能得看 .param.ns 和 .param.operation
roles 字段为空或异常,说明身份上下文丢失
真正危险的越权往往发生在身份冒用或凭证复用场景,比如某个应用账号本该只有 read 权限,却执行了 insert 并被拒绝——这时 .param.roles 应该显示该账号实际拥有的角色。但如果字段为空、为 [] 或出现 "__system",意味着请求绕过了正常认证流程(如直连本地 socket、使用 keyfile 而非 x509 或 SCRAM),或是日志采集时上下文未正确绑定。
实操建议:
- 检查空角色:jq 'select(.atype == "accessDenied" and (.param.roles | length == 0))' audit.log
- 对比成功请求的 .user 和 .param.roles 是否一致,不一致可能表明代理层(如 mongos、API 网关)未透传认证信息
- 注意:启用 auditLog.filter 可减少日志体积,但别过滤掉 authenticate 事件,否则无法关联后续操作的身份来源
时间戳精度低、无客户端 IP,需结合其他日志交叉验证
审计日志里的 localTime 是秒级精度,且默认不记录客户端真实 IP(只记 remote,可能是 mongos 地址或 localhost)。单靠审计日志无法定位具体哪台应用服务器发起了越权请求,也难以还原完整攻击链。
实操建议:
- 在 mongod.conf 中开启详细网络日志:systemLog.verbosity: 1 + net.port 配置下启用 net.bindIpAll: true(仅内网环境)
- 将审计日志与应用层访问日志按时间窗口(±5 秒)对齐,重点看失败前后的 find/update 请求模式
- 注意:MongoDB 本身不记录 HTTP 层信息,如果走的是 MongoDB Realm 或 Atlas App Services,得去对应平台查独立审计流
事情说清了就结束。真正难的不是 grep 出 accessDenied,而是确认那个被拒绝的操作,到底是配置失误、应用 bug,还是有人已经摸到了边界。










