TanStack 及更多依赖包受影响 - 深度解析与调查

MMaximilian Schwarzmüller
컴퓨터/소프트웨어경제 뉴스AI/미래기술

Transcript

00:00:00目前又发生了一起非常重大的供应链攻击,而且该攻击仍在持续进行中
00:00:06它已经从 NPM 蔓延到了 Python 生态系统,所以现在最好先不要安装任何
00:00:12NPM 或 Python 软件包。同时确保你的系统整体设置是安全的。我之前拍过
00:00:19相关的视频,我会在下方分享链接,并在本视频中再次提及,但首先我还是想
00:00:23给你们提供一些受影响的细节,并帮你们查明是否受到了波及。这一切始于
00:00:30TanStack 的各种包,如 TanStack Query、Router、Start 等。就在昨天,也就是 5 月 11 日,
00:00:36在很短的时间内,一些恶意包,或者准确说是所有的 TanStack 软件包,
00:00:43都发布了带有恶意代码的版本,不过在 20 分钟内很快就被控制住了。最终,
00:00:50虽然它被迅速发现并遏制,但所有这些恶意包都在那个时间段,
00:00:57或者说在那个极短的时间窗内发布了。随后它继续蔓延,目前
00:01:03仍在扩散。它传到了 Mistral 的包,虽然(那个包)只有四个用户,但它仍然
00:01:09受到了影响,因为这种恶意软件像蠕虫一样运行,会窃取数据、窃取凭证,如果它
00:01:16安装在你的系统中,也可能会窃取你的凭证。我稍后会具体讲如何查看你是否受到了影响,
00:01:20但它还在向更多的 NPM 软件包扩散,因为这就是它的核心逻辑,
00:01:26甚至已经进入了 Python 生态系统,这正是现在正在发生的事情。这只是几个小时前的事,
00:01:32在我录制这段视频时,仅仅发生了两个小时。那么,你该如何查明自己是否受到了影响呢?
00:01:39如果你在昨天傍晚安装了任何 TanStack 软件包——以我所在的德国时区为例,
00:01:45你必须认为自己受到了影响。如果你在那个时间点左右进行了安装,请记住
00:01:54那是 UTC 时间,所以你需要将其转换到你的时区,如果在那个时间,你必须认为
00:02:00自己受到了影响。但既然它正蔓延到 Mistral 以及更多其他的 JavaScript 软件包,
00:02:06数量多到我无法在这里一一列举,你也必须认为自己或你的机器已经受到波及和入侵。
00:02:13我会在下方分享这些帖子的链接,以便你们深入了解并查看已公布的
00:02:18受影响软件包的完整列表。但正如我提到的,事情仍在继续,
00:02:22所以也许现在先不要安装任何东西。这里还有一些入侵指标(IOC)。你可能需要
00:02:31查找特定的文件哈希值,即 Router 的 JS 文件中的 SHA 哈希。我也会在下方链接这个帖子。
00:02:38如果你有办法监控机器上发生的网络请求,
00:02:42你应该查找发往此 URL 的传出流量,这将是数据已从你的系统中
00:02:48泄露的另一个明确指标。那么“入侵”具体意味着什么呢?它意味着
00:02:55这种恶意软件主要做了两件事。第一件重要的事是它在收割数据。它会
00:03:03寻找 NPM 令牌、GitHub 令牌、AWS 凭证以及其他机密。它会扫描你的系统,寻找
00:03:12存储凭证和机密的典型位置,并将它们收集起来发送到
00:03:18我刚才给你们看的那个 URL。所以它在窃取这些机密。但它不仅如此。
00:03:26正如我提到的,它像蠕虫一样运作,所以它还会利用这些被盗的 GitHub 令牌。例如,
00:03:33它会利用这些令牌和 NPM 令牌来发布更多被入侵的包。如果你是
00:03:40另一个包的维护者,或者你可能有一个在那个时间段运行的 CI/CD 工作流,且它
00:03:46依赖于某些 TanStack 软件包,那么在那个 CI/CD 工作流中,被恶意篡改的
00:03:53TanStack 包就会被拉入。恶意代码可能就在那里执行了。接着在那个
00:04:00工作流中(而不是在你的本地机器上),它也可以窃取特定的凭证,
00:04:06从而为你原本 CI/CD 工作流试图构建的那个包,
00:04:14发布一个包含恶意代码的版本。这就是它传播的方式。正如我提到的,它像蠕虫一样。它利用这些
00:04:20窃取的凭证和令牌来发布更多受污染的包。这也就是它如何传播到
00:04:26Mistral,然后是其他 JavaScript 软件包,甚至进入 Python 生态系统的原因。这就是
00:04:32我们目前的处境。据我所知,它仍在蔓延。那么,你该如何防范呢?
00:04:39我在另一个频道 AkataMind 上专门制作了一个视频。我也会在下方附上链接。
00:04:44简而言之,你要确保尽可能不要直接在宿主机器上运行代码或进行开发,
00:04:51而是使用虚拟机或开发容器之类的环境。不要在机器上存储明文机密。我是指,对于 AWS,
00:04:57例如,你应该使用他们的单点登录方式,而不是在机器上存储 IAM 凭证,
00:05:03对你可能使用的其他服务也应采用类似的技术。此外,
00:05:10你也可以考虑使用像 Infisical 或 Doppler 这样的服务,将你的机密存储在
00:05:16云端,而不是硬盘或 .env 文件中。这是你应该考虑做的。
00:05:25再次强调,我在那段视频中详细讨论过这些内容。你还应该使用支持
00:05:30配置最小发布时长的包管理器和设置,比如 Bun 就支持这个。在
00:05:38bunfig.toml 文件中,你可以设置最小发布时长,以确保即使你运行 bun install,
00:05:44你也只会安装那些至少发布了 X 秒或 X 天(在这个例子中)的包。
00:05:49目前,pnpm 也有类似的功能,最新版本的 npm 也支持。这些我都在那段视频里
00:05:56讲过了。如果你使用的是 Bun 或者对 npm 进行了正确的配置,由于 Bun 默认就会这么做,
00:06:02它还会阻止诸如 post install 脚本等生命周期脚本的执行,也就是你正在
00:06:09安装的那些包自带的脚本,这为你提供了另一层安全机制,因为这类恶意软件通常
00:06:15依赖于在你的系统上执行这些脚本。所以,使用安全的包管理器、配置好安全设置、
00:06:21在虚拟机或开发容器中运行代码,并且不在系统上存储明文机密,
00:06:28这些是你平时就该做的,但现在由于这类攻击正变得越来越严重,就显得更为重要了。
00:06:36我们会深入研究这次攻击的具体原理,因为它确实非常有意思。当然,
00:06:41我们正在面临越来越多的此类事件。我现在几乎每个月甚至更频繁地制作这样的视频,
00:06:46一方面是因为我认为它们现在更容易实施了。在 AI 时代,更容易去分析你想要影响的
00:06:52软件包或依赖项,并分析它们的源代码或 CI/CD 设置,寻找潜在的
00:06:58攻击向量。这次针对 TanStack 的攻击就是这样。并不是说某个维护者的机器
00:07:04被入侵了,而是 TanStack 的 CI/CD 工作流遭到了攻击。我待会会具体谈到。
00:07:12所以,利用 AI 寻找漏洞更容易了,编写代码(当然包括恶意代码)也更容易了。
00:07:22与此同时,软件领域正迎来爆炸式增长。我们编写的软件比以往任何时候都多,
00:07:28因此目标也更多,其中甚至包括许多完全不关心安全的目标。这让此类攻击变得更有“吸引力”。
00:07:34那么这一切是怎么开始的呢?这很有趣。正如我提到的,这并非全新的手段,
00:07:40也不是我们从未见过的,但确实相当精心策划。TanStack 团队发布了一篇
00:07:45复盘文章,解释了攻击是如何发生的。我也在下方提供了链接。
00:07:51不过我当然会在这里给你们总结一下,因为最终这次攻击主要依赖于
00:07:57三个关键步骤,我会详细解释。首先是“Pull Request 靶向攻击”(Pawn Request)模式。
00:08:03TanStack 团队发布了一篇复盘分析,在文章中解释了这次攻击是如何发生的。
00:08:09以及运行时内存提取 OIDC 令牌。好吧,这些都是什么意思?同样地,
00:08:15你可以去读文章看细节,但让我给你们做个总结。让我们从
00:08:22Pull Request Pawn Request 模式说起。那是什么呢?为了理解它,我们必须明白
00:08:30GitHub Actions 当然是 GitHub 提供的 CI/CD 解决方案,是他们的 CI/CD 产品。顺便提一下,
00:08:38我也有一门关于 GitHub Actions 的课程,如果你想学习如何设置 GitHub Actions,
00:08:45如何使用该产品处理 CI/CD 任务,如何发布软件包或网站等等。
00:08:50与所有 CI/CD 工作流工具一样,GitHub Actions 依赖于触发工作流的事件,因为
00:08:58CI/CD 的核心就是以自动化的方式做一些事情。例如,当你推送到主分支时,
00:09:05以自动化的方式发布或部署你的网站。
00:09:10所以有各种事件可以触发工作流,比如“push”就是一个事件,
00:09:16正如所有 CI/CD 工作流工具一样,GitHub Actions 依赖于触发工作流的事件,因为
00:09:24我就想执行某些任务。我想安装依赖项,想构建项目,想将其上传到我的服务器。
00:09:29这就是你可以做的。现在,另一个触发器是 `pull_request_target`。
00:09:34当有人为你的仓库开启 Pull Request 时,这个触发器就会激活。
00:09:40这当然意味着任何人都可以 Fork 你的仓库,在里面做点什么,推送到他们的
00:09:44Fork 仓库里,然后针对你的仓库开启一个 Pull Request。这样就会触发
00:09:49这个工作流。听起来很危险?确实有点。这也是这次攻击的开端。
00:09:56其实还有一个 `pull_request` 触发器。刚才我提到了 `pull_request_target`,
00:10:05但我们也有 `pull_request`。两者的工作方式类似,但 `pull_request` 会在
00:10:14那个 Fork 出来的仓库上下文中运行 CI/CD 工作流。所以无论里面可能在进行什么恶意操作,
00:10:19它都发生在 Fork 的仓库里,而不是原始基础仓库里。所以这不是问题。
00:10:25相比之下,`pull_request_target` 则是在基础仓库的上下文中运行。这当然
00:10:31是存在潜在危险的。正因为任何人都能开启 PR。而且,
00:10:38在这次 TanStack 攻击案例中,在那个 PR 和 Fork 仓库中,攻击者将
00:10:45恶意代码(蠕虫代码/恶意软件)植入到了 TanStack 仓库的 Fork 版本里。
00:10:52然后攻击者开启了 PR,导致 `pull_request_target` 被执行。接着就像刚才提到的,
00:10:58这会启动一个 GitHub Actions 运行器,并在基础仓库的上下文中运行。这意味着什么呢?
00:11:04这并不意味着攻击者获得了基础代码的访问权限,或是能直接将恶意代码合并进仓库,
00:11:10在那个分叉仓库中,攻击者植入了恶意代码、蠕虫代码,也就是 TanStack 仓库中的恶意软件,
00:11:20将会与随后源自基础仓库的 GitHub Actions 执行过程共享,
00:11:26即便那些执行是由完全不同的钩子或事件触发器(如 push 触发器)引起的。
00:11:33接下来的事情就是缓存投毒。但这具体是什么意思呢?嗯,
00:11:40攻击者在他们的 Fork 代码中加入了某些指令,以确保当 GitHub Actions
00:11:46因 `pull_request_target` 触发器而运行时,会执行一条 `hash files` 命令,
00:11:53这是 GitHub Actions 支持的功能,目的是在缓存中存储一些东西。那么,
00:12:00这个缓存是做什么用的?GitHub Actions 缓存的设计初衷非常简单,就是为了加速
00:12:05那些工作流。例如,你可以对依赖项进行哈希处理。核心思想是,
00:12:11如果你的包所依赖的项没有变化,为什么还要重新执行整个安装过程呢?
00:12:17那纯粹是浪费时间,而时间就是金钱,因为你是按工作流运行的时长来付费的。
00:12:23即 GitHub Actions 支持的 hash files 命令,用来在缓存中存储内容。那么,
00:12:28这个缓存究竟是做什么用的呢?GitHub Actions 缓存的设计初衷非常简单,就是为了提高
00:12:33这些工作流的运行速度。例如,你可以对依赖项进行哈希处理。其原理是,
00:12:39如果你项目所依赖的包没有发生变化,为什么还要重新执行一遍完整的
00:12:46安装过程呢?这会耗费大量时间,而时间就是金钱,因为你是按
00:12:52工作流的运行时长付费的。当然,你也不希望工作流跑起来没完没了。
00:12:56所以在大多数工作流中,例如在构建 TanStack 软件包时,
00:13:00你会先安装 TanStack 软件包的依赖项,然后执行构建步骤,
00:13:06构建你的 TanStack 软件包。同样,如果 TanStack 的依赖项
00:13:12没有变化,为什么要重新安装?这就是缓存的初衷,非常有道理。当然,问题在于,
00:13:18由于 pull request target 触发的 GitHub Actions 执行,和其他 GitHub Action 执行,
00:13:24比如 push 触发的执行,共享同一个上下文,它们共享同一个缓存。
00:13:31这就是缓存投毒的切入点,因为攻击者能够缓存恶意版本,或者
00:13:39将恶意代码植入 TanStack 的依赖项中并进行缓存。随后,攻击者
00:13:46只需等待 TanStack 软件包进行正常的 GitHub Actions 工作流运行。
00:13:53只要有维护者推送代码,那么后续的 GitHub Actions 执行就会重用
00:14:01之前由恶意执行建立的同一个缓存,从而拉取
00:14:08准备好的投毒缓存,其中包含了恶意代码。恶意代码就是这样
00:14:13从分叉库进入到正常维护者、在正常 push 时运行的正常 GitHub Actions 执行中的,
00:14:21这些维护者本身并没有受到任何恶意代码影响。缓存最终成为了
00:14:28这两次 GitHub Actions 执行之间的传输媒介。接下来是第三步,一旦
00:14:35恶意代码通过 push 事件进入了 TanStack 常规的 CI/CD 工作流执行中,
00:14:44它窃取了一个短效的 NPM 令牌(本质上是 OIDC 令牌),用于发布恶意版本的
00:14:54TanStack 软件包。我这里指的是什么呢?NPM 有一个功能叫做
00:15:00“可信发布 (Trusted Publishing)”,理论上它能让发布 NPM 软件包更安全,因为
00:15:04向 NPM 发布软件包大致有两种方式。一种是
00:15:11通过你的 NPM 账号创建一个令牌,然后用它发布新版本的软件包。
00:15:19问题是如果令牌被盗,任何人都可能发布该软件包的新版本。为了
00:15:26提升安全性,就有了可信发布流程,NPM 规定:不,你不能从
00:15:33本地机器发布软件包,必须通过其中一个可信提供商,GitHub Actions 就是其中之一,
00:15:37你可以设置针对 GitHub Actions 的可信发布集成。然后,
00:15:44作为该可信发布流程的一部分,系统会获取或请求一个
00:15:50短效发布令牌。接着,该短效令牌将被用于对即将发布的
00:15:57新软件包版本进行签名。理论上,其理念是令牌很难被盗,因为
00:16:03它不在任何维护者的机器上。此外,它是短效的。即使被盗,
00:16:08它的有效期也非常短。当然,问题在于,如果正在请求
00:16:15该可信令牌的 CI/CD 工作流中的运行代码受到了影响,那么
00:16:21恶意代码就能访问这个全新的短效可信发布令牌。这就是
00:16:27这里发生的事情。恶意代码滥用或使用了这个令牌,随后发布了
00:16:36新版本的 TanStack 软件包。有趣的是,这次攻击其实出现了一点失误,
00:16:44因为它虽然获取并使用了可信令牌去调用 NPM API 发布了包含
00:16:52恶意代码的 TanStack 新版本,但其所属的 GitHub Actions 工作流
00:16:58最后却因为推送到 CI/CD 的代码有问题而没能完成。如果
00:17:06攻击者能注意到并在推送有效代码的时间点发动攻击,
00:17:12那么这个工作流本可以顺利完成,他们也就不需要通过手动
00:17:19调用 NPM API 来发布恶意软件包,而是可以像他们做的那样注入代码,
00:17:26让工作流成功跑完,然后一个受损的 TanStack 版本
00:17:32就会被发布出来,而且看起来非常正当,因为是由维护者正常推送,
00:17:38且工作流也顺利完成了。这次攻击之所以能被外部贡献者
00:17:45并顺利完成。由于该工作流并未成功完成,这种攻击方式
00:17:51最终使得一位外部贡献者更容易发现到底发生了什么,
00:18:00因为你可以看到 TanStack 软件包发布了新版本,尽管
00:18:05GitHub Actions 工作流失败了,而按理说不应该有新版本发布。所以你可以看到
00:18:12其中的矛盾之处,这让检测这次攻击变得容易了一些。在这一点上,
00:18:19TanStack 的维护者和我们大家都很幸运。尽管如此,正如你所见,
00:18:26这依然是一次非常精心策划的攻击,它在完全没有入侵任何人机器的情况下生效了。而且尽管它被迅速发现,
00:18:32但还是造成了严重的损失。正如我提到的,它仍在蔓延,这真是一段
00:18:41漫长的历程。但我真的想强调,你必须努力提高系统的
00:18:49安全性。正如我之前以及在本视频中分享的那样,你要确保自己能够降低
00:18:56受影响的风险。这次攻击虽然被迅速发现,但目前仍在扩散,
00:19:05所以一切还没有结束。而且并不是所有的攻击在未来都能
00:19:11被如此迅速地发现。正如所言,他们这次很幸运,如果这次攻击更难被察觉,
00:19:18损失可能会更加惨重。但目前的损失已经很大了,
00:19:24而且还在继续。我确信我们会看到更多此类攻击,因为我提到过,攻击面
00:19:31正在变得越来越大,也越来越诱人。写代码的人越来越多,但其中很多人
00:19:36并不清楚自己在做什么,而 AI 正在辅助这些攻击的实施。这就是目前的
00:19:42现状。如果不是非装不可,现在请先不要安装任何东西,仔细检查你的设置,
00:19:48如果你想深入了解或查看受影响包的完整列表,
00:19:51所有的链接都在下方。

