返回所有文章
發佈於 · 作者 Renaud Deraison

你查的是錯的那四十分鐘

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 量到的第一筆收集,發生在惡意組建上線後的十八分鐘

2026 年 3 月 — 暴露視窗,按比例2月28日3月19日3月22–23日3月24日8月14日PAT 遭竊 · 輪替並非原子性trivy-action — 77 個標籤中 76 個被改寫~12 hsetup-trivy~4 hv0.69.4~3 hDocker Hub .5 / .6~10 hLiteLLM 1.82.7 / 1.82.840 分 — 所有人檢查的視窗2,188 個組織中的 2,085 個 — 95% — 全在此區間內被收集完畢重新歸因視窗與版本出自 Aqua Security 公告 GHSA-69fq-xp46-6x23。記錄層級的計數來自 SOCRadar,由 SecurityWeek 於 2026-08-14 報導。橫軸在每一天的內部為示意性質。
3 月這場行動裡每一個成品的視窗都以小時計,而且每一個都在指名它的公告出現之前就關上了。全世界真正據以行動的那個視窗——五天後 PyPI 上的四十分鐘——是最後才打開的一個。

從流水線裡出去的東西

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 做即時查詢。每 一個決定都會在發生的當下落入安全紀錄(視窗 → 供應鏈紀錄…)。

身分型控制 — 名字出現之後才動作成品已發布t + 0有人注意到數小時 … 數個月歸因公告、IoC控制才攔阻t + 18 分鐘機密已外流t + 5 個月後重新歸因時間下限 — 在抓取的當下動作成品已發布t + 0代理程式解析相依套件代理伺服器讀發布時間年齡 < 2 天 → 不供應改寫中繼資料,或回 451不需要名字,不需要公告
身分型控制必須等世界替那個成品取好名字。時間下限從來不會學到那個名字,也永遠不需要:它只是把發布時間戳,拿去跟一個你事先選好的數字比一比。

記憶體傾印撈得到什麼

竊取程式最高明的一手,是去讀 CI 執行器 worker 的記憶體,把日誌遮罩藏起來的機密撈 回來。它之所以有效,是因為遮罩只是化妝,而值本身握在行程手裡。

把同一招拿去對 Bromure 的工作區跑,撈回來的是佔位符,因為行程手裡握的就是佔位 符。線上邊界是這個產品的核心機 制:你設定的每一份憑證,在 VM 內部都以保留結構的假貨形式出現,而真值加密留在你的 Mac 上。主機端的代理伺服器會在請求離開 VM 之後才把它換到線上,而且只有在請求要送 往這份憑證當初鑄給的那台主機時才會換。

拿竊取程式自己的清單,逐條對著一台 Bromure VM 走一遍:

/proc/*/environ

ANTHROPIC_API_KEYsk-ant-api03-brm-…OPENAI_API_KEYsk-brm-…GH_TOKENghp_ 加上 36 個字元。結構都保留著,所以 claudegh 收下它們毫無怨言,而對其他任何人來說一文不值。

/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 自動機,會掃過每一個對外請求的標頭與內 文。當其中一個假貨出現在一個送往它鑄造範圍之外的主機的請求裡,代理伺服器就把這個 請求當成外洩企圖:

  1. 代理伺服器用 HTTP 451 拒絕該請求,且一個位元組也不轉發給目的地。
  2. Bromure 當場暫停 VM。
  3. 一則警示會指名發生了什麼:一次把工作階段憑證外送到非其鑄造對象主機的嘗試。

接著 Bromure 會把該工作區標記為已遭入侵,而下一次啟動必須先抹掉 VM 的磁碟映像與持 續性家目錄才會開機。你的權杖、SSH 金鑰與工作區設定都能撐過這一關。追蹤檢視器 (⇧⌘I)與 bromure-cli trace leaks 會顯示出事的主機以及那個確切的請求,讓你在一 分鐘之內就能判斷罪魁禍首是某個相依套件,還是一段被注入提示裡的指令。

一個範圍限定在 amazonaws.comAKIA 形狀鑄造物,出現在送往 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」