Trivy 供应链攻击:发生了什么、什么被盗以及如何应对

TL;DR

2026年2月27日至3月22日期间,威胁行为者TeamPCP对Aqua Security的Trivy漏洞扫描器、其GitHub Actions以及超过60个npm包进行了多阶段开源软件供应链攻击。本文将多个厂商报告中的时间线、技术手段、IOC指标和修复指南整合为一份完整的参考资料。

如果您在2026年3月19日至22日期间使用了Trivy、trivy-action或setup-trivy,请假设受到以下影响:​

  • 工作流中所有可访问的机密信息(GitHub令牌、云凭证、SSH密钥、Docker注册表令牌)均已被窃取,使用RSA-4096加密,并发送至攻击者基础设施
  • CI/CD运行器的内存被扫描,收集所有标记为isSecret:true的GitHub Actions机密
  • 在CI环境外运行Trivy v0.69.4的开发人员机器上存在持久的systemd后门,每5分钟向ICP容器轮询获取任意载荷
  • 从CI运行器窃取的npm令牌被用于在60秒内发布64+个包的恶意版本
  • 从被入侵的运行器可访问的Kubernetes集群可能存在具有主机级访问权限的攻击者DaemonSet

事件起源:AI机器人与配置错误的工作流

2026年2月27日,一个名为hackerbot-claw的自主GitHub机器人对Trivy仓库提交了PR #10252。该PR立即被关闭,但这并不重要。Trivy的"API Diff Check"工作流使用了pull_request_target,该工具在执行来自攻击者分支的代码时使用仓库级别的密钥运行。这就是著名的"Pwn Request"模式

该机器人将一个个人访问令牌(PAT)窃取recv.hackmoltrepeat[.]com。截至2月28日,攻击者已完全控制仓库:他们将仓库设为私有、删除了全部178个GitHub Releases(v0.27.0至v0.69.1),并向Open VSIX发布了一个恶意VS Code扩展。

Aqua Security做出响应并轮换了凭证。但轮换并非原子操作。攻击者观察到或拦截了刷新的令牌,通过aqua-bot服务账户维持了访问权限。

第二次入侵:v0.69.4与标签投毒

3月19日17:43 UTC,aqua-bot向Trivy推送了一个恶意的v0.69.4标签,触发了自动化发布流程。该恶意二进制文件通过GitHub Releases、GHCR、Docker Hub、ECR Public、deb/rpm仓库和get.trivy.dev分发,在约21:36 UTC被移除前暴露了约3小时。

该恶意的Trivy二进制文件在载荷运行的同时执行了合法扫描器,从用户视角掩盖了入侵行为。在开发人员机器上(通过GITHUB_ACTIONS != "true"检测),它安装了一个持久性后门,作为systemd用户服务位于~/.config/systemd/user/sysmon.py,每5分钟向一个ICP容器轮询获取任意载荷。

标签投毒

更大的影响范围来自GitHub Actions。在数小时内,aqua-bot强制推送了76个trivy-action标签中的75个(0.0.1至0.34.2版本)以及全部7个setup-trivy标签至恶意提交。超过10,000个GitHub工作流引用了trivy-action。任何使用aquasecurity/[email protected]的工作流会静默解析至攻击者的代码。

该技术经过精心设计以避免检测:攻击者从master HEAD开始,仅替换entrypoint.sh,克隆原始提交的元数据(作者、邮箱、时间戳、提交信息),然后强制推送标签。这些伪装的提交甚至冒充了知名开发者:"rauchg"(Guillermo Rauch,Vercel CEO)用于actions/checkout注入,"DmitriyLewen"(Aqua维护者)用于Trivy提交。

检测特征:原本经过web签名且有GPG签名的提交缺失签名,时间线不可能(日期为2021年的提交其父提交来自2026年3月),以及每个被篡改的提交仅更改了一个文件,而原始提交更改了多个文件。

凭证窃取器的行为

恶意的entrypoint.sh包含一个三阶段载荷,共204行:

  1. 收集:在GitHub托管的运行器上,恶意软件通过无密码sudo提权至root,定位Runner.Worker进程,并通过/proc/<pid>/mem扫描其内存,搜索Actions存储掩码机密的JSON模式{"value":"<secret>","isSecret":true}。它还扫描了50+个文件系统路径以获取SSH密钥、AWS/GCP/Azure凭证、Kubernetes令牌、Docker配置和加密货币钱包。
  2. 加密:收集的数据使用RSA-4096混合加密(OAEP填充)的AES-256-CBC加密,捆绑为tpcp.tar.gz。只有攻击者的私钥可以解密。
  3. 窃取:主通道是通过HTTPS POST到scan.aquasecurtiy[.]org(aquasecurity.org的拼写错误域名,解析至45.148.10.212)。备用通道:如果INPUT_GITHUB_PAT可用,恶意软件会在受害者的GitHub账户上创建一个公开的tpcp-docs仓库,并将加密的捆绑包作为release资产上传。

