AI 点头说没问题的代码,5 天后被攻破
AI 点头说没问题的代码,5 天后被攻破
Snowflake 的一个公开仓库,前阵子被人打穿了。攻击者没费多大力气——只是在 GitHub 上开了一个 issue,标题里塞进一行精心构造的字符串。就这样拿到了 Snowflake 内部 Jira 的凭证,工程、安全合规、漏洞赏金三个项目随便看。
而引入这个漏洞的那次代码变更,最后的 squash commit 署名里,躺着一个特殊的"合著者":Copilot Autofix powered by AI。
一个教科书级的 shell 注入,被"顺手"改了出来
Snowflake 的 snowflake-connector-net 仓库里有个工作流 jira_issue.yml,负责把 GitHub issue 同步到 Jira。它原本的写法很稳:把 issue 标题通过 env: 变量传进去,再用 jq 拼 JSON。这是规避注入的标准姿势。
6 月 18 日,PR #1218 把它改成了直接字符串插值——${{ github.event.issue.title }} 直接拼进 shell 脚本,外面包着一层 echo '...'。
学过 shell 的人一眼就能看出问题:标题里放一个单引号,就能跳出 echo 的引号,执行任意命令。
更糟的是,工作流里那个 if: 条件看着像安全闸门,实际上在 issue 事件里 github.event.pull_request 永远是 null,条件恒为真,任何人都能触发。
AI 在流程里出现过两次,两次都没拦住
这件事最值得玩味的,不是"又有一个注入漏洞",而是 AI 在流程里的两个位置。
第一个位置是合入。PR #1218 的 squash commit 署名里,"Copilot Autofix powered by AI" 被列为共同作者。也就是说,这次引入漏洞的改动,Copilot 是挂了名的。
第二个位置是审查。Wiz 在 8 月 17 日更新了博客,澄清了一点:Copilot 是"共同作者",它检查了这个 PR 和代码变更,结论是 all-clear,没发现那个关键漏洞。至于那行代码本身是不是 AI 写的,反而说不清了。
换句话说,不管是 AI 写的还是人写的,AI 这道"审查闸门"都没拦住一个老掉牙的 shell 注入。
另一边,一个 AI 攻击者 5 天就找到了它
Wiz 的安全研究工具 Red Agent,是个自主运行的 AI。它扫 Snowflake 的 GitHub 组织,标记了 jira_issue.yml 的风险,构造 issue 标题,通过外带回调把一个 Jira 账号(qa@snowflake.net)的凭证偷了出来。
有个细节特别耐人寻味:Red Agent 第一次尝试用 # 注释掉后续代码,结果 # 把 TITLE=$(...) 的右括号也吃掉了,触发 bash 语法错误。它没有停下来等人,而是自己分析了报错,把 payload 改成 ; echo ' 正确闭合了 shell,几秒后成功回连。
从漏洞上线(6 月 18 日)到被发现(6 月 23 日),只隔了 5 天。好在 Snowflake 的响应也快:当天就修了漏洞、轮换了凭证,事后确认没有第三方趁虚而入。
一边是 AI 没拦住一个教科书级的注入,一边是 AI 在几天内自主完成发现、调试、利用。这个对比,比漏洞本身更值得警惕。
别急着怪 Copilot,真正的问题在你
把锅全甩给 Copilot 是偷懒。这个漏洞的本质不是"AI 写了坏代码",而是"团队把 AI 的审查当成了安全保证"。
Wiz 自己总结得很到位:AI 编码工具基于概率模式预测代码,可能无意中把已被弃用的不安全写法重新带回来。它缺少"当初为什么用 env:+jq 而不是直接插值"这种历史上下文——那条安全写法存在的意义就是防这类注入,AI 却把它当成了可以"优化"掉的冗余。
所以真正该做的,是把 AI 生成的 diff 当成人写的代码一样审。静态分析、安全扫描、人工复核,一样都不能少。
你今天可以做的三件事
- 检查自己仓库里所有
issues: opened触发的 workflow,看有没有把${{ github.event.issue.title }}或 body 直接拼进 shell。有的话改成env:+jq。 - 给 Copilot/Cursor 的自动修复设一条硬规矩:凡是用字符串拼接替换掉结构化解析(jq、参数化查询)的 diff,一律人工二次确认。
- CI 里敏感的 token 换成短时效凭证,GitHub Actions 的 runner 凭证别给读内部系统的权限。
参考来源:
✨ 本文由 DeepSeek 生成初稿,Claude 审核润色。