9.1 认证授权与裸奔实例事故


9.1 认证授权与裸奔实例事故

本节摘要:认证用 SCRAM 机制验证身份,授权用基于角色的权限控制(RBAC)限定操作范围。本节从一次公网裸奔实例被拖库的事故讲认证启用流程、内置角色体系与最小权限实践。

事故档案 20:三万个实例的公网裸奔

安全团队扫描发现某测试实例以默认配置暴露在公网:bindIp: 0.0.0.0、未启用认证、云安全组放行 27017 全网。两周内被扫描器发现,数据库被清空,留下一条勒索留言,要求付比特币恢复数据。没有备份,测试数据全部重建。同一年,全球有数万个 MongoDB 实例经历了同样的剧本。

拖库者甚至不需要漏洞——门开着,锁没装

启用认证的正确顺序

# 1. 无认证模式下启动,先建管理员 mongod --config /etc/mongod.conf # 尚未开 authorization
use admin db.createUser({ user: "rootAdmin", pwd: passwordPrompt(), roles: [ { role: "root", db: "admin" } ] });
# 2. 配置文件开启认证后重启 security: authorization: enabled
// 3. 为应用建最小权限业务账号(不要用管理员连应用) use trade db.createUser({ user: "tradeApp", pwd: passwordPrompt(), roles: [ { role: "readWrite", db: "trade" } ] });

内置角色速查

角色 权限 用途
read / readWrite 单库读写 应用账号
dbAdmin 单库管理(索引、统计) DBA 例行维护
userAdmin 单库用户管理 权限自助
clusterMonitor 集群只读监控 监控探针
backup / restore 备份恢复工具 mongodump 专用
root 全部 唯一管理员,人肉登录

攻击路径与防线对照

攻击路径与防线对照

⚠️ 应用直连 root 账号是最普遍的坏习惯:一次 SQL 注入式的条件注入攻击,就能拿到删库权限。每个应用一个库、一个 readWrite 账号。

事故复盘:被拖库的两周时间线

事后从云厂商的流量记录倒推出了完整时间线。第 0 天,测试实例为"方便联调"临时改成 bindIp 0.0.0.0 并在安全组放行 27017,计划联调结束就收回,但没人记得。第 2 天凌晨,来自三个不同 IP 的扫描器完成端口指纹识别——27017 加上未认证的 mongod 响应特征,在扫描器眼里和路灯一样显眼。第 2 天到第 9 天,有人试探性连接,建了一个名为 warning 的集合留言"联系邮箱恢复数据",此时还没有破坏。第 10 天,第二次访问执行了 listDatabases 加 dropDatabase,全部数据消失,库被替换成一条勒索留言。整个过程中实例没有任何异常告警——因为没有任何监控在盯一个"临时"实例。

// 拖库者视角只需要三条命令,门开着时 show dbs; // 1. 看看有什么 use trade; show collections; db.getSiblingDB("trade").dropDatabase(); // 3. 删库勒索

整改分四步:全部实例清点 bindIp 与安全组,临时放行必须带到期时间;认证当天启用,管理员与应用账号分离;为测试环境也配每日备份——测试数据也是劳动成果;把"新增对外端口"纳入变更审批。四步里最便宜也最有效的是第一条,机器学习扫描器找的就是那一扇没关的门。

自定义角色与权限粒度

内置角色不够细时,用自定义角色把权限压到集合与动作级。典型场景:报表服务只需要读两个集合、连 delete 权限都不该有;数据分析同学需要临时查询,但绝不能触碰用户库。

db.getSiblingDB("admin").createRole({ role: "tradeReportReader", privileges: [ { resource: { db: "trade", collection: "orders" }, actions: [ "find" ] }, { resource: { db: "trade", collection: "users" }, actions: [ "find" ] } ], roles: [] }); db.getSiblingDB("trade").createUser({ user: "reporter", pwd: passwordPrompt(), roles: [ "tradeReportReader" ] }); // 验证:reporter 尝试写入应被拒绝

权限评审的口径与第 8 章的事务口径同构:说不出"这个账号为什么需要这个动作",权限就不该给。账号每季度过一遍 connectionStatus 与用户清单,离职与下线服务的账号当天回收——拖库事故里最贵的从来不是攻击者,是忘了收回的钥匙。

口令策略的现实版

认证的实际强度落在口令管理上。三条现实版策略:第一,所有账号口令走密码管理工具生成,长度二十起步,SCRAM 对线下爆破的抵抗能力足够,弱在人对口令的复用;第二,应用账号的口令进配置中心并每季度轮换,轮换动作写成脚本、先双写新旧口令观察一天再废弃旧的,避免轮换变事故;第三,人肉登录一律走跳板机并记录会话,root 账号的每次登录都该留痕。口令策略不需要多复杂,需要的是真的被执行——拖库事故的复盘里,攻击者用的从来不是破译,是复用与撞库。

// 口令轮换的零中断做法:新建新账号 → 观察流量 → 删旧账号 db.getSiblingDB("trade").createUser({ user: "tradeApp2", pwd: passwordPrompt(), roles: [ { role: "readWrite", db: "trade" } ] }); db.getSiblingDB("trade").dropUser("tradeApp"); // 确认新账号承接全部流量后再执行

还有一条容易被忽略的防线:authenticationRestrictions 可以把账号钉死在指定来源 IP 上,即使口令泄露,来自其他地址的连接也会被直接拒绝。对应用账号与 root 账号都值得配上。

本节要点回顾

  • 先建管理员再开认证,顺序反了会把自己锁在门外;
  • 应用账号最小权限,readWrite 单库起步;
  • 27017 不对公网,安全组与 bindIp 双保险;
  • root 只给人,不给程序。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U