安全指南
用 AI 写的代码泄露了 API key,被人盗刷——真正的祸根是放着不管的 CVSS 10.0
用 AI 写好的应用刚一上线,API key 就被偷走,跑出一堆莫名其妙的批量任务把账单刷爆——这是个人开发中真实且常见的事故模式。从绕远路找『凶手』,到最终锁定真正的祸根=公开已数月、却一直没打补丁的 CVSS 10.0 RCE,以及如何用机器来杜绝复发,本文隐去攻击手法,只从防御视角梳理。
本文把一个不熟悉安全的个人开发者常踩的事故,隐去所有专有名词后整理成案例,化为教训。文中不会写出复现攻击的步骤。目的只有一个:「不再重蹈覆辙的防御」。
- 类型
- API key 泄露 / 盗刷
- 严重度
- Critical(被利用的是公开已久的 CVSS 10.0)
- 真因
- Web 框架的认证前 RCE 数月未打补丁、放任不管
- 泄露范围
- 环境变量里的所有 secret(整串 key 一锅端)
- 执行范围
- 局限在非特权用户的容器内(宿主机 root 无恙)
- 根治措施
- ①升级到修复版 ②全部 key 轮换 ③CVE 机器监控
「止住了账单」≠「处理完成」
止住账单只是止血。封堵泄露途径是另一台手术。两件都做完,才算处理完成。
发生了什么(时间线)
Day 0 — 上线·运营
借助 AI 写好的应用上线并在运营。某一天 — 账单暴涨
云 AI 的账单突然飙升。跑的是本不该用的模型,跑的是毫无印象的大量处理。这不是我自己干的。排查 — 第三方盗用
用「自己应用失控」根本说不通。查明是第三方拿着偷来的 key 在跑。追踪 — 真正的调查开始
「那么 key 是从哪里漏的?」——真正的调查从这里才开始。
为什么一开始没能察觉(调查走的弯路)
不怕丢脸,我按犯错的先后顺序写下来。因为这本身就是教训。
先入为主地认定了凶手
→ 无论是「自己的锅」还是「别人的锅」,在下定论前先看数据。
grep 干净就差点放心了
掩盖症状就以为『修好了』
grep 干净也别放心
即便 key 不在文件里,正在运行的进程的环境变量也可能被漏洞抽走。泄露不只在文件里,也在运行时(RCE、HTTP 请求头)。详见 什么是 RCE。
真正的原因:放任不管、公开已久的 CVSS 10.0
把过去访问日志里残留的可疑特征拿去威胁情报里比对,一下就查出了真身。所用的 Web 框架的某个特定版本区间,存在一个公开已久的认证前 RCE(CVSS 10.0),而且早已被实际利用。而我们却放着它,数月没打补丁,一直跑着。
- 这不是被动的 bug,而是攻击者在服务器上执行任意代码、把环境变量带走的主动攻击。
- 不幸中的万幸是,执行范围局限在非特权用户的容器内。入侵调查中,常驻后门、挖矿程序、C2 一律没有检出,能确认的实际损害是环境变量被窃取。
- 不过由于内部 DB 是可达的,所以按DB 内容也已泄露的前提来行动。
教训:奇怪的行为,先怀疑已知 CVE。别一口咬定是自己的 bug。
连数量也数错了——要按「实际运行版本」来判定
横向排查时报告说「另外还有 8 个有漏洞的依赖」,这也错了。因为我是按 package.json 里的下限版本(^ 这种 caret 范围)来数的。真正危险的,只有被固定 pin 死、落在后面的2 个而已。
想确认自己的『实际运行版本』
要看 lock 文件、或正在运行的容器里实际解析出来的版本。
# npm 系:确认实际装进去的版本
npm ls next react react-dom
# 想在运行中的容器里确认时
docker exec <container> npm ls <package>真实情况不是 package.json 里的 ^ 写法,而是这里打印出来的数字。
初期处置做了什么(可复现的步骤)
立即吊销被滥用的 key
把框架升级到修复版
轮换所有 secret
入侵调查
账户侧的防御
复发防范的正解:交给机器盯着
这次事故,归根结底不过是「一个公开已久的 CVSS 10.0,被人为疏忽、放任不管」而已。靠人工巡查必然会漏。交给机器盯着,就能从结构上杜绝。
今天就能做的依赖扫描
免费就能起步。只需在 CI 里加一个步骤。
# 用 OSV 扫描器(Google)检查 lock 文件
osv-scanner --lockfile=pnpm-lock.yaml
# GitHub 的话就启用 Dependabot(仓库设置 → Security)本站自己也照着这篇文章写下的教训,把自身的依赖纳入了 CVE 监控的对象(劝别人做的对策,我们自己也在身体力行)。
教训小结
容易犯的错
- 以「止住了账单」就当作处理完成
- grep 干净就放心
- 在代理层掩盖症状,就以为修好了
- 按
package.json的下限来数漏洞 - 只轮换确认被滥用的那 1 个
正确的防御
- 把止血和「封堵泄露途径」当作两件事,都要做
- 也怀疑运行时泄露(RCE、请求头)
- 升级根源(框架)
- 按「实际运行版本」来判定
- 一旦泄露就把整个 env 全部更换
一句话概括——失控账单只是冰山一角。真因是放任不管的 CVSS 10.0 RCE。而真相不是一击命中得出的,是一次次犯错、碰撞、磨削之后才浮现出来的。
接着读
- 术语:什么是 RCE / 什么是 CVE / 什么是 CVSS
- 对策:安全运营 Next.js(跟进 CVE)
- 事故:Laravel 的 .env 被全部公开的故事
FAQ
QAPI key 泄露了,只把确认被滥用的那一个吊销就行吗?
不行。如果泄露途径是运行时(RCE 或请求头泄露),就要按『环境变量里的所有 secret 已经被一次性全部泄露』的前提来处理,把它们全部轮换才安全。已确认被滥用的 key 只是冰山一角。
Q把代码和 git 都 grep 一遍,没找到 key 就安全了吗?
不能这么断言。即便 key 不在文件里,框架的漏洞也可能从正在运行的进程中把环境变量抽走。泄露不只发生在文件里,也发生在运行时。
Q最可靠的复发防范办法是什么?
用机器来监控依赖的 CVE。这次的真因就在于『一个公开已久的 CVSS 10.0 被人为疏忽,放任了数月』,只要让 Dependabot 或 osv-scanner 帮你盯着,就能从结构上杜绝。