← 返回所有文章
发布于 · 作者 Renaud Deraison

provider 在密钥所在的地方运行

Aikido 发现了一个朝鲜的 RAT,以两个 Terraform provider 的形式分发在 HashiCorp 的注册中心上。terraform init 会下载 provider,terraform plan 会把它当成本地进程运行,而 provider 会读取你的云端凭证,因为 provider 本来就是干这个的。载荷只对某一位受害者的变量名解密,所以把包拉下来的扫描器看到的都是毫无作用的代码。被触及的十八台机器里有十台是 Mac。Bromure Agentic Coding 给那个可执行文件一台 VM、一把假的密钥,以及一份它可以调用的目的地清单。

业界为编码智能体建立的每一项控制,都位于命令的上游:批准计划、阅读 diff、把关 push。没有任何一项站在一个下载下来的 provider 可执行文件与 ~/.aws 之间。

你正要往一个 stack 里加一个容器。你向智能体要一个 Docker provider,它写了四行 HCL,你执行 terraform init。Terraform 从注册中心解析出这个 provider、下载它、 校验它的签名,然后放进缓存。接着你执行 terraform plan,Terraform 就把那个可执行 文件当成你机器上的一个进程启动,并通过 RPC 与它对话。过程中它会向你的云账号认证, 用的是你自己在用的那套环境变量和那份 ~/.aws 配置文件。

这每一步都是文档里写明的设计,而这个月它同时也是一条恶意软件的投递路径。

Aikido 发现了什么

9 月 22 日,Aikido 通报 了一个用 Go 写的远程访问木马,通过两个 Terraform provider 和两个 Go 模块分发。 The Hacker News 第二天跟进报道, 称这是攻击者首次利用 HashiCorp 运营的注册中心投递载荷。Checkmarx、JFrog 和 SafeDep 也补上了各自的分析。

这两个 provider 是 gocommunity-io/dockerd,下载数 222,以及 kreuzwenker/docker,下载数 1,449。后者与真正的 provider kreuzwerker/docker 只差一个字母,而后者宣称的下载量有 5,600 万。两个 Go 模块分别是 8 月 11 日发布的 gocommunity.io/orderedbtree 和 9 月 8 日发布的 gogets.dev/btreex。

Aikido 把这次行动关联到 Graphalgo,ReversingLabs 在二月记录过它,并归因于朝鲜的 操作者。同一把签名密钥自四月起就出现在 npm 的载荷里。人们是通过一份工作邀约走到 这些包面前的:一家假冒的 Web3 公司在 LinkedIn 或 Facebook 上接触开发者,发来一份 编程作业。作业的仓库是干净的,它拉进来的依赖才是恶意软件。

provider 是一个程序,而 Terraform 把密钥交给它

npm install 会执行包的安装脚本,然后退场。terraform init 取回的是一整个 程序,而运行那个程序才是重头戏。

Terraform 的 provider 是一个由 Terraform Core 通过 RPC 调用的可执行文件。 Terraform 运行它,因为运行它正是 provider 的定义。你不会找到可以摘掉的安装钩子, 也不会找到可以审计的生命周期脚本,因为在这里运行是功能本身,而不是解开 tarball 的副作用。

这个可执行文件还跑在你网络中通往生产路径最短的那个位置。Purple Shield 这样描述那台机器: “因此,一个恶意的 provider 会在部署你基础设施的那个人的机器上运行,带着那个人所 拥有的一切权限。”以及:“一台会执行 terraform apply 的笔记本,手边通常就有云厂商 密钥、Git 令牌、SSH 密钥和 Terraform 状态文件。”

npm 上的窃取程序得在你的家目录里四处翻找凭证。Terraform 直接把凭证递给 provider。

文档里的路径,以及被加上去的那个分支注册中心kreuzwenker/dockerinit负责部署的那台机器Terraform Coreplan → 启动,再 RPCprovider 进程你下载来的可执行文件凭证~/.aws、令牌、密钥没有人要求的那个分支,就在 provider 里sha256(containerName + networkID)相符 → AES 密钥 → 解密 → go run .其他任何配置,以及所有扫描器哈希不相符;载荷永远不会被解密它唯一指名的那个配置一个 RAT,回报主机名和硬件,然后等待18 台机器、725 条签到消息:Windows 3、Linux 5、macOS 10
这条链里没有任何漏洞利用。terraform init 从注册中心解析出 provider 并缓存可执行文件;terraform plan 把它当成本地进程启动,并用 RPC 与它对话;provider 读取环境中的凭证链,而这就是协议本身。Graphalgo 的 provider 只多加了一个分支:一个隐藏的入口,会对你自己的两个 Terraform 变量做哈希,只有当哈希值与一个硬编码的值相符时,才会用那个哈希作为 AES 密钥,解开藏在示例夹具里的载荷,并以分离的 'go run' 运行。对其他任何配置,这段代码都毫无作用。

