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。
只寄給一個人
這些 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 憑證帶有一份寫入政策,新工作區預設是 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 的流量裡挑出外洩。把部署跑在金鑰是 誘餌、目的地是你寫下的清單、而被拿下的那台機器是你可以丟掉的地方。