合法Trivy扫描在恶意软件之后运行(第106-204行),因此用户看到的是正常的扫描输出。

CanisterWorm:npm实现自我传播

在Trivy被入侵后不到24小时,CI运行器中被窃取的npm令牌被武器化。3月20日20:45 UTC,TeamPCP部署了CanisterWorm,这是一种自我传播的npm蠕虫,代表了首次观察到使用互联网计算机协议(ICP)容器作为恶意软件C2基础设施。

该蠕虫的传播逻辑:

  • ~/.npmrc、项目.npmrc和环境变量中收集npm令牌
  • 通过/-/whoami认证,通过维护者搜索枚举可发布的包
  • 递增补丁版本,保留原始README,使用--access public --tag latest发布
  • 在finally块中恢复原始文件

28个@emilgroup包在60秒内被入侵。该蠕虫随后传播至@opengov(16个包)、@teale.io/eslint-config@airtm/uuid-base32@pypestream/floating-ui-dom。截至3月21日,Socket报告了跨64+个独立包的135个恶意构件

载荷架构是一个三阶段链:Node.js postinstall加载器解码一个base64 Python脚本,将其写入~/.local/share/pgmon/service.py,并安装为伪装成PostgreSQL监控的systemd用户服务(pgmon.service)。该Python后门休眠5分钟(沙箱规避),然后每50分钟向ICP容器tdtqy-oyaaa-aaaae-af2dq-cai轮询获取指向当前载荷的URL。终止开关:如果URL包含"youtube.com",植入物进入休眠状态。

载荷快速迭代:kamikaze.sh

3月22日,TeamPCP通过ICP容器在约5小时内轮换了6个载荷版本,每个版本在下一轮询时被所有受感染机器获取:

