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

最後一步是一則提示

9 月 18 日,Hacktron 公開了他們一路打進 OpenAI 私有單一倉庫的鏈條。起點是一張上傳到公開論壇的圖片,以及某個影像解碼器裡的堆積溢位,而它在上游的修補沒有帶任何 CVE。終點則是研究人員對某位員工的 Codex 帳號送出一則提示,代理程式便開了那個 pull request。沒有注入,也沒有任何東西失靈;代理程式只是照著握有工作階段的人所要求的去做。Bromure Agentic Coding 把這個寫入的決定放在工作階段之外,放在你的 Mac 上。

這條鏈的最後一步不需要任何漏洞利用。研究人員手上握著某位員工的編碼代理程式,於是 他們請它在私有單一倉庫開一個 pull request,它就開了一個。

7 月 25 日 UTC 早上六點,Hacktron 的三位研究人員已經在 OpenAI 的公開社群論壇上取得 程式碼執行。到了下午稍早,一個編號 1186742 的 pull request 已經躺在私有單一倉庫 openai/openai 裡,而他們沒有為此寫下任何一行程式碼。他們問了一個代理程式,代理 程式就把它提交了。

Hacktron 在 9 月 18 日公開了這條鏈, 值得一環一環讀下去,因為每一環都很平常。研究人員是 Harsh Jaiswal、Mohan Pedhapati 與 Rahul Maini。 The Hacker News 隔天做了報導。

一張圖、一次轉檔,和一個解析器

入口是論壇的圖片上傳。Discourse 用一個叫 FastImage 的 Ruby 函式庫檢查上傳的圖片, 它只讀檔案標頭中足以判斷尺寸與格式的部分。FastImage 不懂 HEIF,也就是 iPhone 照片 送達時所用的容器,因此 Discourse 把這類檔案交給 ImageMagick 的 magick 指令去轉 檔。ImageMagick 用 libheif 解碼 HEIC,而那是 C 寫的。

入口條件就只有這些。一種安全檢查器不認得的格式被送去轉檔器,而轉檔器呼叫了一個以 「長度欄位畸形即記憶體安全問題」的語言寫成的解析器。Hacktron 在 HEIC 解碼裡找到一 個堆積緩衝區溢位,並由此取得越界讀取與寫入的原語。

文書工作比臭蟲本身更糟。上游前一年就修好了那個溢位,卻從未把那筆提交標示為安全性 修補。它沒有 CVE、沒有公告,也沒有任何相依性掃描器讀得到的變更紀錄,所以 Debian 從未回移,而 Discourse 的 Docker 映像檔就這樣帶著完好無缺的臭蟲,在 Debian 12 上 出貨 libheif 1.19.7。全世界沒有任何掃描器有理由標記那個版本。Debian 後來發布了 DSA-6417-1,這個問題如今掛著 CVE-2026-32882——在有人真的利用它之後才配發的編號。

沒有人回報過的臭蟲,你掃不出來。你能事先決定的,是解析器倒下的時候,它站在什麼裡 面。

一次上傳到一個 PR解析一次 HEIC 上傳貼到公開論壇不需帳號權限FastImage安全的標頭檢查不支援 HEIFmagick改由它轉檔ImageMagick 呼叫 libheiflibheif 1.19.7堆積溢位,越界讀寫上游已修,無 CVE、未回移轉進程式碼執行community.openai.com7 月 25 日 06:00 UTC一個 SSO 缺陷論壇工作階段變成免互動的帳號接管某員工的 Codex已連到 GitHub 組織早已獲准寫入無需利用一則提示PR #1186742私有單一倉庫 openai/openai
鏈上的每一環都很平常。FastImage 不支援 HEIF,於是檔案交給 ImageMagick,ImageMagick 呼叫 libheif,而 libheif 裡有一個堆積溢位,它在上游的修補沒有帶 CVE,也從未抵達 Debian。另一個獨立的 SSO 缺陷把論壇上的程式碼執行變成帳號存取,再把帳號存取變成私有單一倉庫裡的一個 pull request。只有最後一步不需要漏洞利用:研究人員對一個早已獲准 push 的編碼代理程式送出一則提示。

漏洞利用花了一個晚上

Hacktron 從 7 月 23 日開始看那條上傳流程。7 月 24 日,他們用 Claude Opus 4.8 去攻 那個溢位;照他們的說法,模型在好幾個工作階段裡都吃力,做不出任何能對付位址空間配 置隨機化的東西——那是把行程的記憶體配置打亂、讓攻擊者無法預測東西在哪裡的防禦。把 堆積溢位變成在 ASLR 底下仍然可靠的執行,正是難的部分,也正是這類研究會卡上一週的 地方。

Anthropic 當晚發布了 Opus 5。Hacktron 開了一個全新的工作階段,三小時後就有了一個 能對本機 Mac 運作的 ARM64 漏洞利用。他們請模型把它移植到 Discourse 所用的 x86-64 與 jemalloc 組態,並在隔天早上六點確認了透過圖片上傳達成的本機程式碼執行。接著他 們讓 Claude 對自家的 Discourse Cloud 執行個體跑自主迴圈。十點時,它在那裡也拿到了 程式碼執行,並讀出 /etc/hosts 作為證明。