只寄给一个人

这些 provider 只攻击一个目标,对所有其他安装它的人视而不见。

两者都在 /internal/provider/resource_docker_container_funcs.go 里带着一个隐藏的 入口,只有当 Terraform 变量 containerName 和 networkID 拼接后的 SHA256 等于 某个硬编码的值时才会触发。接着代码用那个哈希作为 AES 密钥,解开包内的一个文件 路径,把找到的东西解压,逐个解密每个文件,再以分离的 go run . 当成 Go 包运行。 他们把压缩包藏在 examples 目录里,文件名叫 import-resource.sqlite3。

也就是说,攻击者必须知道你给那两个变量起了什么名字,而这个攻击者知道:工作邀约 这个诱饵,就是他学到名字的途径。每一位把包拉下来的研究者、每一个扫描器,拿到的 都是坐在那里什么也不做的代码。你无法检测永远不会解密的东西。

第二阶段是一个远程访问木马。它先回报系统信息,包括硬件属性、操作系统、主机名, 以及这台机器上有没有 node,然后在两个地方等待指令。一个是 Slack:一个叫 portfolio-devs 的工作区、一个叫 frontend-devs 的频道,之后还有第二个工作区, 里面有个叫 qa-announcements 的频道。Aikido 在那里数到 1,240 条加密消息,最早可 追溯到 7 月 16 日。另一个是 Arbitrum Sepolia 测试网上的一份智能合约,位于一个硬 编码的地址,含有 1,402 笔交易,最早追溯到 8 月 6 日。恶意软件每三秒轮询一次 区块链,每十秒轮询一次 Slack。

这两条通道都是通往信誉良好目的地的普通 HTTPS。你的开发机器整天都在跟 Slack 说 话,而其中一台跟公开的 Ethereum RPC 端点说话,在任何一周团队有东西要上链时看起来 都很正常。同一批人在 Checkmarx 上周描述的那个 npm 包里也用了 Sepolia 合约,他们 大大方方地重复使用,因为大大方方地用也行得通。

Aikido 从攻击者自己的 Slack 里捞出了受害者清单:725 条签到消息,涵盖 18 个不重复 的主机名。三台 Windows、五台 Linux、十台 macOS。

在 Bromure 工作区里有什么不同

这场行动要的是凭证。在 Bromure Agentic Coding 里,VM 的 ~/.aws/config 并不含 你的密钥。它指向一个 credential-process 辅助程序,通过 virtio socket 交出你真正的 访问密钥 ID,配上一把 40 个字符的假密钥。手册列出了完全不需要配置就会采用它 的工具:每一个 AWS SDK、aws CLI、boto3,以及 terraform。provider 按它预期的方式 读取凭证链,拿到一份格式完好的配置,然后用一把一文不值的密钥给请求签名。

主机会在出口把那个签名修好。它的 resigner 认得发往 *.amazonaws.com 的流量,剥掉 客户机那注定失败的 SigV4 签名,在请求离开你的 Mac 之前用真正的密钥重新签。你的 terraform plan 照常工作。AWS 则对被偷走的那份凭证副本回以 InvalidSignatureException。

接着那把假密钥想要出去。代理会扫描每一个外发请求的头部和正文,寻找工作区自己的 占位值,用的是便宜到可以挂在全部请求上的 Aho-Corasick 自动机。把一把假密钥发往它 并非为之铸造的主机,比如一个 Slack webhook,代理就回 HTTP 451,一个字节也不转发。 VM 暂停。一行红色的 Credential brokering 落在 Security Timeline 上,Bromure 把这个工作区标记为已被入侵,于是你下次启动时会有选项清掉它的磁盘和家目录,同时 保留你的令牌、SSH 密钥和设置。

C2 仍然得离开这台机器。出向流量是一组有序的允许与拒绝规则,自上而下匹配,最后以 default allow 或 default deny 收尾。一个部署基础设施的工作区需要 registry.terraform.io、releases.hashicorp.com 和你的云 API,这让 portfolio-devs.slack.com 和某个 Arbitrum RPC 端点都不在清单上。主机会强制执行 这些规则两次:虚拟交换机匹配目的 IP 和从 DNS 侧录到的主机名,TCP 和 UDP 都匹配, 代理再按 TLS 服务器名匹配一次。交换机把 80 端口和 443 端口导进代理,不牵涉任何 环境变量,客户机也没有任何东西可以关掉。每一次裁决都会变成 Security Timeline 上 的一行 Firewall。

