跳到正文
>_ITDITDWeb 安全平台

安全指南

用 AI 写的代码泄露了 API key,被人盗刷——真正的祸根是放着不管的 CVSS 10.0

用 AI 写好的应用刚一上线,API key 就被偷走,跑出一堆莫名其妙的批量任务把账单刷爆——这是个人开发中真实且常见的事故模式。从绕远路找『凶手』,到最终锁定真正的祸根=公开已数月、却一直没打补丁的 CVSS 10.0 RCE,以及如何用机器来杜绝复发,本文隐去攻击手法,只从防御视角梳理。

发布于 2026-06-07 2 分钟阅读

本文把一个不熟悉安全的个人开发者常踩的事故,隐去所有专有名词后整理成案例,化为教训。文中不会写出复现攻击的步骤。目的只有一个:「不再重蹈覆辙的防御」。

事故概要 — INCIDENT FILE
类型
API key 泄露 / 盗刷
严重度
Critical(被利用的是公开已久的 CVSS 10.0)
真因
Web 框架的认证前 RCE 数月未打补丁、放任不管
泄露范围
环境变量里的所有 secret(整串 key 一锅端)
执行范围
局限在非特权用户的容器内(宿主机 root 无恙)
根治措施
①升级到修复版 ②全部 key 轮换 ③CVE 机器监控
10.0
CVSS / 最高危等级
数月
放任不管的时长
整个 env
按已泄露处理的范围
机器监控
复发防范的正解

「止住了账单」≠「处理完成」

止住账单只是止血。封堵泄露途径是另一台手术。两件都做完,才算处理完成。

发生了什么(时间线)

  1. Day 0 — 上线·运营

    借助 AI 写好的应用上线并在运营。
  2. 某一天 — 账单暴涨

    云 AI 的账单突然飙升。跑的是本不该用的模型,跑的是毫无印象的大量处理。这不是我自己干的。
  3. 排查 — 第三方盗用

    用「自己应用失控」根本说不通。查明是第三方拿着偷来的 key 在跑。
  4. 追踪 — 真正的调查开始

    「那么 key 是从哪里漏的?」——真正的调查从这里才开始。

为什么一开始没能察觉(调查走的弯路)

不怕丢脸,我按犯错的先后顺序写下来。因为这本身就是教训。

1

先入为主地认定了凶手

起初草率地以为「是自己代码里的兜底逻辑失控了」。可只要看数据(实际跑的处理内容),那绝不可能是我自己干的。
→ 无论是「自己的锅」还是「别人的锅」,在下定论前先看数据。
2

grep 干净就差点放心了

把全部代码、git 历史、关联仓库都搜了一遍 → 哪里都没有明文 key。我在这里几乎要以「无从追踪」收手。只盯着文件,漏看了运行时的泄露。
3

掩盖症状就以为『修好了』

在代理层把症状掩盖住,差点就满足了。可掩盖症状,RCE 本身依然活着。不根治,洞就一直开着。

grep 干净也别放心

即便 key 不在文件里,正在运行的进程的环境变量也可能被漏洞抽走。泄露不只在文件里,也在运行时(RCE、HTTP 请求头)。详见 什么是 RCE

真正的原因:放任不管、公开已久的 CVSS 10.0

把过去访问日志里残留的可疑特征拿去威胁情报里比对,一下就查出了真身。所用的 Web 框架的某个特定版本区间,存在一个公开已久的认证前 RCE(CVSS 10.0),而且早已被实际利用。而我们却放着它,数月没打补丁,一直跑着。

  • 这不是被动的 bug,而是攻击者在服务器上执行任意代码、把环境变量带走的主动攻击
  • 不幸中的万幸是,执行范围局限在非特权用户的容器内。入侵调查中,常驻后门、挖矿程序、C2 一律没有检出,能确认的实际损害是环境变量被窃取
  • 不过由于内部 DB 是可达的,所以按DB 内容也已泄露的前提来行动。
看得见的症状:毫无印象的盗刷(冰山一角)
水面:只能看到这里
↑ 偷来的 key 有时间差地被转卖·滥用
连同环境变量把整串 key 窃取(整个 env 泄露)
↑ 攻击者在运行中的服务器上执行任意代码
真因:放任不管、公开已久的 CVSS 10.0(pre-auth RCE)
账单暴涨只是冰山一角。水面下沉着『放任不管、公开已久的 CVSS 10.0』这个真因。

教训:奇怪的行为,先怀疑已知 CVE。别一口咬定是自己的 bug。

连数量也数错了——要按「实际运行版本」来判定

横向排查时报告说「另外还有 8 个有漏洞的依赖」,这也错了。因为我是按 package.json 里的下限版本^ 这种 caret 范围)来数的。真正危险的,只有被固定 pin 死、落在后面的2 个而已。

想确认自己的『实际运行版本』

要看 lock 文件、或正在运行的容器里实际解析出来的版本。

# npm 系:确认实际装进去的版本
npm ls next react react-dom
 
# 想在运行中的容器里确认时
docker exec <container> npm ls <package>

真实情况不是 package.json 里的 ^ 写法,而是这里打印出来的数字。

初期处置做了什么(可复现的步骤)

1

立即吊销被滥用的 key

止血。但这只是处理的一部分。
2

把框架升级到修复版

真正封堵 RCE。升级后,在日志里确认泄露的特征已消失。
3

轮换所有 secret

按已泄露的前提来做。优先顺序从「在任何地方都能被滥用的」开始:对象存储 key → 会话签名 key → 各类 API key → DB 凭据。
4

入侵调查

确认是否有常驻物、不正常的定时任务、对外通信(这次都没有)。
5

账户侧的防御

修改密码并启用 MFA,确认有没有多出陌生的 API key。

复发防范的正解:交给机器盯着

这次事故,归根结底不过是「一个公开已久的 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。而真相不是一击命中得出的,是一次次犯错、碰撞、磨削之后才浮现出来的。

接着读

FAQ

QAPI key 泄露了,只把确认被滥用的那一个吊销就行吗?
A

不行。如果泄露途径是运行时(RCE 或请求头泄露),就要按『环境变量里的所有 secret 已经被一次性全部泄露』的前提来处理,把它们全部轮换才安全。已确认被滥用的 key 只是冰山一角。

Q把代码和 git 都 grep 一遍,没找到 key 就安全了吗?
A

不能这么断言。即便 key 不在文件里,框架的漏洞也可能从正在运行的进程中把环境变量抽走。泄露不只发生在文件里,也发生在运行时。

Q最可靠的复发防范办法是什么?
A

用机器来监控依赖的 CVE。这次的真因就在于『一个公开已久的 CVSS 10.0 被人为疏忽,放任了数月』,只要让 Dependabot 或 osv-scanner 帮你盯着,就能从结构上杜绝。