他們對這件事的意義很謹慎,而這個但書該放進任何一種解讀裡:

這並非全自主的駭客行動。要引導工作階段、驗證結果與處理揭露,人類的專業知識仍然 不可或缺。

說得對。三位熟練的研究人員把工具對準了目標,而他們的本事並沒有一夜之間改變。臭蟲 也沒變。在那兩個工作階段之間,唯一的新變數是模型,而它把一件屢試屢敗的漏洞利用工 作壓到了三小時。這件事所屬的整個行動,讓 Hacktron 花掉不到三千美元的 token 費用, 以及幾個小時的人力。

這個數字就是規劃的輸入值。一個從未拿到 CVE、蹲在某個檔案轉檔器遞移相依裡的臭蟲, 如今只要一台筆電的價錢就能變成可靠的原語。等公告是一種指望,而沒有人能靠把變更紀 錄讀得更快來跑贏那個時鐘。

最後一步是一則提示

論壇上的程式碼執行,並沒有給 Hacktron 任何 OpenAI 在意的東西。做到那件事的是第二 個問題。OpenAI 把單一登入接成了這樣:只要論壇工作階段遭竄,任何用過論壇的員工,其 ChatGPT 與 Codex 帳號就會在毫無互動的情況下被接管。用 Hacktron 的話說,正是這個 SSO 問題,把論壇的淪陷變成了對 ChatGPT 與 Codex 的存取。

某位員工的 Codex 連著公司的 GitHub 組織,於是研究人員做了最顯而易見的事。他們對那 個帳號送出一則提示,請它在內部單一倉庫開一個 pull request,PR 1186742 便出現在 openai/openai 裡。接著他們就停手,沒有去讀任何原始碼,提交了報告。

把這一步再讀一次。某個握有有效工作階段的人請代理程式做一件平常的事,代理程式就用 它本來就有的寫入權限做了。沒有注入,沒有越獄,沒有畸形輸入。所有人建起來的每一道 控制,都坐在那個工作階段的後面:登入、組織成員資格、倉庫權限。攻擊者一旦握有那個 工作階段,就等於全都握有。

編碼代理程式是一份長期有效的倉庫寫入授權,附著在一段對話上。任何碰得到那段對話的 人,都繼承那份授權。瀏覽器裡的 Codex 如此,筆電上的 Claude Code、Codex CLI 或 Gemini CLI 也如此——在那裡,代理程式在 ~/.git-credentials 裡帶著真的 ghp_ 權杖, 在環境裡帶著真的 GH_TOKEN。在筆電上,入口還會變多:倉庫裡一個被下毒的檔案、一則 issue 留言、套件的 postinstall 腳本、一個被挾持的終端機。

OpenAI 大約花了十四小時修補論壇。報告在那天早上送進去,OpenAI 在同一天 UTC 22:49 確認修補完成。Discourse 在週末推出自己的修補,並於 7 月 28 日發布公告 GHSA-vhm9-85gw-x335,賞金共計 6,500 美元。這個形狀裡落在你自己桌上的那一塊,是代理 程式。

寫入是在哪裡決定的

Bromure Agentic Coding 把「誰可以 push」的答案留在你的 Mac 上,留在代理程式的 工作階段之外,因為工作階段正是攻擊者想搆到的東西。那裡會發生兩件事。

第一,代理程式的環境裡沒有任何可繼承的權杖。工作區的 VM 拿到的是一個替身:ghp_ 加上 36 個字元,形狀做得讓 gh 和 git 都不會有意見,並且由你真正的權杖經 HKDF-SHA256 與每次安裝專屬的鹽推導而來,因此它在不同工作階段之間看起來從不輪替。 你真正的權杖加密存放在主機上,主機代理伺服器會在請求離開 VM 之後,於線路上把它替 換進去,而且只在目的地符合該權杖所簽發的主機時才替換。誠如手冊所言:VM 內部沒有任 何檔案、環境變數或行程會含有真的 API 金鑰、OAuth 權杖、AWS 機密或 SSH 私鑰。把那個 替身帶到它不該去的地方,代理伺服器會回 HTTP 451 而一個位元組都不轉送,並暫停 VM, 在 Security Timeline 寫下一列紅色紀錄。

第二件,正是這個故事所要的。每一份 git 託管服務的憑證都帶著一條寫入政策,在 Guardrails 面板裡按工作區設定,而新工作區的預設是寫入前詢問。讀取一聲不響就過 去。git push 在線路上以 git-receive-pack 的樣子抵達,算作寫入;對 pull request API 的 POST 也一樣。當其中一個抵達時,代理伺服器會扣住請求,在你的 Mac 上跳出一 個對話框,上面載的是原樣的操作而不是摘要:方法與路徑。你可以選允許 15 分鐘、 允許一次、允許本工作階段其餘時間,或不允許。拒絕會以一個普通的 API 錯 誤抵達代理程式,它能據此回報,而不是一條掛住的連線。