Key Takeaway

针对 TanStack 的供应链攻击利用 GitHub Actions 的 pull_request_target 机制实现缓存投毒,并滥用可信发布令牌窃取 AWS 及 GitHub 凭证,暴露了 CI/CD 工作流中共享上下文的严重安全漏洞。

Highlights

  • 2026 年 5 月 11 日 TanStack 生态系统(Query, Router, Start 等)遭到持续性供应链攻击,恶意代码在 20 分钟内完成发布并向 Python 生态蔓延。

  • 攻击者通过 GitHub Actions 的 pull_request_target 触发器,利用恶意 Fork 仓库向基础仓库的缓存投毒,实现跨工作流的恶意代码植入。

  • 该恶意软件具备蠕虫特性,能自动搜索并窃取系统中的 NPM、GitHub、AWS 凭证以及 .env 文件中的明文机密,并发送至远程服务器。

  • 受损的 CI/CD 工作流利用 NPM 的“可信发布”(Trusted Publishing)机制,获取短效 OIDC 令牌以发布看似合法的受污染软件包版本。

  • 通过设置包管理器的最小发布时长(min-age)配置,如 Bun 的发布验证功能,可以有效拦截发布时间过短的恶意软件包。

  • 防范此类攻击的核心手段包括使用开发容器(Dev Containers)隔离环境、采用 SSO 认证替代明文 IAM 凭证,以及禁用生命周期脚本。

