四次事件,一个共同点
过去几年最著名的几起开源供应链事件,技术细节各不相同,但模式惊人地一致:
- event-stream(2018):原作者把维护权转给陌生人,新维护者在依赖里塞进针对特定钱包的窃密代码;
- ua-parser-js(2021):维护者账号被劫持,发布带挖矿与窃密的版本;
- colors(2022):维护者因不满商业使用,主动发布死循环版本,无数项目构建中断;
- xz(2024):攻击者用两年时间扮演热心贡献者,逐步拿到维护权,在构建脚本里埋后门。
共同点是:信任建立在「人」身上,而不是「代码」上。 你锁定了版本号,但没锁定发布者;你审了源码,但没审构建脚本。
攻击者从哪进来
三条路径覆盖了绝大多数案例:
- 维护者账号:钓鱼、密码复用、npm token 泄露。拿到账号就能发新版本,签名和来源看起来都正常。
- 维护权转移:原维护者累了,把包交给「热心人」。event-stream 和 xz 都走了这条路。
- 安装期执行:
postinstall脚本、setup.py、构建脚本。代码在 CI 里跑起来,凭据就在环境变量里。
防御:把「信任」变成「可验证」
没有单一措施能根治,但可以把暴露窗口压到很小:
- 锁文件 + 完整性哈希:
package-lock.json、poetry.lock里的 integrity 字段能发现内容被替换,但防不住「新版本本身就是恶意的」; - 私有代理与白名单:Nexus、Artifactory 这类制品库做缓存与准入,新包不会立刻进入生产;
- 延迟升级:新版本发布后等几天再升,让社区先踩雷;
- 审查安装脚本:CI 里禁用
postinstall(npm ci --ignore-scripts),确实需要的单独白名单; - 最小化 CI 凭据:发布 token 只给发布流水线,构建环境不挂生产密钥;
- 出网限制:构建机默认不能访问外网,依赖只能从代理拉。
被忽略的一环:你自己发布的包
如果你维护着内部包或公开包,同样要防:开启 2FA、使用细粒度 token、开启 provenance 证明(npm 的 --provenance),让下游能验证「这个包确实由这条流水线构建」。
我的判断
供应链安全没法做到「零风险」,因为它本质上是把信任外包给了一群你素未谋面的人。能做的只有两件事:缩小信任面(少依赖、少权限),缩短发现时间(监控、比对、回滚能力)。
xz 那件事留下的教训最值得记住:攻击者不急着得手,他愿意等两年。防守方如果只盯着「今天有没有新 CVE」,永远慢一步。
延伸阅读
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END










暂无评论内容