程式碼比模型先執行
Pluto Security 找到 31 個偽裝成小型 Qwen2 測試檢查點的 Hugging Face 儲存庫。沒有一個裡面有模型。每個都放了一個 Python 檔案,只要用 trust_remote_code 載入模型就會立刻執行,安裝隱藏的挖礦程式、開機掛鉤,部分版本還會開一個反向 shell。主打的儲存庫通過了 Hugging Face 的掃描器,下載次數達 28,412 次,下架後還有一份幾乎一模一樣的副本留在線上。編碼代理寫測試時會去找小型測試模型。在 Bromure Agentic Coding 中,這次載入在 VM 裡執行,挖礦程式連不到礦池,Security Timeline 把每條連線對應到開啟它的工具呼叫,一次回溯就能移除酬載植入的東西。
權重檔只有 42 位元組,裡面沒有任何張量。真正幹活的是一個 Python 檔案,模型一載入它就執行,任何模型程式碼都還沒開始動。
10 月 29 日,Pluto Security 發表了 Load/Mine,分析一場自 9 月起在 Hugging Face 和中國的 ModelScope 上進行的攻擊活動。Pluto 在七個帳號中找到 31 個惡意儲存庫。它們偽裝成小型 Qwen2 測試檢查點,名稱像是 qwen2-tiny,合計記錄到超過 32,000 次下載。BleepingComputer 同一天也做了報導。
Pluto 用一句話總結這場活動:「The code runs before the model does」(程式碼比模型先執行)。
載入模型如何變成挖礦
這次攻擊沒有用下毒的權重,也沒有用 pickle 漏洞。它用的是一個功能。Hugging Face 的模型可以附帶自己的 Python 程式碼,只要呼叫端傳入 trust_remote_code=True,Transformers 函式庫就會執行那段程式碼。儲存庫的 config.json 在 auto_map 項目裡指定一個模組和一個類別,例如 modeling_evil2.EvilModel。Transformers 下載那個 .py 檔案並匯入它。檔案最上層的一切都會在匯入時執行,甚至在類別被傳回之前。
在這些儲存庫裡,model.safetensors 只有 42 位元組。裡面沒有模型。所有工作都由 Python 檔案完成:
- 它先扎根。 它在
/tmp/.cch/svc.py寫入一個每兩分鐘重新啟動挖礦程式的看門狗,並在~/.bashrc、/root/.bashrc和/root/.profile加上開機掛鉤。第一波的版本還會在authorized_keys植入一把名為qx-5090-op的 SSH 金鑰,並在/etc/cron.d/.sync-helper放一個 cron 工作。 - 它下載挖礦程式並藏起來。 下載器用空的代理伺服器處理器建構,所以會忽略企業可能仰賴的
HTTP_PROXY和HTTPS_PROXY設定。40.6 MB 的二進位檔改過 UPX 標記,讓upx -d拒絕解開。它從記憶體中的匿名檔案執行,程序名稱是[kworker/u20:1],透過 Kryptex 在 8048 埠挖 Pearl。 - 它開一扇門。 三個版本會加上連往
212.32.253.3443 或 9443 埠的反向 shell,斷線就重連。 - 它衝著主機去。 第二波一個相關的腳本會從特權容器掛載主機的磁碟,在底下的伺服器上取得 root。
掃描過關,又捲土重來
Hugging Face 用防毒軟體以及 Protect AI 和 JFrog 的合作工具掃描上傳內容。Pluto 說,這些工具找的是已知惡意程式和不安全的模型格式,不看 Python 檔案做了什麼。第二波的主打儲存庫 MENXIANGYU/qwen2-tiny「scanned clean while it collected 28,412 downloads」(累積 28,412 次下載期間掃描都沒問題)。一個合作掃描器對酬載檔案的判定是「not a machine-learning model」(不是機器學習模型)。
Pluto 把第一波通報給 Hugging Face 和 ModelScope,兩者都刪除了帳號。幾天之內,19 個新的儲存庫在新帳號下出現。Pluto 發布報告幾小時後,操作者公開了一個容器逃逸腳本。Hugging Face 停用了主打的儲存庫,但 BleepingComputer 證實,一份幾乎一模一樣的副本 htt0118/qwen2-tiny 仍在線上,帶著同樣的挖礦程式和反向 shell。
小型測試模型正是你的代理會找的東西
這場活動是為執行 Xinference、GPUStack 等推論平台的 GPU 伺服器打造的。但這個誘餌在開發者的電腦上一樣有效,因為編碼代理在為呼叫模型的程式碼寫測試時,會去找一個小型的 Qwen2 檢查點。請代理替你的嵌入管線或微調腳本加一個測試,它就會去找一個小到幾秒就能載入的模型。這場活動的名稱,qwen2-tiny 和它的變體,就是為了對上這種搜尋。
代理不必粗心也會開啟這個旗標。儲存庫附帶自訂程式碼時,Transformers 會停下來,告訴呼叫端要傳入 trust_remote_code=True。對一個正在修失敗測試的代理來說,這就是一個訊息裡印好修法的錯誤。它加上參數,重跑測試。測試過不過已經不重要:挖礦程式在匯入時就啟動了。
在筆電上,這個檔案以你的身分執行。~/.bashrc 裡的開機掛鉤會在你之後開的每個終端機裡執行。反向 shell 給操作者一個提示字元,你的 ~/.ssh、雲端憑證、gh 登入和 HF_TOKEN 都在手邊。Pluto 給載入過這些模型的人的建議是:把這台電腦視為已遭入侵,並輪替這個程序能碰到的每一組憑證。
Pluto 的強化建議中,有一句描述了你想要的環境:執行不受信任的模型程式碼時,要「with limited privileges, restricted egress and no production secrets」(權限受限、對外連線受限,而且沒有正式環境的機密)。
同一個測試,在 Bromure Agentic Coding 裡
Bromure Agentic Coding 在你 Mac 上的 Ubuntu VM 裡執行 Claude Code、Codex、Grok、Kimi 和 omp。每台機器都有自己的磁碟、自己的家目錄和自己的網路政策,政策在 Mac 上執行,代理碰不到。以下是同一個測試在 Bromure 機器上的情形,一次看酬載的一個階段。
挖礦程式連不到礦池
VM 開啟的每條連線都會經過 Mac 上 Bromure 的虛擬交換器和代理伺服器。80 和 443 埠在交換器就被轉進代理伺服器,所以帶著空代理伺服器處理器的下載器一樣會被攔下。這場活動用來繞過企業代理伺服器設定的手法在這裡無處可繞,因為攔截不依賴 VM 裡的任何設定。
在 Sandbox Policy 把 Unmatched traffic 設為 Deny,或把機器換到 OpenShell + Sentry 引擎,那裡除非有規則允許,否則一律拒絕。入門政策允許唯讀的 GitHub、npm 和 PyPI 加上 Ubuntu 的鏡像站,你再為下載加上 huggingface.co。這樣一來,挖礦程式連往 prl-us.kryptex.network:8048 的連線不符合任何規則,會被拒絕。連往 212.32.253.3 的反向 shell 也一樣,兩個埠都是。OpenShell 機器預設開啟 Tell the agent when policy blocks it,代理也會在對話中讀到什麼被擋了,以及它應該停下來問你,而不是花接下來十分鐘想辦法繞過去。
在開啟 Enforce binaries 的 OpenShell 機器上,每條連線還會對應到開啟它的程式,每個程式的雜湊在第一次執行時就被釘住。從記憶體匿名檔案啟動的挖礦程式,是沒有任何規則提到的程式。把程序名稱改成 [kworker/u20:1] 也改變不了這點,因為驗證器辨識的是二進位檔本身,不是它給自己取的名字。
你看得到是哪個工具呼叫做的
操作者選 [kworker/u20:1] 這個名字,是為了騙過在繁忙伺服器上看 ps 或 top 的人。在啟用核心哨兵的 Bromure 機器上,VM 的核心會透過一條代理殺不掉的私有通道,把每次程式啟動和每條連線回報給 Mac。每條被擋的連線都會連同背後的鏈出現在 Security Timeline,大致像 Tool pytest tests/test_embed.py: claude → bash → python → TCP 212.32.253.3:443。
在那一列上按兩下,network lineage 會呈現完整經過:你下的提示、代理在呼叫前寫了什麼、確切的指令、從代理到發起連線的那個程序之間的每個程序,以及是哪一層拒絕的。當一次測試執行撥向一個裸 IP 位址,你會看到一列紅色,裡面就是載入模型的那條指令。
在 Guardrails 設為 Quarantine 的代理看門狗,會根據同樣的訊號行動,不必等你。一分鐘內被擋的次數暴增,或是工作階段穩定後出現新的目的地,超過門檻,Bromure 就會在幾毫秒內切斷 VM 的網路並暫停它。警示會列出它看到的內容。Keep quarantined 會在網路仍然切斷的狀態下恢復 VM,讓你進去查看。
VM 裡的金鑰是替身
假設酬載的某個版本走得更遠,在一台你保留預設防火牆的機器上。操作者拿到一個 Ubuntu VM 的 shell,而裡面的每組憑證都是替身。環境中的 GH_TOKEN 和 ~/.git-credentials 裡的權杖都是假的,Mac 上的代理伺服器只在送往 GitHub 的途中才換成真的。把你的 Hugging Face 權杖加到 Other API keys,環境變數名稱設為 HF_TOKEN,主機篩選設為 huggingface.co,VM 拿到的就是只在那裡有效的 brm_… 替身。SSH 金鑰從來不會進入 VM:代理可以要求簽章,金鑰的位元組留在 Mac 上。
替身也是絆線。如果酬載把 GitHub 替身送到 HTTPS 的 GitHub 以外的任何地方,代理伺服器會以 HTTP 451 擋下請求、暫停 VM,並開啟一則警示,指出是哪組憑證、原本要送往哪台主機。
清理只要點兩下
Pluto 為受感染主機列的清理清單很長:找出看門狗、移除 shell 掛鉤、刪除 cron 工作、從六個 authorized_keys 路徑拿掉 SSH 金鑰、檢查有沒有惡意註冊,如果沒把握就從可信映像重建。Bromure 機器本來就有那個可信映像。Reset disk 會從基礎映像重新複製系統磁碟,把 /etc/cron.d/.sync-helper 和酬載寫在家目錄以外的其他東西一起帶走。Rewind home… 會把 /home/ubuntu 還原到先前某次開機的檢查點。Bromure 每次機器啟動都會建立一個,所以測試前的 ~/.bashrc 就在其中。你與主機共用的資料夾在 Mac 上,不受影響。
在開啟 Enforce binaries 的 OpenShell 機器上,這份清單大半根本寫不進去。代理沒有 root 權限,所以 /root/.bashrc 和 /root/.profile 的掛鉤、cron 工作,以及寫入 root 的 authorized_keys 都會被拒絕,每次拒絕都是 Timeline 裡的一列 Guest sandbox。
對外連線在 Mac 上決定
不管 VM 的設定怎麼寫,80 和 443 埠都會進代理伺服器。把未符合的流量設為拒絕,礦池和 shell 都會被擋。
每條連線都有名字
核心哨兵把每條連線對應到背後的工具呼叫和程序鏈。偽裝過的挖礦程式,照樣會顯示為那次模型載入。
是替身,不是金鑰
VM 裡的 GitHub、雲端和 Hugging Face 權杖都是假的,只在各自的主機上有效。送到別處就會觸發警示。
隨時一台乾淨的機器
Reset disk 和 Rewind home 會把 VM 還原到測試前的樣子,持久化也一併清掉。
把模型儲存庫當成套件看待
Pluto 在報告中列出了指標。如果你曾在啟用自訂程式碼的情況下載入這些模型,請檢查 /tmp/.cch/、帶有挖礦參數的 [kworker/u20:1] 程序和開機掛鉤,然後輪替那台電腦能碰到的每一組憑證。在對任何儲存庫開啟 trust_remote_code 之前,先讀它的 modeling_*.py,並釘住你讀過的那個 commit。
Pluto 的報告顯示,模型儲存庫在掃描器把它當檔案格式檢查的同時,實際上像程式碼儲存庫一樣運作。如果你的代理會替測試挑模型,遲早會載入這樣的檔案。在 Bromure 機器上,那個檔案在一個只有替身金鑰、對外政策由 Mac 執行的 VM 裡執行,事後你還能把機器還原到乾淨的檢查點。
想看 trust_remote_code 檢查本身失效的較早案例,請讀旗標說了不。想了解 Bromure 如何篩檢代理安裝的套件,請讀那個版本才發布兩小時。