這個面板自己對執行位置的描述,就是最要緊的那一句:在代理伺服器上強制執行,因此 VM 裡被攻陷的代理程式無法繞過。

不管是誰在駕駛代理程式——你、埋在 fixture 檔案裡的提示注入,或是某個握著從你手上奪 走的工作階段的人——寫入都從同一個代理伺服器出去,並以同一種方式被分類。你 Mac 上那 個對話框就是授權,而握有你工作階段的攻擊者,並沒有握有你的 Mac。

筆電上的代理程式代理程式的工作階段你、注入,或任何奪走工作階段的人,都碰得到真憑證,伸手可及GH_TOKEN · ~/.git-credentials機器上任何行程都讀得到POST /repos/org/repo/pulls → GitHub權限由工作階段承載Bromure 工作區裡的代理程式VM 內代理程式的工作階段ghp_ + 36 字元的替身沒有真東西可繼承交換器改道 :80 與 :443主機代理伺服器分類它POST /repos/org/repo/pulls = 寫入git-receive-pack 也算Mac 上跳出對話框,附上原樣呼叫15 分鐘 · 一次 · 本階段 · 不允許權限由 Mac 承載
在開發者的筆電上,代理程式握著真的權杖,所以一則提示就夠了,權限由工作階段承載。在 Bromure 工作區裡,VM 握的是替身,主機代理伺服器在出去的路上把真權杖替換進去,而護欄在寫入抵達 GitHub 之前就先分類它。新工作區的預設值寫入前詢問,會在你的 Mac 上跳出一個載著原樣方法與路徑的對話框。不管握著工作階段的是誰,他握的都不是那台 Mac。

解析器的問題也會來到你的桌上

這條鏈的另一半同樣可以一般化,而把它算進成本裡的人並不多。

編碼代理程式整天都在讀自己沒寫過的檔案:跟著倉庫一起來的測試 fixture、附在錯誤回 報裡的圖片、你貼進對話裡的截圖、一份規格的 PDF、從發行頁抓下來的 tarball、一個字 型檔、前端要播放的影片。處理這些東西,意味著去呼叫 magick、ffmpeg、 pdftotext 或 unzip,或是綁進 Python 的某個影像函式庫,而它們每一個都是吃著別人 選定的位元組的 C 或 C++ 解析器。那正是讓論壇上傳一路抵達 libheif 的條件。代理程 式碰上這個條件的次數比你多,因為它在打開任何東西之前從不遲疑。

偵測在這裡沒有答案。libheif 的臭蟲在最要緊的那一刻沒有 CVE,所以沒有任何掃描器、 允許清單或版本鎖定會說半個字。你能挑的只有爆炸半徑,就這樣而已。

在 Bromure 工作區裡,那個解碼器跑在 Apple Virtualization.framework 底下的 Ubuntu 訪客中,那是一道 hypervisor 邊界而不是行程沙箱,而且那個訪客被 Bromure 在架構上就 當成不可信。影像解析器裡的堆積溢位,為攻擊者買到的是一台 Linux VM 裡的 shell:它的 憑證是替身,而它通往網際網路的路徑是主機上一組有順序的規則。虛擬交換器依目的地 IP 與從 DNS 竊聽到的主機名,在 TCP 與 UDP 上比對那組規則,代理伺服器再依 TLS 伺服器 名稱與 HTTP 方法比對一次。把未命中的流量設為拒絕,那個 shell 手上就只剩一張允 許清單,別無其他。交換器把 :80 與 :443 改道進代理伺服器本身,所以沒有任何代理 變數可供 shell 取消。規則的修改不必重開機就會抵達進行中的工作階段,而每一個判定都 會落成 Security Timeline 裡的一列 Firewall 紀錄。

假設解析器一定會倒

這個臭蟲在最要緊的那一刻沒有 CVE。就當解碼器會輸來規劃,並事先決定它倒下時站 在什麼裡面。

工作階段不等於授權

任何碰得到那段對話的人,都繼承代理程式的寫入權限。把 push 的決定放到對話碰不 到的地方。

別在 VM 裡留下可偷的東西

一個工具會接受的替身,在上游一文不值;對把它帶去別處的攻擊者來說,更是連一文 都不如。

替漏洞利用標個價

三小時,加上幾千美元的 token。把公告讀得更快,回答不了這個數字。

Hacktron 的文章是好研究,也誠實交代了其中有多少仍得靠人類掌舵。明年還會成立的,是 結尾那一段。這條鏈上的每一道防禦,都坐在一個身分的後面;而當帶著那個身分的工作階段 易手之後,它們就全都不再有任何意見了。有人請代理程式做事,代理程式就照做了。

如果你的代理程式能 push,那就問問 push 的權限住在哪裡,以及一個把代理程式所有東西 都奪走的攻擊者,會不會連那份權限一起奪走。在筆電上,他會。把這一個決定搬離代理程式 所在的機器,這條鏈的最後一步就有了可以停下來的地方。