← All posts / Meta

25 分鐘拿到管理員權限:自主駭客代理 Strix 從三年前的映像檔挖出活_token,接管 Baseten 的 GitHub

自主滲透測試代理 Strix 只拿到一個網域名稱,就找到暴露的 Harbor registry、拉下 Docker 映像檔,並從 build history 挖出一個 2023 年就存在、至今仍有效的 GitHub 管理員權限 token——整個過程只花了約 25 分鐘。

25 分鐘拿到管理員權限:自主駭客代理 Strix 從三年前的映像檔挖出活_token,接管 Baseten 的 GitHub

Strix 是一家打造「自主駭客代理」的資安新創,他們原本想使用 Baseten——估值 130 億美元的推論平台——來跑自己的模型。但在把資料交給對方之前,這家資安公司做了一件符合本性的事:先掃描這家供應商。他們把自家的代理指向 *.baseten.co,不給任何憑證、不給原始碼,就讓它自己跑。

大約 25 分鐘後,代理帶回了一個有效的 GitHub personal access token,屬於 basetenbot 帳號——這個帳號握有 Baseten 主要產品儲存庫、驅動生產叢集的 GitOps 儲存庫,以及 Homebrew 發佈渠道的管理員與推送權限,另外還對多個私有儲存庫(包括按客戶區分的 repo)有讀寫權限。

藏著這個 token 的映像檔建於 2023 年 3 月。而這個 token 在 2026 年 7 月依然有效。

攻擊鏈如何展開

這條攻擊鏈值得細看,因為每個單獨步驟都很平凡——真正不平凡的是代理自主把它們串了起來。

  1. 偵察。 Strix 枚舉主機與憑證日誌, mapping Baseten 的對外攻擊面,在 gcp-us-east4-zlw.registry.baseten.co 發現了一個 Harbor 容器 registry。
  2. 匿名拉取。 其中一個 Harbor 專案是公開的。不需要任何 token 或身分驗證,代理就能列出儲存庫、鑄造範圍限於 repository:baseten/baseten-app:pull 的匿名 pull token,並下載映像檔的 manifest 與 blob。
  3. 第一組憑證——已失效。 在 baseten/baseten-app 裡,代理找到一組 AWS 金鑰,用唯讀的 sts:GetCallerIdentity 測試,回應是 InvalidClientTokenId——死金鑰。人為的分類流程可能在這裡就停下來,把「registry 暴露」當成結論回報。Strix 繼續挖。
  4. 活的那一個。 它拉下映像檔層、執行 TruffleHog,並直接檢查映像檔組態。在 history[].created_by——每個映像檔都會附帶的 Docker build history 中繼資料——裡,躺著一個傳統的 GitHub PAT,嵌在一條 RUN 指令裡,${GITHUB_TOKEN} 已被展開成實際值。
  5. 驗證。 對 GitHub 發一個唯讀的 GET /user,回傳 200 與帳號名稱 basetenbot。X-OAuth-Scopes: repo 標頭與組織成員檢查確認它屬於 basetenlabs。逐儲存庫的權限檢查確認了對三個 repo 的 admin/push,以及至少四個私有 repo 的讀寫權。

到此為止,代理停手了。它沒有 clone 客戶儲存庫、沒有 push 任何東西、沒有改任何組態——它改寫了漏洞通報信。

Token 為什麼會跑進 build history

根本原因是個熟悉的 Dockerfile 反模式。某次建置需要從 GitHub 拉私有依賴套件,於是有人把 token 當成 build argument 傳進去:

ARG GITHUB_TOKEN
RUN GITHUB_TOKEN=${GITHUB_TOKEN} bash -c '\
  if [[ "${GITHUB_TOKEN}" != "" ]]; then \
    git config --global --add \
    url."https://***@github.com/".insteadOf "git@github.com:"; \
  fi'

Docker 會把 build argument 記錄在映像檔的中繼資料與 history 裡——而這次它記下的是實際的 token 值。Docker 官方文件明確警告過這個模式。同一片段裡還藏著第二個問題:git config --global 會把帶認證的 URL 寫進映像檔內的 Git 設定檔,等於用第二種方式把憑證再持久化一次,就算修掉 build-arg 路徑也躲不掉。

正確的修法是使用 BuildKit secret mount 搭配不會持久化的暫時認證,然後同時檢查映像檔的層和 history——並撤銷舊 token,因為改 Dockerfile 對已經散佈出去的映像檔毫無作用。

為什麼這件事的意義遠超過 Baseten

必須公道地說,Baseten 的資安團隊處理得很好:通報在 7 月 13 日晚上 11:10 送出,Harbor 專案第二天早上轉為私有,token 在 7 月 14 日下午 4:34 前完成輪換,公司確認問題為 critical 並在 7 月 17 日關閉其餘發現。他們還寄了 T 恤給 Strix 致謝。公開揭露於 9 月進行,Baseten 也在 Hacker News 討論串中正式確認。

真正令人不安的教訓在別處:

  • 舊 artifact 是最軟的軟肋。 資安防護的注意力集中在執行中的程式碼與最新的原始碼儲存庫。一個三年前建置、公開可下載、帶著從未輪換的組織級管理員 token 的容器映像檔,就這樣待在注意力範圍之外超過三年。
  • 憑證衛生會放大一切。 拉一個依賴套件只需要對那個套件的讀取權。一個同時擁有產品 repo、GitOps 部署 repo 與發佈渠道管理員權限的建置 token,會把「洩漏」變成「供應鏈接管」。最小權限加上到期日,是事件與災難之間的差別。
  • 自主攻擊現在很便宜。 這次掃描不是什麼鎖定目標的行動——它只是一次「供應商導入檢查」,只是執行者從人類團隊換成了代理。同樣的 25 分鐘攻擊鏈任何人都能用,而且可以規模化。同一週,Hacker News 的討論之外,還有以牟利為動機的攻擊者用自主多代理框架在六小時內入侵數千組第三方憑證的報導,以及 7AI 記錄某代理以四天、17,600 個動作入侵 Hugging Face 的始末。再加上西班牙 AEPD 在 9 月 15 日提交的全球首件 AI 代理端到端資料外洩正式記錄,圖像已經完整:代理式攻擊已經從理論走進正式的事故檔案。

防禦檢查清單

Strix 對所有用容器跑 GitHub 依賴的人給出的建議,值得原味重述:

  1. 檢查哪些東西可以匿名拉取——包括老舊 tag 和多年沒人想起的專案。
  2. 閱讀 build history,而不只是檔案系統:docker history --no-trunc,或 config blob 的 history[].created_by 欄位。
  3. 把 secret 移出 build argument,改用 secret mount,並確認使用 secret 的指令不會把它寫回映像檔。
  4. 限縮建置 token 權限:唯讀、最小權限、設到期日。
  5. 對自己的系統跑一個自主攻擊者——因為如果代理能在 25 分鐘內從舊映像檔找到活的管理員 token,你會希望是你自己的代理先找到。

一次稱職滲透測試的邊際成本等於一個人類團隊一週工時的時代,已經結束了。攻擊面沒有變——變的是走完它的經濟學。