供应链攻击时代:为什么 npm、PyPI、Docker 成了新战场
最后更新:2026-09-16
本文是产业观察,不带系列编号。过去安全防护的重心是"守住自己写的代码和服务器边界",但今天一个应用动辄引入数百到数千个直接与间接(传递)依赖,攻击者发现:与其硬攻一个防守严密的目标,不如攻陷它信任的某个上游组件——一次得手,顺流而下感染成千上万用户。这就是软件供应链攻击成为新主战场的原因。安全开发流程见DevSecOps,默认不信任的思路见零信任,开源靠谁养活见开源是怎么赚钱的。本文把攻击手法、标志性事件和可执行的防御分层讲清楚,xz 事件暴露的开源维护者困境也一并讨论。
先说结论
- 攻击面从"你写的代码"转移到"你信任的依赖",而信任被默认得太廉价。 现代软件是组装出来的:一个前端项目的 node_modules、一个 Python 服务的虚拟环境、一个 Docker 镜像里层层叠叠的基础层,传递依赖数量轻松上千。你审查了自己的几百行代码,却几乎不可能逐行审完所有间接依赖。攻击者正是利用这种"隐式信任"。
- 供应链攻击的性价比极高:一次投毒,杠杆式扩散。 攻陷一个被广泛使用的库、一个维护者账户、一个 CI 系统或一个基础镜像,恶意代码会随正常的安装、构建、更新进入所有下游。SolarWinds、xz-utils 等事件证明,攻击者可以极其耐心地"养鱼"——长期贡献、建立信誉,再在合适时机植入难以察觉的后门。
- 攻击手法已成体系:拼写抢注(typosquatting)、依赖混淆(dependency confusion)、维护者账号劫持/钓鱼、植入带恶意 install 脚本的版本、篡改 CI/CD 凭证与构建产物、毒化容器镜像和基础镜像、针对编译器/更新通道的"上游污染"。门槛在下降、自动化投毒工具泛滥。
- xz 事件是开源"公地悲剧"的安全切片:一个被无数系统间接依赖的关键压缩库,长期主要靠一两名志愿者无偿维护,攻击者以"帮忙分担工作"的方式接近维护权,再尝试植入后门。它暴露了"数字社会的关键基础设施由疲惫的志愿者用业余时间支撑、缺乏可持续资金和审查"的系统性风险(与开源商业模式直接相关)。
- 防御必须分层、默认不信任,且要纳入工程流程而非靠自觉。 没有银弹,要靠组合拳:SBOM 让你知道自己用了什么、依赖最小化与锁定版本、制品签名与来源证明(provenance/attestation)、私有仓库与命名空间防护、CI/CD 最小权限与隔离、镜像扫描、零信任网络,以及把这些嵌进 DevSecOps 自动化门禁。
一句话:当软件主要由"信任的第三方零件"组装而成,最薄弱的环节往往不在你的代码里,而在你从未见过的某个传递依赖或某个疲惫的维护者身上;供应链攻击用一次上游得手换取下游规模性失陷,因此防御的核心是把隐式信任变成可验证、可追溯、最小化、默认怀疑的工程体系,并正视开源关键基础设施长期缺乏供养的治理问题。
一、为什么攻击面转移了:依赖爆炸与隐式信任

几个结构性事实放大了风险:
- 传递依赖数量巨大:
npm install后 node_modules 里数百上千包是常态,绝大多数是你没有主动选择、也没有审查过的"依赖的依赖的依赖"。 - 安装即执行:很多包管理器在安装阶段就会运行安装脚本(postinstall 等),恶意代码不必等你 import,安装时就能执行。
- 更新自动化:CI 里
latest/宽松版本范围、自动合并的依赖更新机器人,可能在无人审阅时把恶意版本带进构建。 - 信任是传递的:你信任 A,A 信任 B,B 的维护者账号被钓鱼,攻击就穿透了你——而你只和 A 有关系。
二、主要攻击手法(按入口分类)
| 手法 | 怎么做 | 典型后果 |
|---|---|---|
| 拼写抢注 typosquatting | 注册与热门包近似的名字(lodahs、reqeusts),等手滑者安装 | 安装脚本窃凭证/挖矿/后门 |
| 依赖混淆 | 在公共仓库上传与企业内部私有包同名的"更高版本",骗构建器拉取公开恶意包 | 内网信息泄露、CI 凭证失窃 |
| 维护者劫持/钓鱼 | 钓鱼拿下维护者 npm/PyPI/邮箱账号与 2FA,发布恶意版本 | 向正版包的全部用户推送后门 |
| 恶意版本/植入 | 取得提交权后植入难察觉的后门,再发版 | 长期潜伏、定向触发 |
| install 脚本滥用 | 在安装钩子里执行任意命令 | 无需运行程序即中招 |
| CI/CD 投毒 | 窃取流水线 Token、篡改构建步骤/缓存/产物 | 污染所有经此构建的正式制品 |
| 容器/基础镜像投毒 | 毒化 Docker Hub 公共镜像或基础层,植入挖矿/后门 | 凡拉取该镜像者失陷、横向移动 |
| 工具链/更新通道 | 攻击编译器、更新服务器、签名密钥(SolarWinds 式) | 可信签名的"官方"更新带毒 |
值得注意的是趋势:早期多为广撒网的抢注和挖矿脚本(低水平、自动化),近年出现 APT 级别的定向、长期潜伏攻击——攻击者愿意花数月甚至数年贡献代码、建立信任,再精准投毒。SolarWinds 如此,xz-utils 也险些如此。
三、xz-utils 事件:开源公地悲剧的安全切片
2024 年曝光的 xz-utils(几乎所有主流 Linux 发行版间接依赖的 XZ/YZ 压缩库)事件,是理解供应链脆弱性的最佳案例:
- 关键基础设施,维护者极少:这个被海量系统依赖的基础库,长期主要靠一两名志愿者用业余时间维护,维护者同时背负大量功能请求和"嫌更新慢"的抱怨,已接近倦怠。
- 攻击者用"帮忙"接近权力:一名长期参与者持续提交高质量补丁、分担维护工作、逐步取得信任和提交权限——典型的"社会工程 + 耐心养鱼"。
- 后门藏得很深:后门通过复杂的构建过程注入、仅在特定条件下激活,试图影响 SSH 认证路径,代码被刻意混淆,常规审查很难发现。
- 靠运气和尽职的第三方才被拦下:最终是一名工程师在做性能观察时注意到异常的 CPU 开销,顺藤摸瓜发现,属于"差点没被发现"。
它的产业含义超出单一漏洞:互联网的关键软件地基,有相当一部分由没有充足资金、人手和安全审计的志愿者维护,构成"公地悲剧"——所有人免费享用,少数人承担崩溃风险。 这正是开源是怎么赚钱的里"免费代码不等于免费维护"在安全维度的爆发。修复不只是技术问题,更是资金(企业/基金会资助关键依赖)、治理(多人维护、身份与发布流程)和可持续性问题。
四、防御:把"隐式信任"变成"可验证、最小化"
供应链安全没有单点银弹,要沿软件交付链路做分层防御(defense in depth),并且尽量自动化、嵌进 CI/CD 而不是靠人记得:

关键实践:
- 建立 SBOM(软件物料清单):知道自己到底用了哪些直接和传递依赖、版本与许可证,才能在新漏洞爆发时快速判断是否受影响、定位受影响系统。
- 依赖最小化与版本治理:少引一个包就少一份攻击面;锁定 lockfile、避免
latest漂移;用依赖更新工具但要求审阅与测试;定期移除不用的依赖。 - 命名空间与私有仓库防护:防依赖混淆——内部包名在公共仓库做"占位"或用 scoped 命名,构建时强制内部源优先、禁止回退到公网同名包。
- 制品签名与来源证明(SLSA / sigstore):对构建产物做签名、记录"由哪个可验证的构建过程、从哪个提交产生",部署时验证,防篡改和非法构建。
- 加固 CI/CD:流水线用短期、最小权限凭证(OIDC 而非长期密钥),隔离秘密、限制第三方 Action/插件、审计构建日志——CI 是供应链攻击的高价值目标。
- 容器安全:用精简/ distroless 基础镜像、固定摘要而非浮动 tag、镜像扫描、准入控制只允许签名镜像部署。
- 零信任与最小权限:即便恶意代码混进来,也限制它能访问的凭证、网络和数据(见零信任、网络与系统安全)。
- DevSecOps 左移与门禁:把 SCA(软件成分分析)、密钥泄露扫描、IaC/容器扫描做成 CI 强制门禁(见DevSecOps),并配套事件响应与"依赖应急替换"预案。
- 支持上游:企业通过资助、贡献、采购"软件物料保证"服务,为关键依赖的可持续维护付费,是降低公地悲剧风险的长期解。
五、给团队和个人的清单
工程团队(可立即做):
- 生成并维护 SBOM;统一私有包源与命名空间策略。
- 锁定依赖版本、开启 SCA 与镜像扫描 CI 门禁;清点 install 脚本和第三方 CI 插件。
- CI 改短期凭证 + 最小权限;秘密分级、定期轮换。
- 为关键开源依赖指定内部 owner,跟踪其维护者活跃度与安全公告。
个人开发者:
- 装包前看作者、下载量、维护状态,警惕拼写相近的包和异常的安装脚本。
- 不向构建脚本暴露多余权限和长期 Token;谨慎点自动更新机器人的 PR。
- 理解"引入一个依赖 = 信任一条供应链",在小工具里优先用标准库/零依赖方案。
管理者/合规:
- 把供应链风险纳入安全与合规(SBOM、可追溯、第三方审计),尤其在强监管行业。
- 用"关键依赖资助"把对开源的索取变成可持续投入,降低 xz 式系统性风险。
相关与延伸
一句话总结:软件供应链攻击之所以成为主战场,是因为现代应用由上千个"默认信任、却从未逐行审查"的依赖拼装而成,攻击者攻陷一个上游点就能借正常的安装与构建向下游杠杆式扩散;从 typosquatting 到 SolarWinds、xz,手法从广撒网进化到长期潜伏,而 xz 更暴露了关键开源设施由疲惫志愿者支撑的公地悲剧——唯一可持续的答案,是用 SBOM、最小化、签名溯源、加固 CI、零信任和 DevSecOps 门禁把隐式信任变为可验证信任,同时真正为关键上游的维护买单。