Timeline

供应链攻击的现状与波及范围

  • TanStack 旗下所有软件包在 2026 年 5 月 11 日均发布了包含恶意代码的版本。
  • 恶意软件以蠕虫方式运行,已经从 NPM 蔓延至 Mistral 等软件包以及 Python 生态系统。
  • 攻击发生后的数小时内,用户应停止安装任何新的 NPM 或 Python 软件包以避免感染。

这次攻击始于 5 月 11 日,虽然 TanStack 团队在 20 分钟内遏制了漏洞,但恶意版本已经传播。受感染的包会像蠕虫一样在系统中运行,主要目标是窃取敏感憑据。目前攻击仍在动态扩散中,即便只有少数用户的包(如 Mistral 相关包)也未能幸免。

系统受损指标与即时检查方法

  • 在 UTC 时间 5 月 11 日晚间安装过 TanStack 相关包的用户必须视为系统已受损。
  • 监控网络流量中是否存在发往特定恶意 URL 的异常传出请求是识别数据泄露的明确指标。
  • 通过检查 Router 等 JS 文件的 SHA 哈希值可以验证本地环境是否存在入侵迹象。

由于受影响的软件包列表持续增加,无法一一列举,用户需根据安装时间戳进行自我审计。具体的入侵指标(IOC)包括特定的文件哈希和恶意数据接收服务器的地址。一旦确认匹配,意味着系统中的机密信息可能已经外泄。

