那道指令的開頭是 npm install
2026 年 8 月 24 日,Cato Networks 的 CTRL 團隊公布了一場攻擊行動:它買下「codex macos download」的 Google 贊助搜尋結果,把開發者帶到一個偽裝成 OpenAI Codex 入口的 Google Sites 頁面,再請他們把一道指令貼進終端機,而那道指令的第一行,是真正的 Codex npm install。那一行之後的所有東西都是 base64。Bromure Agentic Coding 從不讓任何人去尋找程式設計代理:安裝指令是 Bromure 自家映像目錄裡的一段固定字串,它在用完即棄的 Linux VM 中執行,VM 裡的憑證都是誘餌,而只要把未匹配的對外流量設為拒絕,載入器的那三次取得就永遠離不開這台機器。
兩年來,業界一直在為程式設計代理「裝了什麼」建立控制。這場行動往前多走一步, 轉而汙染你「怎麼安裝」程式設計代理。那個步驟沒有登錄檔,沒有鎖定檔,也沒有 來源證明。它只有一個搜尋框、一個看起來沒問題的頁面,以及你自己放在鍵盤上的手。
一位開發者想在自己的 Mac 上試試 OpenAI 的 Codex CLI,於是在 Google 打下
codex macos download。第一筆結果是贊助的,排在 OpenAI 自家連結之上。它通往一個
乾淨的下載入口,標誌對、字體排版也對,託管在 sites.google.com 上。頁面說:打開
終端機,把這個貼上去。
他貼了。指令以 npm install 開頭,套件名稱也是對的。
8 月 24 日,Cato Networks 的研究團隊 CTRL 發表了 這場行動的分析, 同日獲得 Infosecurity Magazine、 SiliconANGLE 與 Techzine 轉載。最終酬載是一支 macOS 竊密程式。真正值得寫成報告的,是它的投遞方式。
頁面由 Google 託管,誘餌由別人託管。
Google Sites 是個網站產生器。任何人只要有帳號,就能在 Google 網域上擁有一個頁面, 而憑證、鎖頭圖示和網址的前半段,全都屬於 Google。那就是被租來的信任。
頁面本身什麼也沒有。Cato 在上面完全沒找到惡意程式碼。誘餌是透過一個指向攻擊者
基礎設施的 iframe 送達的;依 Cato 的分析,它還經由 Google 的靜態內容代理轉送,
所以連第二段跳轉看起來都像是 Google 在抓取什麼東西。Techzine 點名了其中兩個
iframe 主機:bright-links[.]com 與 swiftsaverfin[.]com。
這樣的切割替營運者買到兩件事。看得見的那個頁面永遠不變,所以它不會觸發信譽系統, 也不會被重新審查;而他們可以隨時在 iframe 的另一側替換酬載。Cato 數出三組彼此 獨立的基礎設施,其中第三組重複使用了第一組的一個 iframe 主機。
其餘部分讀起來就像一場成熟的廣告詐欺行動。活的誘餌放在一個混淆過的路徑 /codexx/
底下,而 /codex/ 送出的是無害的東西。營運者會對訪客做指紋辨識,任何看起來不像
合理 macOS 目標的訪客,都只會看到良性內容——那是他們對研究人員與沙箱的回答。網域
會輪替。酬載網址也會變。
第一行是真的
ClickFix 不會送你一個檔案。它給你一道指示,讓你自己把酬載搬過邊界。用 Techzine 的話說:「沒有任何檔案被下載,自然也沒有防毒方案能攔。」
貼上的指令,開頭是一段看起來完全合法、屬於 Codex 的 npm install 字串。整個把戲
就在這裡。一個正要安裝 Node CLI 的開發者看到 npm install,看到自己搜尋的套件
名稱,大腦就把這一行歸檔成正確。後面那團 base64 只隔著一個 shell 運算子,就在
同一行上,在複製框的折線之外。當一道指令的前半段正是你預期的指令時,你不會去讀
它的後半段。
解碼之後,它會抓一支遠端指令稿,再把它導進 zsh。接下來是三個階段。
第一階段是一支用死程式碼和未使用變數灌水的 shell 指令稿,裡面包著一團編碼過
的資料,最後交給 eval。灌水的存在,是為了打敗特徵比對,也是為了讓隨手翻閱檔案
的人放棄。
第二階段在做任何其他事之前,先帶著 event=pasted 這個參數向攻擊者回報。那是
一張投遞回執。另一端有人正在量測他們各個到達頁的「貼上率」,就像行銷人員量測
轉換率一樣。
第三階段把二進位檔下載到 /tmp/helper,執行 xattr -c 移除 macOS 加在所有
來自網路之物上的隔離屬性——正是這一步讓 Gatekeeper 的警告消失——接著標記為可執行
並啟動它。這支二進位檔是通用 Mach-O,所以在 Intel 和 Apple Silicon 上都能跑。
Cato 指出,營運者仍在持續開發這支載入器。較早的樣本使用帶壓縮的 base64;較新的 則使用 AES 加密的 gzip 容器,其金鑰是從散落在指令稿各處的變數重新組合而成。
至於最終酬載,Cato 把這套投遞框架連結到 Atomic macOS Stealer:載入器網址的
結構、遙測請求的形狀、/tmp/helper 這個暫存路徑、xattr 的移除、以更新為主題的
酬載網址。他們稱這些重疊之處既強烈又一致,但止步於為那支二進位檔命名——這是正確
的判斷,而且在轉述時值得一併保留。
掃描器上游的那一步
把業界花了兩年、為程式設計代理打造的那些控制排開來看。登錄檔信譽掃描。鎖定檔與 完整性雜湊。建置來源與證明。SBOM。新套件版本的最短存在時間閘門。對生成程式碼做 靜態分析。它們都有效,而且都從同一個假設出發:開發者已經有一套能用的工具鏈, 風險在下游,在他拿它來做什麼。
這場行動坐在這一切的上游。這裡的偽造品是代理本身,而它在任何登錄檔之前、 任何鎖定檔之前就已抵達,以一道開發者用自己的手打進自己終端機的指令抵達。掃描器 沒有任何成品可以評分,因為那個成品是網頁上的一句話。
鎖定目標是這門經濟的另一半。會去競標 codex macos download 的人,買的是一台屬於
「此刻正在架設程式設計代理」之人的機器:一台在一小時內就會裝著 API 權杖、SSH
金鑰、一份 ~/.aws/credentials 和一枚 GitHub PAT 的機器,而這些東西竊密程式全都
知道去哪裡找。安裝的那一刻,是待在現場最便宜的時間點。
我們寫過這件事的反面:在 FakeGit 與 AgentBaiting 裡,是代理 去搜尋,再把安裝步驟遞給人。這裡則是人去搜尋。兩者都成立,因為「先發現、再貼上」 就是軟體被安裝的方式,而這個迴圈的兩端都沒有掃描器坐鎮。
安裝指令從哪裡來
Bromure Agentic Coding 沒有這樣的迴圈,原因就在代理是從哪裡 來的。
你不會去尋找程式設計代理。Codex 是設定檔 Agents 面板裡的一個開關,就在 Claude Code 和 Grok Build 旁邊。把它打開,挑好它怎麼驗證身分,下一次工作階段開始時代理 就在那裡了,在設定檔的 VM 內某個 kitty 分頁中執行。
那個開關背後是一段固定字串。Bromure 從自家目錄佈建基礎映像,而 Codex 的條目只有 一行:
npx --yes @socketsecurity/cli npm install -g --silent @openai/codex
Claude Code 依同樣的模式是 @anthropic-ai/claude-code,Kimi Code 是
@moonshot-ai/kimi-code。套件名稱是編譯進應用程式的,不是被發現的。登錄檔是
registry.npmjs.org,不是某個 iframe 指定的任何主機。安裝過程被包在 socket.dev
的 CLI 之外,而取得動作會經由主機的 MITM 代理送出,設定檔的 Supply Chain
政策就在那裡生效:預設開啟的兩天存在時間閘門,加上 OSV 查詢、socket.dev 或 Delpi
過濾,以及你可以打開的安裝指令稿剝除。
那條路徑沒有搜尋結果,也沒有可供冒充的頁面。攻擊者花錢去汙染的那個管道,根本不在 這套工作流程裡;而「不存在」不是一個你得記得去操作的控制。
那就照樣貼上看看
架構必須撐過有人無視它的那一天。把這場行動拿過來,讓它贏,然後帶著它走一遍設定 檔:有人找到那個假入口,複製了指令,把它貼進自己做代理工作的終端機——在 Bromure Agentic Coding 裡,那是 VM 內的一個 kitty 分頁。
那支二進位檔跑不起來。 通用 Mach-O 是 macOS 的執行檔。客體是 Ubuntu。xattr
不是 Linux 指令,而 /tmp/helper 是一個位在「本來就是要被丟掉」的系統磁碟上的
路徑。Resources → Storage 提供給 /home/ubuntu 的 Erase home…,以及
Reset to base…——從共用、唯讀的基礎映像重新複製工作區的系統磁碟。
那三次取得出不去。 這條鏈的每個階段都需要網路:base64 解碼後那個拉取第一階段
的網址、event=pasted 信標,以及酬載下載。Guardrails → Outbound connections
是一張 pf 風格的規則表:動作、協定(tcp、udp、web、any)、主機或 CIDR、
連接埠,而 web 還可以列 HTTP 方法;由上往下比對,第一個命中者勝出,而
Unmatched traffic 可設為 Allow 或 Deny。把它設成 Deny,再列出你工作真正需要的
主機就好。bright-links.com 不在其中。Bromure 在主機端、在虛擬交換器與代理裡強制
執行這張表,所以客體對自己環境做的任何事,都改變不了答案。
值得坐下來細看的是第二階段。event=pasted 是營運者的轉換指標,是那個告訴他們某個
到達頁有效、而且有多有效的 ping。當這個請求死在主機上,他們拿到的不是一份感染
失敗報告。他們拿到的是沉默,而沉默讀起來就像一次沒有轉換的廣告曝光。
竊取拿回去的是佔位符。 Atomic 這一級的竊密程式,盯的是瀏覽器資料、加密錢包,
以及——在開發者機器上——憑證。在 Bromure 的設定檔裡,真正的值從來不會進入 VM。
通用 API 金鑰是以環境變數匯出的 brm_… 假值,由主機代理在傳輸途中替換掉。
kubeconfig 是合成的,搭配用過即丟的用戶端憑證。AWS 請求會在主機端重新簽署,所以
任何繞過代理的東西都會拿到 InvalidSignatureException。~/.docker/config.json
裡放的是一團假的 base64。私鑰 SSH 金鑰根本不在裡面等著被讀:
它們從來就不在。逐一為憑證打開
Require approval to use,每一次替換就會變成一個主機端對話框,附帶一段有時限的
授權:五分鐘、一小時,或這次工作階段的其餘時間。一個設定檔最多只分享你挑選的八個
資料夾,所以 Mac 的其餘部分並不是「多走幾層目錄」就能到的地方。
關聯分析早就在你螢幕上了。 Cato 自己的結論是,這場行動的任何單一階段都不會 讓它現形;要偵測到它,得把贊助搜尋的投遞、內嵌的網頁內容、終端機裡的執行,以及 對外活動放在一起關聯。當你的證據分散在瀏覽器、shell 和網路側錄裡,那是個難題。 在設定檔裡,它只是一個視窗:Security Log(Window → Security Log…)是一張 主機端的單一時序表格,記錄套件判定、防火牆決策、憑證替換與提示注入偵測。客體 程式碼無法編輯它,因為客體程式碼根本碰不到它。
把它打開
存在時間閘門預設就是開的,設在兩天。其他層次都是設定,而且值得花兩分鐘: Guardrails → Outbound connections,把 Unmatched traffic 設成 Deny,再為你 工作需要的主機建一份允許清單。Supply Chain → OSV vulnerability check,如果你 有金鑰,再加上 socket.dev 或 Delpi 過濾,還有安裝指令稿剝除。Credentials → 對任何能花錢或能刪資料的東西打開 Require approval to use。然後把 Security Log 視窗開在第二台螢幕上放一個星期,看看你的建置到底都在跟誰說話。
贊助搜尋結果還會繼續當那道前門,受信任網域上的網站產生器也還會繼續出租門廊。 Google 會下架這些頁面,然後又會有三個新的立起來,因為這門生意的算式是「每次點擊 幾塊美金」對上「一台裝著雲端憑證的開發工作站」。 安裝 Bromure Agentic Coding,從設定檔編輯器而不是下載頁啟用你 的代理,讓下一道被貼上的指令,落在一台生來就是要被丟掉的機器裡。