在负责部署的那台机器上provider 进程,以你的身份运行读 ~/.aws → 真正的密钥读 git 令牌、SSH 密钥、tfstate你的家目录就是波及范围外发流量,无人过问portfolio-devs.slack.com · 每 10 秒Arbitrum Sepolia RPC · 每 3 秒两个目的地的信誉都很好没有任何异常可以告警善后:轮换这台笔记本够得到的一切在 Bromure Agentic Coding 工作区里provider 进程,在客户机里读 ~/.aws → access key ID + 假密钥Ubuntu 24.04,在虚拟机监控器之下一块用菜单项就能换掉的磁盘外发流量,逐条匹配你的规则slack.com → 无规则 → default denySepolia RPC → 无规则 → default deny而如果假密钥被带出范围HTTP 451,一个字节也不转发,VM 暂停善后:清掉再启动,什么都不用轮换
同一个 provider 可执行文件,两台机器。在笔记本上它以你的身份运行,从凭证链里读到一把真的密钥,并且连得上 Slack 和一个公开的 RPC 端点,因为没有人写下针对它的规则。在 Bromure 工作区里,它跑在虚拟机监控器之下的 Ubuntu 客户机中,找到的是一组访问密钥 ID 配一把假密钥,出去的路上还会碰到一组有序的出向规则;把假密钥带往它并非为之铸造的地方会拿到 HTTP 451,并让 VM 暂停。真正该往上游走的 AWS 调用会在主机上重新签名,所以 terraform 照常工作,而具破坏性的 AWS 操作则会停在一个显示于 Mac 上的对话框。

删除由你批准

在 provider 与你的账号之间还有一道控制,而当攻击者要的是你的基础设施而不是你的 机密时,那才是最关键的一道。

AWS 凭证带有一份写入策略,新工作区默认是 Prompt before write。代理依据 X-Amz-Target 头或 Action= 参数里的动作名来分类每一次调用:Get*、List* 和 Describe* 是读取,一声不响地放行;而 Delete*、Terminate*、Revoke* 及其同类则算作破坏性操作。写入会停在一个标题为 Allow write on "<scope>" from workspace "<name>"? 的对话框,上面逐字显示该操作 而不是摘要,并列出 Allow for 15 minutes、Allow once、 Allow for the rest of the session 和 Don't allow。你拒绝,客户机就收到一个 AccessDeniedException,智能体会回报并继续往下做。

按下那个对话框的人是你,在你的 Mac 上。VM 里的进程够不着它,Slack 频道另一端的 那个人也够不着。

把工具链当成执行器看待

terraform init 和 terraform plan 就是下载并运行。要问一个 provider 在里面 会运行什么,因为它真的会运行。

不要让活的机密留在伸手可及之处

给 provider 一条能满足它的凭证链,以及一把一文不值的密钥。签名由主机在边界的 另一侧完成,并限定在那把密钥所属的目的地。

先给目的地点名

一个部署用的工作区需要的是一份简短稳定的清单:注册中心、发行主机、你的云 API。写下一次,把未命中的流量设成 Deny,死信箱合约就没有地方可以轮询。

让不可逆的操作开口问

让读取安静地跑。让 Terminate* 的代价,是一个显示在 VM 够不着的屏幕上的 对话框。

这里没有人找到漏洞

HashiCorp 的注册中心发出了别人向它索取的那个包。Terraform 校验了拿到的校验和, 并用它运行每一个 provider 的方式运行了这个 provider。没有沙箱被攻破,没有智能体 读到被污染的指令,也没有模型出错。

两个加起来 1,671 次下载的 provider 触及了至少 18 台机器,大多是 Mac,就坐在那里每 三秒轮询一次智能合约。撑起整条链的是文档里写明的行为。唯一新颖的部分——一段除非 你的变量恰好起了特定名字、否则一直保持加密的载荷——存在的理由,就是不让任何东西 找到它。

所以这些事要在会话开始之前就定下来,因为过程中你抓不到它们。你没办法扫描一个尚未 解开载荷的 provider,也没办法从发往 Slack 的流量里挑出外泄。把部署跑在密钥是诱 饵、目的地是你写下的清单、而被拿下的那台机器是你可以扔掉的地方。