恶意软件的收割逻辑与蠕虫传播

  • 恶意代码通过扫描系统典型存储位置,窃取 NPM 令牌、GitHub 令牌和 AWS 凭证。
  • 攻击者利用窃取的 GitHub 令牌在受害者的 CI/CD 工作流中发布更多受污染的软件包。
  • 即便开发者本地机器未中毒,只要 CI/CD 环境依赖了受损包,其维护的项目也会成为传播媒介。

该软件不仅停留在静态的数据窃取,还具备极强的扩张性。它会寻找存储在硬盘或环境变量中的明文机密,并利用这些权限冒充合法维护者发布新包。这种方式导致了攻击从一个工具库迅速扩散到整个生态系统,形成链式反应。

开发者环境的安全防御策略

  • 使用虚拟机或开发容器(Dev Containers)替代宿主机器直接运行代码可降低感染风险。
  • Infisical 或 Doppler 等云端机密管理服务能避免在本地存储 .env 明文文件。
  • 配置 Bun 或 pnpm 的最小发布时长限制,可以阻止自动安装发布不到 24 小时的新包。

防御建议集中在环境隔离和机密管理上。通过 SSO 认证代替 IAM 凭证能减少本地明文机密的存留。同时,现代包管理器提供的脚本禁用功能和发布时长验证能拦截大部分自动触发的恶意代码执行,在攻击爆发初期提供关键的防护层。