时间 (UTC)版本功能
~11:45v1一体化K8s DaemonSet,具备主机逃逸、systemd持久化、​针对伊朗的擦除器
12:45v2模块化加载器,获取kube.py,C2切换至Cloudflare隧道
13:00v3纯SSH/Docker蠕虫(扫描本地/24网段的22、2375端口)
13:25v3.1模块分离:kube.py(损坏的K8s)+ prop.py(SSH/Docker蠕虫)
14:56v3.2工作的ICP后门,kubectl重新启用,新的Cloudflare隧道
16:15v3.3WAV隐写术:载荷隐藏在音频文件中(bg_kube.wavbg_prop.wav

针对伊朗的擦除器(由ramimac发现)部署了一个独立的DaemonSet,检测Asia/Tehran时区或fa_IR区域设置并执行rm -rf / --no-preserve-root。这是该活动中唯一的破坏性、非窃取性组件。

截至16:00 UTC,TeamPCP还直接推送了恶意Docker Hub镜像(v0.69.5v0.69.6),绕过了GitHub release流水线。20:31-20:32 UTC,aquasec-com组织中的44个内部Aqua仓库被篡改。ICP容器于21:31 UTC被列入黑名单。

安全团队的IOC指标

网络指标

指标角色
scan.aquasecurtiy[.]org / 45.148.10.212主C2(TECHOFF SRV LIMITED,阿姆斯特丹)
tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0[.]ioICP容器死滴(2026/03/22列入黑名单)
plug-tab-protective-relay.trycloudflare[.]comCloudflare隧道(凭证窃取)
investigation-launches-hearings-copying.trycloudflare[.]comCloudflare隧道(kamikaze v2)
championships-peoples-point-cassette.trycloudflare[.]comCloudflare隧道(v3/v3.1)
create-sensitivity-grad-sequence.trycloudflare[.]comCloudflare隧道(v3.2/v3.3)
recv.hackmoltrepeat[.]com阶段1 PAT窃取

受影响的构件

组件受影响版本暴露时间
Trivy二进制文件v0.69.4(所有仓库),Docker Hub v0.69.5/v0.69.6~3小时
trivy-action标签0.0.1-0.34.2(75个标签)~12小时
setup-trivy标签v0.2.0-v0.2.5~4小时
npm(@emilgroup、@opengov等)135+个构件,64+个包各不相同

恶意容器摘要(v0.69.4)

根据Aqua的安全公告

架构摘要
Genericsha256:27f446230c60bbf0b70e008db798bd4f33b7826f9f76f756606f5417100beef3
amd64sha256:12c702212dee1cbec9471e9261501a3335963321fe76e60e5a715b5acd3c40a2
arm64sha256:2d7cee41048988eec27615412e7c6e2e21046f2b5faa888c24e11ca6764058ed

文件系统指标

路径组件
~/.config/systemd/user/sysmon.pyTrivy开发人员机器后门
~/.config/systemd/user/pgmon.serviceCanisterWorm持久化
~/.local/share/pgmon/service.pyCanisterWorm Python后门
/tmp/pglog载荷暂存
/tmp/.pg_state状态跟踪

狩猎查询

  • GitHub:在您的组织中搜索名为tpcp-docs或匹配tpcp-docs-*的仓库。其存在表明通过死滴备用通道成功窃取了凭证。
  • GitHub Actions:审查3月19日至20日引用aquasecurity/trivy-actionaquasecurity/setup-trivy的工作流运行。检查"Run Trivy"和"Setup environment"步骤中是否有异常输出。
  • Kubernetes:在kube-system命名空间中搜索名为host-provisioner-stdhost-provisioner-iran的DaemonSet。
  • systemd:检查匹配的sysmonpgmonpgmonitorinternal-monitor用户服务。
  • npm:针对@emilgroup@opengov@teale.io@airtm@pypestream范围中的包版本与预期版本进行审计。

现在需要做什么

如果您在2026年3月19日至22日期间使用了Trivy:​

  1. 固定到安全版本:Trivy使用v0.69.3或更早版本,trivy-action使用v0.35.0,setup-trivy使用v0.2.6。Aqua的安全公告有完整的版本矩阵。
  2. 轮换受影响工作流可访问的所有机密信息:GitHub令牌、云提供商凭证、Docker注册表令牌、SSH密钥。
  3. 在网络边界阻止C2域名和IP。
  4. 使用cosign签名对照已知良好摘要验证容器完整性。

长期加固措施:​

  • 将所有GitHub Actions固定到完整提交SHA,而不是版本标签。标签可以被强制推送;提交SHA不能。
  • 使用StepSecurity Harden-Runner或等效工具来监控和限制CI运行器的出站网络流量。
  • 审计pull_request_target工作流,确保它们永远不会用提升的权限检出不受信任的分支代码。
  • 将凭证轮换视为原子操作:首先撤销,然后重新颁发,旧的和新的凭证之间没有重叠窗口。

可独立验证的证据

本文中的多项声明可以使用gh CLI直接对照GitHub的公共API进行验证。我们于2026年3月23日运行了这些查询。您可以复现它们。

release缺口确实存在

阶段1中缺少了178个release(v0.27.0至v0.69.1):

bash
$ gh api repos/aquasecurity/trivy/releases --paginate \ --jq '.[].tag_name' | sort -V | tail -20 # ... v0.25.4 v0.26.0 v0.69.2 # ← gap: 43 minor versions missing v0.69.3

恶意的v0.69.4标签已被删除:

bash
$ gh api repos/aquasecurity/trivy/git/ref/tags/v0.69.4 # {"message":"Not Found","status":"404"}

冒充提交存在且未签名

提交1885610c仍在Trivy的对象存储中。它声称由"DmitriyLewen"(真实的Aqua维护者)创作,但没有签名。合法的DmitriyLewen提交通过GitHub web合并进行,由GitHub进行GPG签名。

bash
$ gh api repos/aquasecurity/trivy/git/commits/1885610c6a34811c8296416ae69f568002ef11ec \ --jq '{author: .author.name, email: .author.email, message: .message, verified: .verification.verified, reason: .verification.reason}' { "author": "DmitriyLewen", "email": "[email protected]", "message": "fix(ci): Use correct checkout pinning", "verified": false, "reason": "unsigned" }

setup-trivy恶意提交也存在相同的模式,该提交冒充了"Tomochika Hara"(thara):

bash
$ gh api repos/aquasecurity/setup-trivy/git/commits/8afa9b9f9183b4e00c46e2b82d34047e3c177bd0 \ --jq '{author: .author.name, verified: .verification.verified, reason: .verification.reason}' { "author": "Tomochika Hara", "verified": false, "reason": "unsigned" }

setup-trivy:仅一个标签存留

标签v0.2.0至v0.2.5在修复后未恢复。只有干净的v0.2.6保留下来,匹配Aqua的安全公告

bash
$ gh api repos/aquasecurity/setup-trivy/tags --jq '.[].name' v0.2.6

setup-trivy事件API实时捕获了事件响应:

bash
$ gh api repos/aquasecurity/setup-trivy/events \ --jq '[.[] | {type, actor: .actor.login, created_at, ref: .payload.ref, release: .payload.release.tag_name}]' # ... # 21:07 UTC - nikpivkin deletes v0.2.5 tag (incident response begins) # 21:34 UTC - itaysk adds emergency collaborator # 21:43 UTC - simar7 publishes clean v0.2.6 release

trivy-action标签现在全部带有v前缀

原始无前缀标签(0.0.1至0.34.2)在修复期间被删除,无法重新创建,因为GitHub的不可变release功能锁定了攻击者强制推送的版本。现在存在74个标签,全部带有v前缀:

bash
$ gh api repos/aquasecurity/trivy-action/tags --paginate \ --jq '.[].name' | wc -l 74 $ gh api repos/aquasecurity/trivy-action/tags --paginate \ --jq '.[].name' | sort -V | tail -5 v0.33.0 v0.33.1 v0.34.0 v0.35.0 # ← only safe tag (pointed to master HEAD during attack)

死滴窃取方法有效

Wiz的分析描述了一个备用通道,其中恶意软件在受害者GitHub账户上创建tpcp-docs仓库。这些仓库可通过公开搜索找到:

bash
$ gh search repos "tpcp-docs" --json fullName,createdAt [ {"createdAt": "2026-03-22T18:30:12Z", "fullName": "kaufmann-digital/tpcp-docs"}, {"createdAt": "2026-03-22T21:59:30Z", "fullName": "BEUMERGroupBot/tpcp-docs"}, {"createdAt": "2026-03-22T23:08:47Z", "fullName": "cloud-team-si-it/tpcp-docs"}, {"createdAt": "2026-03-22T23:25:10Z", "fullName": "dhoppe/tpcp-docs"} ]

全部创建于2026年3月22日。如果您在组织中找到tpcp-docs仓库,请假设该工作流运行中的所有机密信息已被泄露。

讨论区垃圾信息可见

讨论 #10420有772条评论。API显示协调机器人账户在同一时间戳发送的相同消息:

bash
$ gh api "repos/aquasecurity/trivy/discussions/10420" \ --jq '{title, comments}' {"title": "Why did this discussion about the Trivy incident get removed/closed", "comments": 772} $ gh api "repos/aquasecurity/trivy/discussions/10420/comments?per_page=5" \ --jq '[.[] | {created_at, body: .body[0:60]}]' [ {"created_at": "2026-03-19T23:56:52Z", "body": "To be explicit, it wasn't just discussion on the previous..."}, {"created_at": "2026-03-20T00:01:16Z", "body": "sugma and ligma, teampcp owns you"}, {"created_at": "2026-03-20T00:01:17Z", "body": "sugma and ligma, teampcp owns you"}, {"created_at": "2026-03-20T00:08:33Z", "body": "this solved my issue, thanks"}, {"created_at": "2026-03-20T00:08:33Z", "body": "thanks for the detailed explanation"} ]

相同时间戳(00:08:33 UTC)的相同消息来自协调的机器人账户。

tfsec和traceeshark确认横向移动

两个仓库在3月19日显示pushed_at时间戳,与Wiz记录aqua-bot注入一致:

bash
$ gh api orgs/aquasecurity/repos --paginate \ --jq '.[] | select(.name | test("tfsec$|traceeshark")) | {name, pushed_at}' {"name": "tfsec", "pushed_at": "2026-03-19T23:03:55Z"} {"name": "traceeshark", "pushed_at": "2026-03-19T22:56:37Z"}

更大的图景

这次活动是当前供应链攻击的基准。TeamPCP(也被追踪为PCPcat、ShellForce、DeadCatx3)将AI辅助侦察、CI/CD凭证窃取、自我传播的跨生态系统蠕虫、去中心化C2基础设施和破坏性载荷整合为单一行动。他们在5小时内迭代了6个载荷版本,比大多数组织召集事件响应团队的速度还要快。

令人不安的教训:一个配置错误的GitHub Actions工作流导致了一条链式反应,入侵了一家安全供应商的二进制分发系统、75个被10,000+个工作流使用的GitHub Action标签、64+个npm包、Docker Hub镜像和内部基础设施。CI/CD凭证泄露的影响范围不是线性的,而是复合的。

  • supply-chain-security
  • trivy
  • github-actions
  • npm
  • ci-cd-security
  • incident-response
  • malware

来自SafeDep博客的最新内容

关注以获取开源安全与工程的最新更新和洞察