一个分享链接就能接管 ChatGPT 账号,已修复

安全公司 Parameter Labs 10 月 28 日公开了一条 ChatGPT 漏洞链:攻击者发布一段公开分享的对话,受害者打开链接,攻击者的 JavaScript 就以 chatgpt.com 的身份在受害者的登录会话里运行,能列出对话、读取从没分享过的私密对话并发到外部服务器。OpenAI 10 月 26 日已修复,定级 P1。普通用户不用做什么,做 AI 产品、在页面里渲染模型生成内容的团队值得对照查一遍。
沙箱本身没坏,坏在主页面里的一个监听器
ChatGPT 的预览功能会把 HTML 代码块渲染成小网页,放在另一个域名的受限 iframe 里。研究者确认这层隔离是有效的:预览里的代码读不到外面的 ChatGPT 页面。
缺口在同一段对话里的交互式图表。图表加载了 MathLive,它会在主页面装一个虚拟键盘用的 postMessage 监听器,来源校验策略是 none,接受任何窗口发来的消息。键盘布局字段又允许写 HTML,最后被直接赋值给主页面的 innerHTML。名为 createHTML 的辅助函数看着像过滤器,实际只是把字符串原样标成可信。
研究者往里塞了一个不带 sandbox 属性的 srcdoc iframe。它由主页面创建,所以继承了 chatgpt.com 的身份。CSP 拦住了内联脚本,却放行 YouTube;YouTube oEmbed 接口会把非法的 callback 参数原样写进返回的脚本,整条链就接上了。
预览框的隔离一直有效,出问题的是主页面里替它转发消息的 MathLive 监听器,它不检查消息是谁发的。
攻击链每一环和本该拦住它的那道门
| 环节 | 发生了什么 | 本该怎么拦 |
|---|---|---|
| 跨窗口消息 | MathLive 不检查发送方 | 同时校验 event.origin 和 event.source |
| 设置项带 HTML | 键盘布局可写原始 HTML | 跨窗口传的设置不接受原始 HTML |
| 新建文档 | 无沙箱 iframe 继承主站身份 | 受不可信内容影响的文档保持沙箱 |
| CSP | YouTube JSONP 被当成脚本执行 | CSP 只当兜底,不当唯一防线 |
研究者特别说明,YouTube 接口只是绕 CSP 的最后一步,不是根因。只封这个接口,不可信的预览照样能往 ChatGPT 页面里塞 HTML。
时间线和几个数字
- 10 月 13 日在生产代码上确认 MathLive 消息到 HTML 的路径;14 日升级为任意 JavaScript 执行。
- 16 日确认跨账号、跨工作区的分享链接投递;17 日做到打开分享即自动预览,受害者唯一的操作就是点开链接,三次复现里弹窗都在约 4.5 秒后出现。
- 18 日提交报告;21 日 OpenAI 复现,等级从 P2 升到 P1;22 日一套前端修好,23 日 CSP 调整挡住了部分链路。
- 26 日两套前端都拒绝了报告里的跨窗口消息,问题关闭,赏金共 5000 美元。
拿到 chatgpt.com 身份后,载荷直接读取页面里的会话信息,调用 /backend-api/conversations 列出最近 20 条对话,再取一条私密对话的内容。研究者为了演示只外传了一小段数据,但按他们的说法,前端能调的认证接口都在同一个边界里,等于能以受害者身份做任何事。
谁该关心
普通 ChatGPT 用户这次不需要操作,漏洞已在服务端修复。陌生人发来的分享链接,照常谨慎对待即可。
做 Artifacts、代码预览、Canvas 一类功能的团队需要细看。这类产品会在页面里渲染模型生成、甚至用户分享来的网页,主页面又常常加载各种第三方库。可以按上表逐项查:主页面里每个 message 监听器有没有校验来源窗口,设置类消息能不能带 HTML,动态创建的 iframe 有没有加 sandbox,CSP 白名单里有没有能回显参数的 JSONP 接口。站内的 扩展供应链自查教程 讲的是另一类入口,可以一起对照。
最后
如果你的产品也渲染模型生成的 HTML,今天就在代码里搜一遍 addEventListener("message",逐个确认它校验了发送方。