你查的是錯的那四十分鐘
2026 年 8 月 14 日,SOCRadar 公布了對 LiteLLM 供應鏈入侵事件的重新歸因:受影響的組織中有 95% 早在數天之前就已經被搜刮乾淨,而動手的是 Aqua Security 自家的掃描器 Trivy。五個月的事件應變全部瞄準了錯的時間視窗,因為那條鏈上的每一道控制,都得先等某個人把惡意套件叫出名字。Bromure Agentic Coding 的供應鏈層與憑證層不需要那個名字。
有人叫你去查,3 月 24 日那四十分鐘的視窗裡,你有沒有安裝過某個 Python 套件。五 個月後,SOCRadar 發現 95% 的受害者早在五天前就已經被掏空了,而掏空他們的,是他 們自己的漏洞掃描器。
8 月 14 日,SecurityWeek 的 Ionut Arghire 公布了 SOCRadar 的重新歸因, 對象是今年規模最大的幾起開發基礎設施入侵之一。SOCRadar 手上握有記錄層級的資料 集,涵蓋 2,188 個實體逐一的收集紀錄,並且問了一個沒人問過的問題:每個組織的資料 到底是什麼時候離開的?
其中 2,085 個組織,收集在所有人被警告的那些惡意套件發布之前就已經結束。這是 95%。SOCRadar 的結論只需要一句話:
「這個時間點對得上的是上游的 Trivy 入侵,而不是 LiteLLM 的安裝視窗。所有人報導 的那四十分鐘是收尾的一幕,不是整齣戲。」
研究人員早在 3 月就把酬載拆解過了。SOCRadar 改變的是時間線。五個月來,整個產業拿 著在有人指名那個壞東西之前什麼都做不了的控制,對著錯的視窗做事件應變。
收尾的一幕
那著名的四十分鐘屬於 LiteLLM。3 月 24 日,1.82.7 與 1.82.8 兩個版本在 10:39 UTC 上了 PyPI,撐了大約四十分鐘後被 PyPI 隔離。當時的指引要你把那天 16:00 UTC 之前所 有的安裝都當成可疑。
這個酬載值得停下來看看它擊敗了什麼。它是一個放進 site-packages 的 .pth
檔,litellm_init.pth。Python 會在直譯器啟動時執行 .pth 檔,不管有沒有東西去
import 安裝它的那個套件。沒有 setup.py 可檢查,也沒有安裝腳本可拆掉,所以
--ignore-scripts 以及所有建立在安裝腳本執行模型上的控制,全都從旁邊擦身而過。
The Hacker News 在 8 月 12 日
報導了這個機制,
並提到生態系層級的識別碼 CVE-2026-33634。搜刮到的東西送往
models.litellm[.]cloud。
攻擊者用一枚偷來的 PyPI 發布權杖把那些 wheel 推了上去,繞過 LiteLLM 自己的發布流 程。那麼,那枚權杖是哪來的?
整齣戲
Trivy。Aqua Security 的開源漏洞掃描器,數以萬計的流水線正是拿它來找這一類問題。
Aqua 的公告
GHSA-69fq-xp46-6x23
沒有淡化根本原因。一個被追蹤為 TeamPCP(Google 稱為 UNC6780)的威脅行為者,在 2
月底透過 pull_request_target 工作流程,從 Trivy 的 GitHub Actions 環境裡取走了
一枚個人存取權杖。Aqua 在 3 月 1 日揭露此事並輪替了憑證。公告接著寫道,那次輪替
「並非原子性的(並非所有憑證都同時被撤銷)」。攻擊者就坐在那次輪替的內部,Aqua
鑄出多少新的機密,他就收走多少。
十九天後,他把這些用了出去。3 月 19 日,他把 aquasecurity/trivy-action 77 個
版本標籤中的 76 個,以及 aquasecurity/setup-trivy 全部七個標籤,force-push
到惡意的提交上,於是一個釘在 @v0.28.0 的工作流程會抓到新的程式碼,而儲存庫上看
起來什麼都沒變。他用被入侵的服務帳號 aqua-bot,把 Trivy v0.69.4 發布到
GHCR、ECR Public、Docker Hub、deb 與 rpm。Docker Hub 上的映像 v0.69.5 與 v0.69.6
在 3 月 22 至 23 日跟上,用的是另一枚各自被入侵的憑證。
StepSecurity 的拆解 描述了流水線一旦執行它,被植入的竊取程式會做些什麼:
- 透過
/proc/*/environ讀取執行器上每一個行程的環境變數。 - 用 base64 編碼的 Python,經由
/proc/<pid>/mem傾印 GitHub Actions 執行器 worker 行程的記憶體。這會把執行器在日誌裡遮罩掉的機密撈回來,因為遮罩只是一 道日誌過濾器,而行程記憶體裡放的是未遮罩的值。 - 掃遍檔案系統,尋找 SSH 私鑰、git 憑證,AWS、GCP 與 Azure 的權杖,Kubernetes 的 secret、Docker 設定、資料庫憑證、Terraform 狀態,以及加密貨幣錢包。
- 把這一切用一把寫死在程式裡的 RSA-4096 公鑰做混合加密封起來,送往
scan.aquasecurtiy.org。
再讀一次那個主機名稱。那是廠商自己的網域,兩個字母對調了位置。對網域信譽情資來
說,或是對凌晨兩點掃過對外流量的分析師來說,一台掃描器在跟看起來像
aquasecurity.org 的東西講話,是整頁裡最不值得留意的一行。
如果上傳失敗,竊取程式會在受害者自己的 GitHub 帳號上開一個名為 tpcp-docs 的公開
儲存庫,把戰利品當成 release 附件掛上去。
SOCRadar 量到的第一筆收集,發生在惡意組建上線後的十八分鐘。
從流水線裡出去的東西
Hudson Rock 取得了那份封存檔,Help Net Security 在 8 月 13 日 報導了它的大小: 153 GB、433,909 個檔案、118,829 份 CI 執行器傾印,對應到 2,488 個企業網域。 CloudSEK 獨立清點出約 434,000 個檔案,把暴露規模放在接近 2,500 個組織、超過 430,000 條流水線。Hudson Rock 的技術長 Alon Gal 稱之為一場「全球性的道德揭露行 動」,並說這個量級「把我們推進了一個在應變方式上完全嶄新的世界」。Kevin Beaumont 的判讀是:「這是一場因為 AI 安全做得差而造成的大規模供應鏈入侵。」
SOCRadar 逐組織的拆解走得更遠。超過一千個組織丟失了 JWT 與驗證權杖。數百個丟失了 私鑰、AWS 存取金鑰、GitLab 權杖、OpenAI API 金鑰、Slack webhook、GitHub Actions 權杖與 Google API 金鑰。有一個組織丟失了約 3,477 項個別的機密。一千一百個組織暴露 了提交者的電子郵件位址。執行器涵蓋 GitHub Actions、GitLab CI、Jenkins、Bitbucket、 CircleCI 與 Buildkite。德國、巴西與法國受創最重,而這些收集成果現在正在 Telegram 上被轉手。
那條鏈上的每一道控制都需要一個名字
把 3 月時存在的防禦一字排開,然後記下每一道在能夠動作之前需要什麼。
公告需要 Aqua 先辨識出入侵。下架需要 PyPI 先辨識出套件。IoC 清單需要有人把
scan.aquasecurtiy.org 讀成敵意的東西,而不是讀成一個大家都信任的廠商名字的錯
字。「你有沒有在 10:39 到 16:00 UTC 之間安裝」這張檢查清單,需要挑對那五個小時。
網域信譽需要那個網域先有信譽。那份告訴 2,085 個組織他們一直在看錯的一週的重新歸
因,需要一份外洩封存檔浮出水面,還需要一支研究團隊在五個月後坐下來把它翻完。
上述每一項都是身分型的控制:它要等到有人替某樣東西命名之後,才對那樣東西動作。 命名是生態系分享知識的方式,而且行得通,所以這不是在抱怨那些寫公告的人。這是在談 操作的先後順序。身分型控制在命名之後才抵達,而在這場行動裡,每個成品都活了三到十 二小時。從發布到第一筆收集之間的那十八分鐘,回答了這個順序夠不夠好的問題。
在這裡能改變結局的控制,是那些不需要名字的。
一只時鐘,不是一位神諭
Bromure Agentic Coding 讓每一次套件抓取都先過主機端的 MITM 代理伺服器,才讓任何一 個位元組抵達 VM,而 年齡關卡是唯一一個預設就開著的 供應鏈層。它拒絕任何比截止值更年輕的套件版本。出廠預設是兩天。
它不做任何查詢。它對維護者、發布者、簽章或名字都沒有意見。它讀發布時間戳,套上一 個下限,前提是:一個剛出爐的版本,最有可能就是一小時前被劫走的那個;而等這個視窗 過去,對一個正在工作的開發者來說幾乎不花什麼成本。
面對這場行動,那就是整場仗。LiteLLM 1.82.7 與 1.82.8 存在了四十分鐘。兩天的下限意
味著,從 VM 內部看,它們根本從未存在過。代理伺服器會把太新的版本從登錄檔的中繼資
料裡剔掉,並把 latest 與其他 dist-tag 重新指向仍然存活的最新版本,於是
pip install litellm 和所有 semver 範圍都會解析到 1.82.6,不報錯,也沒有任何東西
讓代理程式繞過去。如果你用精確釘選去要一個被下毒的版本,成品抓取的後備關卡會回一
個 451,內文會把該套件的實際年齡跟所要求的最低值擺在一起。pip 會原樣印出來:
Bromure Supply-Chain Security blocked this request: pypi package
[email protected] published 34 minutes ago — policy requires 2 days minimum
這道關卡涵蓋 npm、PyPI、Cargo、RubyGems 與 Packagist,都有完整的發布時間資料。至 於 pip,由於預設的 PEP 503 索引不帶時間戳,它會對 PyPI 的 JSON API 做即時查詢。每 一個決定都會在發生的當下落入安全紀錄(視窗 → 供應鏈紀錄…)。
記憶體傾印撈得到什麼
竊取程式最高明的一手,是去讀 CI 執行器 worker 的記憶體,把日誌遮罩藏起來的機密撈 回來。它之所以有效,是因為遮罩只是化妝,而值本身握在行程手裡。
把同一招拿去對 Bromure 的工作區跑,撈回來的是佔位符,因為行程手裡握的就是佔位 符。線上邊界是這個產品的核心機 制:你設定的每一份憑證,在 VM 內部都以保留結構的假貨形式出現,而真值加密留在你的 Mac 上。主機端的代理伺服器會在請求離開 VM 之後才把它換到線上,而且只有在請求要送 往這份憑證當初鑄給的那台主機時才會換。
拿竊取程式自己的清單,逐條對著一台 Bromure VM 走一遍:
/proc/*/environ
ANTHROPIC_API_KEY 是 sk-ant-api03-brm-…。OPENAI_API_KEY 是
sk-brm-…。GH_TOKEN 是 ghp_ 加上 36 個字元。結構都保留著,所以 claude
和 gh 收下它們毫無怨言,而對其他任何人來說一文不值。
/proc/<pid>/mem
傾印行程撈到的,是跟環境變數裡一模一樣的佔位符。記憶體更深處也沒有藏著什麼有 特權的副本:主機端的代理伺服器是在 VM 外面、在位元組已經離開之後才做替換的。
SSH 私鑰
一把也沒有。SSH_AUTH_SOCK 指向的是一座架在 vsock 埠 8444 上的 ssh-agent 橋
接。私鑰的位元組從未進入客體,也無法從客體裡讀出來。Bromure 在設計上就讓你
macOS 的登入代理不被暴露。
雲端、k8s、Docker、資料庫
~/.aws/config、~/.kube/config、~/.docker/config.json、
~/.git-credentials、~/.config/doctl/config.yaml。全都在,全都有內容,也全
都是假的:brm-k8s-…、brm-docker-…、brm-db-…、glpat- 加 20 個字元、
dop_v1_ 加十六進位。
Bromure 用 HKDF-SHA256,從真值加上一份每次安裝各自的 32 位元組鹽,導出每一個假 貨,因此一個會對自己的金鑰做指紋的工具(Claude Code 會快取一份金鑰雜湊)永遠不會 看到憑證在不同工作階段之間換過。也沒有什麼開關可以忘記打開。代理伺服器是 VM 通往 網路的唯一路徑,所以繞過它的請求身上帶的是佔位符,會在上游驗證那一關失敗。這道邊 界在結構上就是失效即關閉。
從沒有人見過的那個域名仿冒
現在來看外送。3 月 19 日,scan.aquasecurtiy.org 沒有信譽、沒有歷史,也沒有任何
理由出現在誰的封鎖清單上。TeamPCP 挑上它,正是為了這一點。
Bromure 的入侵偵測一樣也不讀。它盯的是你的憑證,而不是目的地的信譽。一具用工 作區自己鑄出的那些假貨建成的 Aho-Corasick 自動機,會掃過每一個對外請求的標頭與內 文。當其中一個假貨出現在一個送往它鑄造範圍之外的主機的請求裡,代理伺服器就把這個 請求當成外洩企圖:
- 代理伺服器用 HTTP 451 拒絕該請求,且一個位元組也不轉發給目的地。
- Bromure 當場暫停 VM。
- 一則警示會指名發生了什麼:一次把工作階段憑證外送到非其鑄造對象主機的嘗試。
接著 Bromure 會把該工作區標記為已遭入侵,而下一次啟動必須先抹掉 VM 的磁碟映像與持
續性家目錄才會開機。你的權杖、SSH 金鑰與工作區設定都能撐過這一關。追蹤檢視器
(⇧⌘I)與 bromure-cli trace leaks 會顯示出事的主機以及那個確切的請求,讓你在一
分鐘之內就能判斷罪魁禍首是某個相依套件,還是一段被注入提示裡的指令。
一個範圍限定在 amazonaws.com 的 AKIA 形狀鑄造物,出現在送往
scan.aquasecurtiy.org 的 POST 裡,第一次嘗試就會觸發那具自動機。偵測器從沒聽過
那個網域,也不需要聽過。它知道的是:這份憑證只有一個合法的目的地族群,而這一個是
另一個。沒有東西需要啟用;它在每一個請求上都在跑。
你早就擁有的那條時間線
這裡的紀錄問題,活得比事件本身還久。
研究人員是從外面重建時間線的,材料是一份 153 GB 的外洩封存檔,而它得先浮出水面、 被取得、被篩過,才有人說得出哪個視窗才是關鍵。受害者沒辦法從自己的紀錄裡回答,因 為那些紀錄漏掉了決定一切的兩個事實:他們的流水線在什麼時候抓了哪些成品,以及由此 產生的行程把流量送去了哪裡。
Bromure 的工作區把這份紀錄當成運作的副產物生出來。每一次抓取都穿過主機端的代理伺
服器,所以安全紀錄會以即時串流的形式保有每一筆年齡關卡、OSV、socket.dev 與 451 的
判定。每一個請求都穿過同一台代理伺服器,所以 bromure-cli trace ls 會按工作區給你
主機、方法、狀態、延遲,以及 swap×N / LEAK×N 標記,trace hostnames 會列出一
個工作階段接觸過的每一個不同主機,trace summary 則把整份聚合起來。這一切在你的
Mac 上靜態儲存時,都在保險庫主金鑰之下保持加密。
於是 SOCRadar 在 8 月才回答出來的那個問題——我被收集了嗎,什麼時候?——變成一次你 自己在 3 月就能對著自己的資料跑的兩分鐘查詢,不必等任何人先公布正確的名字。
這就是全部的論點。沒有人能讓生態系替東西取名字取得更快:3 月讓我們看到,連廠商自 己的事件應變都留下了一條十九天的尾巴和一個錯的歸因。你能改變的,是你的結局要不要 取決於那個名字。在登錄檔前面放一只時鐘,在跑程式碼的那台機器裡放上佔位符,並且為 它抓了什麼、跟誰講過話,留下你自己的紀錄。
資料來源:SecurityWeek,「Trivy, Not LiteLLM Behind the 2,500 Org Compromise」(2026 年 8 月 14 日) · Aqua Security 公告 GHSA-69fq-xp46-6x23 · StepSecurity,「Trivy Compromised a Second Time」 · The Hacker News(2026 年 8 月 12 日) · Help Net Security(2026 年 8 月 13 日) · CrowdStrike,「From Scanner to Stealer」