Skip to content

필사 모드: npm 供应链攻击为何挥之不去 — npm-scan 实际做的事,以及真正有效的防御

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

引言 — npm 供应链攻击不是例外,而是结构

npm 供应链被攻破的消息,如今与其说是新闻,不如说更接近背景噪音。知名的包被劫持,只是安装了一下令牌就外泄,几天后又在另一个包上重演同样的事。它之所以不是一连串的偶然,是因为 npm 的设计本身就给攻击让出了很宽的攻击面。本文以最近在 Hacker News 上引发热议的 npm-scan 这个工具为线索,诚实地厘清那个攻击面究竟在哪里,以及用工具能挡住的部分和挡不住的部分各到什么程度。

先把话说清楚。npm-scan 的 README 信心十足,把难以验证的数字摆在最前面。我认为这个工具瞄准的问题是真实的,但我不会照单全收那些数字。让我们把问题和工具分开来看。

为什么 npm 在结构上是脆弱的

三样东西叠加在一起。第一,安装脚本。npm 包可以在 preinstallinstallpostinstall 钩子里执行任意代码,而那段代码是以运行安装的用户权限来跑的。npm install 一次,就等于执行了不受信任的代码。第二,传递依赖。哪怕你亲自挑的包只有十个,node_modules 里也会进来数百个,而你早已签字同意信任那数百名维护者。第三,typosquatting。像 lodashlodahs 这样只差一个字母的名字,一次打字错误就会把恶意包拉进来。

单独来看,每一样都是可以管理的。危险在于它们会组合起来。一个 typosquatting 的名字把包拉进来,那个包的 postinstall 在安装时运行,而它又是作为你所信任的某个东西的传递依赖被捎带进来的,于是没有人去审查它。这三个弱点与其说是三种各自独立的风险,不如说是一条流水线。

2025 年的 Shai-Hulud 蠕虫证明了这不是理论。根据 Palo Alto Unit42 的分析,这个蠕虫借由安装脚本执行,用偷来的 npm 令牌登录到 registry,再往同一开发者维护的其他包里植入恶意代码,从而自我繁殖。它的目标是 .npmrc 里的 npm 令牌、GitHub PAT,以及 AWS、GCP、Azure 的 API 密钥。11 月的 "Shai-Hulud 2.0" 把执行时机从 postinstall 挪到了 preinstall,用 Unit42 的说法,这"彻底消除了人的介入,实际上保证了在几乎所有构建服务器上都会执行"。2.0 横跨数万个 GitHub 仓库,在约 350 名用户身上制造出超过 2 万 5 千个恶意仓库。

这三样之下还有第四个因素:维护者账户。这类行动大多不是从在你的代码里找 bug 开始的 — 而是从用被钓鱼的登录凭据或泄露的令牌劫持一个受信任的发布者,再上传你已经依赖的那个包的恶意版本开始的。正规账户发布它的那一刻,上面的所有机制就会自动把它运送到你这里。所以风险的单位不是单个的包,而是整条供应链。

关键在于,这些攻击并没有动用什么特殊的零日漏洞,而是原封不动地利用了 npm 有意提供的功能 — 安装脚本和发布权限。所以它们不会消失。

npm-scan 实际检查什么(以及无法验证什么)

npm-scan 是以 @lateos/npm-scan 发布的、大部分用 JavaScript 写成的 CLI 工具。它打出的口号是"抓住 npm audit、Snyk、Socket 漏掉的东西"。用法很简单。

npm install -g @lateos/npm-scan
npm-scan axios                 # 检查单个包
npm-scan scan-lockfile         # 检查整个锁文件
npm-scan express --json > findings.json

README 主打的是通过"23 个检测器(D1–D25)"进行的静态与行为分析。据称它完全在本地运行,没有遥测也不依赖云,目标是每次 CI 运行不到 30 秒。输出这一侧是可以接入实际工作的 — SARIF(对接 GitHub Security)、CycloneDX 与 SPDX 的 SBOM 导出、YAML 策略允许列表、GitHub Actions 集成。它瞄准的威胁类别也和本文前半部分精确重合:凭据窃取、混淆与反调试、从内存中提取 OIDC 与 AI 密钥,以及蠕虫传播。