GitHub Actions 缓存投毒技术细节

  • 攻击者利用 pull_request_target 触发器在基础仓库的上下文中运行恶意 Fork 代码。
  • 恶意 PR 通过执行 hash files 命令篡改 GitHub Actions 共享缓存中的依赖项。
  • 当正常维护者推送代码触发 push 事件时,工作流会拉取已被投毒的缓存并执行恶意逻辑。

pull_request_target 与常规 pull_request 触发器的不同之处在于它运行在基础仓库的特权上下文中。攻击者利用这一特性,通过 PR 预先将恶意代码注入缓存。由于 GitHub Actions 的缓存机制是为了节省构建时间而跨运行共享的,这使得恶意代码能从未经审核的 PR 潜入到正式的构建流程中。

可信发布机制与攻击的偶然暴露

  • 恶意代码在受损的工作流中实时截获了用于“可信发布”的短效 OIDC 令牌。
  • 由于攻击脚本在发布恶意包后导致原有的构建工作流报错失败,导致了行为矛盾而被察觉。
  • 外部贡献者发现 GitHub Actions 构建失败但 NPM 却发布了新版本,从而揭开了攻击的真相。

NPM 的可信发布(Trusted Publishing)本意是消除长期令牌的风险,但当 CI/CD 运行环境本身被入侵时,短效令牌同样会被实时窃取。这次攻击因脚本编写瑕疵,导致 GitHub Actions 状态与 NPM 发布状态不一致。这种矛盾成为了检测攻击的突破口,否则受污染的版本可能会在更长时间内保持“合法”外壳。

Community Posts

No posts yet. Be the first to write about this video!

Write about this video