ChatGPT 共享連結被利用完成帳號接管漏洞
We Took over ChatGPT Accounts
研究團隊發現,透過 ChatGPT 的共享功能,攻擊者可在受害者已登入的情況下執行任意 JavaScript,進而取得受害者帳號的私人對話與 API 許可權。
- 漏洞原理:透過圖表載入 MathLive 與 Preview 互動,最終在主頁建立未 sandbox 的 iframe,執行指令碼。
此研究揭示 ChatGPT 共享功能可被利用進行帳號接管,說明跨功能安全隔離失效的危險,對開發者與使用者皆有警示意義。
在 2026 年 10 月,我們發現了一種方法,能將普通的公開 ChatGPT 分享轉變為由攻擊者控制的 JavaScript,並在開啟者的已登入會話內執行。
攻擊者可以建立惡意對話,使用 ChatGPT 的正常分享功能發布,並將連結傳送給另一位使用者。當受害者開啟該分享時,payload 以 chatgpt.com 方式執行,並取得受害者已驗證的網頁會話存取權。
我們確認此程式碼能列舉受害者的對話清單,擷取從未被分享給攻擊者的私密對話內容,並將該對話的資料傳送至外部伺服器。payload 不僅限於修改頁面或顯示警告訊息,它還能直接以受害者身分呼叫 ChatGPT 的已驗證 API,基本上讓我們能接管受害者的 ChatGPT 帳號。
受害者不需要安裝任何應用程式、加入專案、貼上程式碼或開啟開發者工具。開啟分享連結即為唯一所需操作。
OpenAI 在 10 月 26 日修正了此問題,並將此報告分類為 P1。
此漏洞鏈路始於兩項看似無害的功能:一個圖表與一個 HTML 預覽。
flowchart TD
A["Attacker publishes a ChatGPT conversation"] --> B["Victim opens the public share"]
B --> C["HTML Preview crosses into the main chatgpt.com page"]
C --> D["JavaScript runs in the victim's signed-in session"]
D --> E["Any action on chatgpt.com as the victim:<br/>read private chats and files, change the account"]技術流程說明
它始於一個圖表與一個預覽
這項研究的起點問題很簡單:如果預覽程式碼被封裝在另一個網站中,它仍能與什麼進行通訊?
ChatGPT 可以利用其預覽功能將 HTML 程式碼塊轉換為小型可執行網頁。由於該頁面可能包含任意程式碼,ChatGPT 明智地將其執行於不同網域的受限框架中。預覽能執行 JavaScript,但瀏覽器將其視為不同網站,因此無法直接檢視其周圍已登入的 ChatGPT 頁面。
起初,這種隔離看似完全如預期運作。預覽內的程式碼無法讀取 parent.document(即主 ChatGPT 頁面)。這本應是結束。
對話中還包含一個正常的互動圖表。幕後,該圖表載入了一個名為 MathLive 的數學函式庫。載入它不僅繪製圖表,還在主 ChatGPT 頁面安裝了一個監聽器,用於接收與 MathLive 螢幕鍵盤相關的訊息。
瀏覽器呼叫相關 API postMessage。即使瀏覽器阻止兩個框架互相閱讀對方頁面,這也是兩個框架交換資訊的合法方式。可以把它想像成一個信箱:受限的預覽無法進入主頁面,但仍能透過信箱投遞訊息。
只有接收端檢查訊息傳送者,這才是安全的。
MathLive 在我們關注的設定中未進行此檢查。其監聽器接受了一條能改變虛擬鍵盤佈局的訊息,而該佈局允許包含 HTML。圖表安裝了接收端;預覽則提供了不可信的傳送端。兩個不相關的功能意外地創造了一條跨越沙盒的路徑。
本不應該工作的訊息
預覽發送了兩條訊息。第一條將虛擬鍵盤佈局替換為我們的 HTML。第二條指示 MathLive 顯示鍵盤,導致其渲染新的佈局:
top.postMessage({
type: "mathlive#virtual-keyboard-message",
action: "update-setting",
layouts: [{
label: "Compatibility",
layers: [{ id: "compatibility", markup }]
}]
}, "*");
top.postMessage({
type: "mathlive#virtual-keyboard-message",
action: "show",
animate: false
}, "*");MathLive 辨識到公開訊息型別,但其頂層控制器使用了 none 的來源政策,表示它接受來自任何網域的訊息。它也未要求 update-setting 訊息必須來自先前批准的視窗。任何能接觸到監聽器且知道訊息格式的人都能替換佈局。
當鍵盤開啟時,MathLive 將提供的佈局合併成一個 HTML 字串,並將其指派給真實 ChatGPT 頁面中的 innerHTML。一個名為 createHTML 的輔助程式最初看起來好像會對該值進行清理。實際上它並沒有;它將字串標記為可信並將我們的 HTML 原封不動地傳遞過去。
這很令人興奮,但我們仍然沒有 JavaScript 執行。透過 innerHTML 插入的
下一步是使用 iframe 與 srcdoc 的一個瀏覽器功能,該功能允許嵌入的框架在屬性中直接攜帶其 HTML:
<iframe srcdoc="..."></iframe>MathLive 將此框架插入主 ChatGPT 文件,且未使用 sandbox 屬性。這給了我們兩個有用的特性。首先,srcdoc 建立了一個全新的檔案,外部指令碼可以在其中執行。其次,因為主 ChatGPT 頁面建立了該框架,瀏覽器賦予它 https://chatgpt.com 網頁身份。新框架內的程式碼因此能夠存取父頁面。
使用 YouTube JSONP 迴避 CSP
ChatGPT 的內容安全政策(CSP)再次阻止了我們。CSP 控制頁面可以載入哪些指令碼,並且政策正確地阻擋了內聯 JavaScript。然而,它允許來自 YouTube 的指令碼。
YouTube 的 oEmbed 端點支援類似 JSONP 的回呼引數。JSONP 回應是 JavaScript 而非普通 JSON。該端點試圖拒絕無效的回呼,但它在回應中先放入回呼再回傳驗證錯誤:
https://www.youtube.com/oembed?callback=globalThis.__marker=31337;void回應的開頭如下:
// API callback
globalThis.__marker=31337;void({
"error": {
"code": 400,
"message": "Invalid JSONP callback name: ..."
}
});將我們的回呼結尾加上 ;void 使 YouTube 附加的物件變得無害。由於 YouTube 已被 ChatGPT 的 CSP 允許,瀏覽器將此回應作為指令碼載入並在新框架內執行我們的回呼。
我們現在已經在 chatgpt.com 執行了任意 JavaScript。原始預覽仍然隔離在其獨立的網頁地址上;我們透過說服主頁中的可信元件為我們建立一個新的、未受沙盒限制的檔案,已經跨越了邊界。
將實驗室漏洞轉化為單一公開連結
到目前為止,這是一個有用的沙盒逃逸,但第一個可行版本並非令人信服的實際攻擊。它使用研究人員控制的 ChatGPT App 作為傳送者。受害者必須先連線或呼叫該 App。
接著我們嘗試透過原生 HTML 附件傳遞有效負載。ChatGPT 在相同的沙盒預覽中開啟附件,因而能夠傳送惡意的 MathLive 訊息。
然而,當我們將對話匯出為公開分享時,附件仍保留,但先前的圖表卻不見了。圖表是必要的,因為它載入了 MathLive 並在主頁安裝了易受攻擊的訊息監聽器。沒有它,附件仍能傳送訊息,但沒有任何東西在監聽或處理。
突破點是將兩個部分直接放入一個普通公開對話中:
- 一個真實的圖表在主頁初始化 MathLive。
- 後來由助手撰寫的 HTML 預覽發送了兩則訊息。
結果產生的 /share/<id> 連結為另一位使用者保留了完整的鏈結。攻擊者可以以任何共享 ChatGPT 對話的方式傳遞它——透過電子郵件、聊天、支援單或社群貼文。受害者不需要安裝任何東西、加入攻擊者的工作區,或透過攻擊者控制的流程進行驗證。只需訪問攻擊者共享的 ChatGPT URL 即可。
我們觀察到兩個互動版本。在最穩定的重現中,受害者開啟分享並點選 ChatGPT 的正常 Preview 按鈕一次。我們之後將 HTML 區塊標記為可預覽,讓 ChatGPT 自動開啟。在那個版本中,開啟連結是受害者唯一的點選。我們在另一個帳號中重現了自動路徑三次,包括硬重新整理後,警示大約在 4.5 秒後出現。
圖表與 Preview 彼此獨立載入,因此有效負載有時會在 MathLive 準備好前傳送訊息。最終承載程式透過有限時間重試同兩條訊息來處理競爭。主頁中的保護機制確保私人對話證明與外部請求僅執行一次。

