导语:错误不在"发送消息",而在"发给谁"
OAuth 授权完成后,浏览器弹窗需要告诉主页面"连接成功"。window.postMessage 正是为跨窗口通信设计的工具。但 DocsGPT 的回调页面把含有 session_token、用户邮箱的消息发送给 window.opener,同时把目标 Origin 写成 '*'。这相当于把"弹窗由谁打开"误当成"谁有资格接收会话标识"。
【事实】GitHub 漏洞记录于 2026 年 9 月 15 日公开 CVE-2026-91201,描述 DocsGPT 至 0.20.0 的上述风险,CVSS 4.0 评分 5.3;漏洞记录的影响包括泄露连接器会话标识及邮箱、借会话标识断开连接器。记录把受影响范围写在描述中,但"影响版本/修复版本"结构化栏均为 Unknown,不能把它理解成厂商已发布修复版本。
背景:跨源限制不会替应用做授权
同源策略通常阻止一个站点直接读取另一个站点的页面内容。postMessage 则允许页面主动向其他窗口发送数据,以便完成支付、登录、OAuth 弹窗等流程。此时浏览器只负责按应用指定的目标 Origin 投递,不会判断负载中的字段是不是密钥或是否属于这个接收者。
postMessage(message, targetOrigin) 的第二个参数不是"方便开发的占位符"。精确 Origin 是向浏览器声明"仅当目标窗口当前处于这个站点时才投递";'*' 则不设置这道收件限制。MDN 的安全说明也要求传输敏感数据时指定精确目标 Origin,接收端验证 event.origin 与 event.source。这是通用浏览器机制,不是 DocsGPT 专有实现。
技术原理:两个方向、两种不同的校验
【事实】项目 Issue #2766于 2026 年 9 月 13 日公开相关代码路径:OAuth 回调状态页发送 {type, session_token, user_email} 给 window.opener,其 targetOrigin 为 '*';前端 ConnectorAuth.tsx 的消息处理函数依据消息类型接受成功通知,却没有验证 event.origin。漏洞公告所链接的固定快照回调代码和前端代码可供审阅。
这里有两条彼此独立的边界:
| 方向 | 应由谁检查 | 应检查什么 | 缺失时的典型风险 |
|---|---|---|---|
| 弹窗 → opener | 发送方与浏览器 | targetOrigin 是否为被允许的前端 Origin |
会话标识发给非预期 opener |
| opener → 前端 | 接收方 | event.origin 与 event.source 是否同时匹配 |
其他窗口可伪造"授权成功"消息 |
接收端的 Origin 检查不能追回已经发出去的消息;发送端限制目标也不能自动保证前端不接收伪造消息。type、provider、user_email 只是数据字段,不能替代窗口身份与来源验证。
【事实】修复 PR #2780由贡献者于 2026 年 9 月 14 日提出:候选修改包括限制目标 Origin、前端同时校验弹窗引用与回调 Origin、避免成功令牌进入 URL,以及将连接器删除和同步操作绑定当前用户。截至本文核验时 PR 仍 Open;下文称之为"候选修复设计",不表示正式发布版本已有这些保护。
风险影响:会话标识不是云服务 Access Token,却也不是公开信息
【事实】CVE 描述的是 DocsGPT 的连接器 session_token 与提供方账户邮箱,而非直接向 opener 发送 Google Drive 原始 OAuth Access Token。项目 Issue还展示了未核验调用者身份的断开连接器路径;但 Issue 的"其他 API 或文件可访问"论断及 PR 作者提出的更广攻击链,需要依部署条件与具体服务端校验来判断,不能当成每个实例都已遭遇云盘泄露。
【推断】多用户部署中,连接器会话标识一旦被错误窗口接收,可能触发连接中断、异常关联或后续越权链。其实际影响受账户权限、连接器开启状况、会话归属检查、令牌生命周期以及接入提供方约束影响。
截至 2026 年 9 月 15 日,上述一手材料没有确认在野利用;公开 PoC 或研究者测试不是现实攻击统计。
无害复现:只模拟投递规则,不触碰真实连接器
在本地用固定字符串比较目标 Origin:
def browser_would_deliver(target_origin, opener_origin):
return target_origin == "*" or target_origin == opener_origin
allowed = "https://notes.example.invalid"
other = "https://other.example.invalid"
assert browser_would_deliver("*", other)
assert not browser_would_deliver(allowed, other)
assert browser_would_deliver(allowed, allowed)
这只是浏览器消息投递决策的概念等价模型:不打开浏览器、不请求 DocsGPT、不包含有效凭据,也不证明任何具体部署已经泄漏。开发团队真正需要的回归测试还应覆盖 event.source 不匹配、Origin 不匹配、null Origin、授权流程过期、提供方类型不一致和同源但不同弹窗实例。
开发与安全团队的行动清单
【建议·P0】若在使用 DocsGPT 云盘连接器,识别版本与公网可达的 OAuth 回调,核对前端/后端部署 Origin;在正式修复确认并部署前,考虑暂停外部连接器授权,限制连接器访问,并审核近期异常断连、重新授权和会话归属变化。避免把会话标识、邮箱或 OAuth 值写入应用/反向代理日志。
【建议·P1】前端采用明确的允许 Origin 清单,后端产生回调时只对允许 Origin 投递;前端验证 event.origin、event.source === popupRef、预期消息格式与当前进行中的 OAuth 流程。所有使用 session_token 的服务端操作必须校验认证用户、会话所有权与 provider 一致性;不能仅靠前端检查兜底。
【建议·P2】在 CI 加入"敏感 postMessage 不得使用 '*'"静态规则,并建立跨源消息回归矩阵。审核同仓库支付/登录/MCP 等弹窗路径的变体,落实成功回调 URL 不携带会话标识、Cache-Control: no-store 与 Referrer-Policy: no-referrer。候选 PR 的测试结果由 PR 作者陈述,合并与发布前仍须由维护者审核。
总结
这不是 OAuth 协议本身被破解,而是 OAuth 完成后的"浏览器跨窗口通知"被赋予了过高信任。发送端负责指定收件人,接收端负责确认发件人,后端负责确认会话归属。三者缺一,结构正确的成功消息仍可能成为错误对象手里的连接器会话。