ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

供应链攻击如何绕过来源证明?从Shai-Hulud事件看NPM安全实践

供应链攻击如何绕过来源证明?从Shai-Hulud事件看NPM安全实践 1. 从一次真实的供应链攻击看“来源证明”的局限性如果你负责前端项目、Node.js后端或者任何依赖NPM生态的工程最近可能听过“供应链安全”和“来源证明”这些词。它们听起来像是能彻底解决依赖包被投毒、被劫持的银弹。但现实是就在不久前一个名为“Shai-Hulud”的恶意NPM包成功绕过了包括GitHub Actions工作流在内的来源验证机制完成了一次典型的供应链攻击。这件事最值得关注的不是攻击本身而是它清晰地暴露了当前“来源证明”技术的实际边界——它远非万能甚至可能因为被过度信任而制造新的盲区。简单说“来源证明”旨在回答一个问题“这个发布的包是否真的来自它声称的源代码仓库和构建流程”GitHub等平台通过签名和不可变记录来提供这种证明。然而Shai-Hulud事件证明攻击者完全可以在拥有合法仓库提交权限、能触发官方CI/CD流程的前提下注入恶意代码。这意味着即使来源被证明是“真实”的内容也可能是“有毒”的。对于开发者而言理解这个边界比盲目启用某个安全开关更重要。本文将基于这次事件拆解供应链攻击的路径并重点分析“来源证明”在哪些环节会失效以及我们在日常npm install、发包和依赖审计时真正应该关注什么。2. 复盘Shai-Hulud一次“合规”的投毒攻击要理解防御的局限先得看清攻击是如何发生的。Shai-Hulud不是一个直接发布到NPM的恶意包它巧妙地隐藏在一个看似正常的开源项目更新中。2.1 攻击链拆解权限与信任的滥用典型的NPM供应链攻击有几类1) 劫持维护者账号直接发布恶意包2) 依赖混淆攻击向公共仓库发布与内部包同名的恶意包3) 攻击项目的构建或发布流程。Shai-Hulud属于第三种但更精细。获取合法项目权限攻击者可能通过贡献代码、成为合作者或者直接入侵了某个开源项目维护者的账户。关键点在于他获得了对项目源代码仓库如GitHub的写入权限。在构建流程中注入恶意代码项目通常使用GitHub Actions等CI/CD工具来自动化测试、构建和发布。攻击者向仓库提交的代码本身可能看起来无害但修改了构建配置文件如package.json的scripts字段或GitHub Actions工作流文件。例如在prepublish或postinstall脚本中插入从远程服务器下载并执行恶意载荷的命令。触发“可信”的构建与发布当这次提交合并到主分支或直接触发工作流时GitHub Actions会基于项目官方配置执行构建。因为整个过程是在GitHub的受信环境中完成的所以生成的发布记录、二进制产物都带有“来源证明”证明它们确实源自这个仓库的这次提交和这次工作流运行。恶意包被“洗白”发布构建流程最终执行npm publish将包含恶意脚本的包发布到NPM Registry。由于整个发布动作由GitHub的官方actions/setup-node等受信动作完成且附带了完整的来源证明签名因此从NPM和供应链安全工具的角度看这个包的来源是清晰、可信的。最终用户npm install这个包时恶意脚本在安装阶段通过postinstall或后续被触发执行。而所有基于“来源证明”的验证都会显示这个包来自合法的官方仓库和构建流程。攻击的核心在于将恶意行为前置到了“来源”之内利用了我们对构建流程本身的信任。2.2 与常见安装报错的根本区别这里需要区分清楚这种攻击与你在日常开发中遇到的npm install报错如网络问题ECONNRESET、权限问题禁止运行脚本、命令未找到无法识别npm有本质不同。那些是环境配置或网络问题而供应链攻击是包内容本身被恶意篡改。前者导致安装失败后者可能导致安装“成功”但系统已被入侵。例如你可能会遇到npm ERR! code ECONNRESET npm ERR! network request to https://registry.npmjs.org/xxx failed这是网络层问题。而供应链攻击成功后你的安装日志可能看起来完全正常但一个隐藏的postinstall脚本已经在后台运行了。3. “来源证明”到底是什么又能证明什么在Shai-Hulud的背景下我们有必要重新审视“来源证明”的实际能力。3.1 技术实现签名与不可变记录以GitHub和NPM的集成为例“来源证明”大致是这样工作的生成证明当代码在GitHub Actions中构建并发布时GitHub会生成一个“证明”文件。这个文件使用Sigstore的密钥对构建环境、源代码提交哈希、工作流路径、触发者等信息进行数字签名。关联发布在发布包到NPM时这个签名证明会随包一同上传。验证用户或自动化工具可以通过查询NPM Registry或Sigstore的透明日志验证这个包是否由特定的GitHub仓库、特定的工作流在某个时间点生成。你可以通过npm命令查看包的来源信息如果已启用npm view package-name provenance如果配置了合适的验证策略在npm install时也可能进行自动验证。3.2 它能证明的有效边界身份真实性证明这个包确实是由某个特定的GitHub账户或组织下的自动化工作流发布的而不是来自一个仿冒的账户或本地随意发布的。构建环境一致性证明包是在一个声明过的、相对可控的CI环境中构建的减少了“它在我的机器上构建”引入的差异和潜在风险。源码关联性证明发布的包内容理论上与某个特定的代码提交版本相关联。这主要防御了发布劫持和仓库混淆攻击。例如攻击者盗取了维护者的NPM账号密码直接从本地电脑发布一个恶意版本。由于这个发布行为没有对应的GitHub Actions工作流记录和签名来源证明会缺失或验证失败从而触发警报。3.3 它不能证明的关键局限这正是Shai-Hulud事件揭示的痛点不能证明代码意图这是最大的局限。来源证明只保证包来自某个仓库和流程但不保证该仓库里的代码是善意的。如果攻击者有权向仓库提交恶意代码那么来源证明只会“忠实地”证明这个恶意包来源“正宗”。不能证明依赖安全即使你的项目代码纯净构建工作流中执行的npm install可能会引入有问题的间接依赖。来源证明不覆盖依赖链的完整性。不能防止工作流配置被篡改如果攻击者有权修改.github/workflows/*.yml文件他就可以将恶意脚本直接嵌入到CI流程中。来源证明会证明这个被篡改后的工作流是“合法”的。覆盖范围有限并非所有包都启用了来源证明。大量旧包、或非通过集成CI/CD发布的包没有此信息。过度依赖此功能会让人对无证明的包产生不必要的怀疑或对有证明的包产生虚假的安全感。结论就是来源证明是一个强大的“真实性”验证工具但不是一个“安全性”验证工具。它解决了“是不是你发的”问题但解决不了“你发的是不是好的”问题。4. 开发者日常超越来源证明的实战安全清单既然来源证明有局限作为一线开发者在npm install、引入依赖和发布包时应该建立更立体的防御习惯。不要只盯着一个安全特性。4.1 安装与使用阶段怀疑一切尤其是脚本审查package.json中的scripts在安装一个新依赖特别是知名度不高的依赖前习惯性地去NPM页面或直接npm view package查看其package.json。重点关注preinstall,install,postinstall,prepublish等生命周期脚本。任何试图下载、执行远程脚本或进行网络调用的命令都应视为红色警报。npm view shady-package scripts使用--ignore-scripts进行安全安装在不确定或高安全要求的环境如CI/CD服务器中安装依赖时可以使用--ignore-scripts参数阻止所有包的生命周期脚本执行。这能直接阻断大多数通过postinstall进行的攻击。npm install --ignore-scripts注意这可能会破坏那些确实需要在安装时编译原生模块如node-gyp的包。你需要权衡安全与功能。锁定依赖版本与完整性校验使用package-lock.json或yarn.lock确保团队所有成员和构建环境使用完全相同的依赖树避免因版本浮动意外引入有问题的更新。启用npm audit定期运行npm audit检查已知漏洞。虽然它不能防未知攻击0-day但能防已知漏洞。理解npm ci在CI环境中使用npm ci而不是npm install。它会严格根据package-lock.json安装并带来更纯净、可预测的安装环境。配置安全的NPM Registry与镜像使用官方源或可信的国内镜像避免配置指向恶意仓库。处理常见的网络错误如ECONNRESET时应检查网络和镜像配置而非随意降低安全等级。# 检查当前配置 npm config get registry # 设置为淘宝镜像示例 npm config set registry https://registry.npmmirror.com/4.2 维护与发布阶段守护你的仓库和流程如果你是包维护者你的责任更大。最小化仓库与发布权限遵循最小权限原则。不要给所有合作者都授予仓库的写入权限和NPM的发布权限。使用GitHub的Protected Branches保护主分支要求Pull Request和代码审查。在NPM组织中使用团队和精细的包访问权限。审查CI/CD工作流文件将.github/workflows/目录下的YAML文件视为关键基础设施代码。任何对其的修改都应经过严格的代码审查。确保工作流中使用的Action都是来自官方或可信来源的固定版本使用SHA256哈希而非标签。# 好使用固定哈希 - uses: actions/setup-nodev4 with: node-version: 20 # 更好使用具体提交哈希 - uses: actions/setup-node8f0e2c3d64cfaaf6d7c8d9c0f6a0e9b4e4a1b2c3启用并理解“来源证明”尽管有局限它仍是重要的一环。为你的项目启用GitHub Actions的发布流程并签署你的包。这至少为你的用户建立了基础的真实性信任。同时要清楚地向用户说明这不能替代他们对包内容的审查。实施依赖更新策略不要盲目更新所有依赖。使用npm outdated或依赖更新服务如Dependabot, Renovate但为更新配置规则仅接受补丁版本自动更新对次要版本和主要版本更新进行人工审查。审查时重点看变更日志和代码差异。4.3 组织与架构层面纵深防御对于企业或大型项目需要考虑更上游的防御。私有Registry与镜像代理搭建私有的NPM Registry如Verdaccio或使用云服务商的私有制品库。将所有公共依赖缓存到内部所有安装请求都经过内部代理。这可以统一实施安全扫描、访问控制和依赖冻结。静态代码分析与软件成分分析SCA在CI流水线中集成SAST工具扫描源代码集成SCA工具如Snyk, WhiteSource扫描package-lock.json识别许可证风险、已知漏洞以及潜在的恶意包模式如混淆代码、可疑域名。沙盒化构建环境确保CI/CD运行器如GitHub Actions Runner, Jenkins Agent是临时的、隔离的。构建完成后立即销毁防止持久化攻击。限制构建容器对内部网络的访问权限。制定清晰的依赖引入政策规定团队引入新依赖需要经过哪些审批步骤包括安全性评估、许可证审查、活跃度检查等。优先选择活跃维护、社区认可度高的项目。5. 当问题发生时如何调查与响应即使采取了所有预防措施怀疑或确认遭遇供应链攻击时应该怎么做立即隔离与评估断开网络如果怀疑恶意脚本已运行立即断开受影响机器的网络防止数据外泄或二次感染。锁定账户如果维护者账户可能被盗立即在GitHub、NPM等所有相关平台重置密码、吊销会话、检查访问日志。下线恶意包如果是自己维护的包被入侵立即使用npm deprecate标记包为废弃或联系NPM安全团队下架恶意版本。深入取证分析检查发布记录在NPM上查看该包的所有版本发布历史、发布时间、发布者。对比正常版本和可疑版本的package.json差异特别是scripts和dependencies。审查源代码提交在GitHub上定位导致问题的具体提交。检查提交者、修改内容、是否绕过Code Review。分析构建日志查看GitHub Actions等CI/CD系统的运行日志寻找异常命令或下载行为。检查“来源证明”运行npm view packageversion provenance查看该版本的证明详情确认其构建工作流和源码提交判断证明本身是否被伪造可能性极低但需验证。清除影响与通知清理环境彻底清理受影响的开发机、构建服务器和生产环境。可能需要重装系统或容器镜像。轮换密钥如果构建或发布流程中使用的任何密钥、令牌可能已泄露立即轮换。透明沟通如果影响到用户通过安全公告、仓库公告、社交媒体等渠道清晰说明事件经过、影响范围、已采取的措施和用户需要做的修复如升级到安全版本。Shai-Hulud攻击事件像一次清醒的演练它告诉我们供应链安全没有单点解决方案。“来源证明”是一项重要的进步它抬高了攻击门槛但它只是防御纵深中的一层。真正的安全来自于对整个软件生命周期的持续警惕从代码提交审查、CI/CD工作流安全、依赖选择策略到最终用户的安装习惯。作为开发者最务实的态度是利用好“来源证明”这类工具同时绝不将全部信任寄托于它。始终假设依赖可能存在问题并通过流程和技术手段将潜在损害控制在最小范围。
返回列表