到这里都还合理。问题出在 README 给每个类别都贴上了 98%、99% 这样的检测率。这些数字既没有公开的方法论,也没有数据集,更没有基准测试。如果不知道是在什么样本上、以什么为真阳性测出来的值,那它就不是测量,而是主张。何况 README 对误报(false positive)和局限性只字未提。所有扫描器都会产生误报,也都会有漏掉的东西。只罗列长处的安全工具,本身就是需要警惕的对象。至于单个检测器 D1–D25 具体抓什么、怎么抓,光靠 README 也无从得知 — 作为详细文档链接过去的文件,在我查看的那个时点打不开。

最后说说许可证。个人、开源、评估用途按 MIT 免费,但生产环境使用需要从每年 $799$9,900 不等的商业许可证。这是把它常驻接入 CI 之前该弄清楚的价码。

关于措辞再补一句。"抓住 npm audit、Snyk、Socket 漏掉的东西"不是中立的陈述,而是竞争性的主张 — Socket 和 Snyk 都是自带行为分析的成熟工具,而"漏掉"这个词承担了很多东西。"现有工具抓不到的,我们能抓到"这类宣传,不该当成结论,而该当成一个要在你自己的依赖上亲自验证的假设来看待。

那么,真正有效的防御

扫描器只是防御的一层,而铁了心的攻击者会绕开各种启发式规则。真正缩小爆炸半径的,是那些枯燥的基本功。

  • 关掉安装脚本。.npmrc 里写上 ignore-scripts=true,或者使用 npm install --ignore-scripts,Shai-Hulud 这类蠕虫盯上的那个执行点本身就消失了。这不是没有代价的 — 像原生模块那样确实需要构建脚本的包,得做例外处理。不过大多数依赖并不需要脚本。
  • 锁文件与 npm ci 提交 package-lock.json,并在 CI 里用 npm ci 而不是 npm install。它会原样安装被固定的依赖树,一旦出现漂移就失败。如果养成在 PR 里让人去读锁文件 diff 的习惯,那些意外新进来的传递依赖就会被看见。
  • 验证 provenance。 npm 的 provenance 与可信发布(attestation)把包绑定到构建它的那套 CI 上。可以用 npm audit signatures 来核对签名。
  • 减少依赖。 这是最根本的。每一个传递依赖都是一块攻击面,都是又多出一个需要信任的维护者。在为了一行工具函数而多加一个包之前,先问问:值不值得为它扩大那块攻击面。
  • 管理令牌。 不要把 .npmrc 令牌随手丢在构建机器上。收窄它的范围,并定期轮换。记住 Shai-Hulud 翻的正是那个文件。

这份清单大致是按杠杆效应排序的。拦截安装脚本排在最前面,是因为它关上的正是近来那些蠕虫走过的那扇门;减少依赖排在最后,不是因为它不重要,而是因为它的效果显现得最慢 — 拉长时间看,能把其余一切都缩小的正是这一项。

npm-scan、Socket、Snyk 这类扫描器,作为叠在这些基本功之上的一层是有价值的。只是,扫描器替代不了上面那份清单。

结语

npm 供应链攻击之所以挥之不去,是因为它不是 bug,而是对功能的滥用。安装脚本和发布权限正是让 npm 变得便利的那些要素,而攻击者只是按其本意去用它们。所以这个问题不会被"根治",只能被管理。

但这也不是该悲观的事。趋势确实在恶化 — pre-install 执行和自我复制的蠕虫是明显的升级 — 但应对之策众所周知,而且大多是免费的。缺的不是知识,而是这样一个事实:在事故爆发之前,人们很少会把这些枯燥的卫生习惯放上优先级。

我认为,对待 npm-scan 这类工具,也该是同样的态度。它瞄准的问题是真实的,本地运行、SARIF、SBOM 这些设计也都合理。但没有方法论支撑的检测率,加上对局限性的沉默,要求的是验证,而非信任。把扫描器当作一层,同时别忘了真正缩小爆炸半径的是锁文件与拦截脚本、provenance,以及更少的依赖 — 这才是诚实的结论。

参考资料

현재 단락 (1/30)

npm 供应链被攻破的消息,如今与其说是新闻,不如说更接近背景噪音。知名的包被劫持,只是安装了一下令牌就外泄,几天后又在另一个包上重演同样的事。它之所以不是一连串的偶然,是因为 npm 的设计本身就给...

작성 글자: 0원문 글자: 3,626작성 단락: 0/30