此時,公開分享能顯示由瀏覽器識別為來自 chatgpt.com 的警示,並能在主頁新增橫幅。這證明瞭我們的程式碼執行位置,但尚未說明漏洞的重要性。
一個警示不足以
此時,顯示警示是漏洞能做的最不吸引人的事。JavaScript 以 chatgpt.com 身份執行,意味著它可以使用與正常網頁應用相同的已驗證上下文與 ChatGPT 互動。
有效負載讀取頁面已可取得的會話資訊,並利用它呼叫 ChatGPT 的對話 API。首先,它列舉受害者最近的對話。接著,它能為選定的私人對話請求訊息資料:
GET /backend-api/conversations?...&limit=20&order=updated&offset=0
GET /backend-api/conversations/<conversation-id>?num_turns=2這是直接已驗證的 API 存取,而非抓取螢幕上偶然可見的文字。目標對話與惡意分享無關,且從未與攻擊者分享。
一旦回應可供注入的 JavaScript 使用,將資料傳送離站點變得簡單。有效負載向我們控制的伺服器發送了一個普通的圖片請求。
我們故意只將回應的一小部分傳送離站點。注入的程式碼已經收到已驗證的對話回應並能讀取其內容,因此漏洞不僅限於該值。

結果是一個實用的跨使用者資料竊取原語。惡意公開對話能在另一使用者已登入的 ChatGPT 會話內執行,列舉私人對話、檢索私人訊息內容,並將選定資料傳送至攻擊者控制的伺服器。
由於程式碼以網頁應用的已驗證存取執行,其影響範圍並非僅限於我們證明中使用的對話端點。其他透過 ChatGPT 已驗證前端 API 暴露的資料與操作亦位於同一安全邊界。基本上,我們能代表受害者的 ChatGPT 帳號執行任何操作。
出錯之處與 OpenAI 的修復
Preview 沙盒本身運作正常。Preview 內的程式碼無法直接閱讀 ChatGPT 頁面。問題在於主頁中的另一個元件願意接受其指令。
三項設計決策將此錯誤變成完整的利用:
- MathLive 接受了一條改變狀態的訊息,卻未證明是哪個框架傳送。
- 訊息格式允許將原始 HTML 作為鍵盤佈局資料提供。
- 該 HTML 能建立一個未受沙盒限制的檔案,並繼承
chatgpt.com身份。
YouTube 的回應是 CSP 的最後一個迴避方法,而非根本原因。僅封鎖該端點會移除一條 JavaScript 的路徑,但仍允許未受信任的 Preview 在 ChatGPT 中插入 HTML。
OpenAI 重現了 JavaScript 執行與私密對話證明。修正已在 ChatGPT 網頁前端的兩個版本中推出。在推廣期間,CSP 更改阻擋了原始連結的一部分,仍有一個版本需要更新。10 月 26 日,OpenAI 確認兩個版本現在都拒絕了報告中使用的跨框架 MathLive 訊息,並將問題標記為已解決。
防禦教訓相當簡單:
- 訊息接收者應該驗證傳送者的網域地址以及被授權的精確視窗。
- 跨視窗設定不應包含原始 HTML。
- 動態建立的檔案在其內容可能被未受信任的框架影響時,應保持沙盒化。
- CSP 應作為備用控制,而非在攻擊者控制的 HTML 到達主站後唯一的屏障。
這個漏洞之所以有趣,是因為每個獨立功能看起來都合理。Preview 被隔離。圖表能載入數學鍵盤。框架能交換訊息。CSP 允許主要媒體供應商。公開對話可以被分享。漏洞僅在所有這些功能在同一頁面結合時才出現。
時間表
| 日期 | 事件 |
|---|---|
| 2026 年 10 月 13 日 | 確認 MathLive 訊息到 HTML 的路徑符合 ChatGPT 的正式程式碼。 |
| 2026 年 10 月 14 日 | 透過正常 UI 重現整個連結,並將已修正的提示升級為任意 JavaScript。 |
| 2026 年 10 月 16 日 | 確認在不同帳號與工作區間透過一次 Preview 點選即可傳送公開分享。 |
| 2026 年 10 月 17 日 | 確認從分享導覽自動產生 Preview,並在硬重新整理後重現。 |
| 2026 年 10 月 18 日 | 將報告提交至 OpenAI 的漏洞賞金計畫。 |
| 2026 年 10 月 19 日 | 新增受控私聊披露證明。 |
| 2026 年 10 月 21 日 | OpenAI 重現了此問題;嚴重性從最初的 P2 分級提升至 P1。 |
| 2026 年 10 月 22 日 | OpenAI 報告說一個網頁前端已修復,另一個仍在更新。 |
| 2026 年 10 月 23 日 | CSP 更改阻擋了部分連結,而一個前端更新仍在進行。 |
| 2026 年 10 月 26 日 | OpenAI 確認兩個前端都拒絕了報告的訊息,解決了問題,並獲得總計 $5,000 的賞金。 |
技術說明
上述主線包含完整的利用鏈。以下細節供想更深入追蹤正式程式碼與載荷轉換的讀者參考。
相關去混淆後的控制流程等同於:
window.addEventListener("message", (event) => {
if (event.data?.type !== "mathlive#virtual-keyboard-message") return;
if (!validateOrigin(event.origin, this.originValidator)) return;
this.handleMessage(event.data, event.source);
});
if (message.action === "update-setting" && message.layouts) {
this.layouts = message.layouts;
}佈局正常化保留了 layouts[].layers[].markup。顯示鍵盤最終執行相當於:
keyboardContainer.innerHTML = MathfieldElement.createHTML(keyboardHTML);createHTML 作為身份或 Trusted Types 包裝器,而非清理器。Trusted Types 能限制誰將值賦給 HTML sink,但身份政策並不會使攻擊者控制的字串安全。
解析器與 CSP 過渡。 innerHTML 操作可建立 iframe,但直接插入的指令碼仍保持惰性。未沙盒化的 srcdoc 觸發新 HTML 檔案解析,並繼承嵌入的 ChatGPT 原始域。該檔案也繼承了測試期間捕獲的有效 CSP,該 CSP 阻擋了內聯程式碼但允許 www.youtube.com 在 script-src-elem。
oEmbed 回應回傳 HTTP 200 並包含 Content-Type: text/javascript; charset=UTF-8 與 X-Content-Type-Options: nosniff;未觀測到 Cross-Origin-Resource-Policy 標頭。Google 在回呼中重寫了尖括號,因此載荷使用傳統的 function 語法而非箭頭函式。以 ;void 結束程式會消耗端點附加的錯誤物件。
在私人對話驗證中,函式以 toString() 轉成原始碼,雙引號改為單引號,並壓縮空白。URLSearchParams 接著將其編碼進回呼,指令碼 URL 在 <script src> 內部被 HTML 轉義,整個元素再度被轉義到 iframe 的 srcdoc 屬性。最終的回呼長度為 5,533 個字元;YouTube URL 為 7,309 個字元;指令碼元素為 7,337 個字元;最後的 iframe 標記為 7,390 個字元。
已驗證的讀取。 同源框架從父檔案解析 #client-bootstrap,提取會話存取權杖與帳戶 ID,並要求權杖中的帳戶 ID 與會話一致(兩者皆存在時)。它使用以下記憶體內部標頭:
Authorization: Bearer <victim token>
ChatGPT-Account-ID: <victim account>
Accept: application/json主要請求為:
GET /backend-api/conversations?exclude_conversation_origin=tpp&expand=false&is_archived=false&is_starred=false&limit=20&order=updated&offset=0
GET /backend-api/conversations/<id>?num_turns=2&include_has_versions=true只有在詳細請求返回 403、404 或 405 時,程式才會嘗試舊版 /backend-api/conversation/<id>?include_full_conversation=true 路徑。驗證僅傳輸回應中一個小型選擇值。在外部圖片請求之前,它已清除對權杖、帳戶、啟動程式、會話和請求標頭的引用。
關鍵收穫
- 隔離僅與每一座跨越其上的橋梁同等強度。沙盒化的框架仍可能在受特權的監聽器信任來自錯誤傳送者的訊息時,危害其父級。
postMessage接收者必須在接受改變狀態的指令前,驗證event.origin與event.source。- HTML 注入可透過全新、未沙盒化的檔案及允許的第三方指令碼端點,轉變為 JavaScript 執行。CSP 是有價值的深度防禦,但無法將不安全的渲染路徑變為安全。
- 同源 JavaScript 的影響必須在受害者已驗證的環境中衡量。一旦程式以
chatgpt.com執行,它就能存取與正常前端相同的私人資料與帳戶操作,而不只是顯示警示。 - 跨功能測試很重要。此漏洞鏈存在於公共分享、預覽、MathLive、框架建立與 CSP 之間的縫隙,而非單一功能獨立存在。
更廣泛的教訓是,安全邊界必須在整個產品中保持,而不僅僅在各個獨立元件內。任何公開或可分享的表面都應視為攻擊者控制的輸入,每一條跨框架訊息都必須驗證,且不受信任的標記應在整個渲染過程中保持沙盒化。