sub2api曝OAuth高危漏洞:仅凭邮箱即可接管账户

开源 AI API 网关 sub2api 日前披露了一个高危账户接管漏洞(CVSS 8.8)。攻击者仅需知道目标用户的注册邮箱,无需密码、验证码或任何用户交互,就能将自己的 OAuth 身份绑定到对方账户上,获取全部 API 密钥、账单余额和订阅配额的控制权。

sub2api 是一个在 GitHub 上获得超过 36000 Star 的开源项目,用于将 Claude、OpenAI、Gemini、Grok 等 AI 产品的订阅配额统一为 API 接口对外分发,支持用户间拼车共享订阅。项目使用 Go 语言开发,受影响版本为 v0.1.171 及之前所有版本,截至漏洞披露时尚无正式修复版本发布。

sub2api OAuth 账户接管漏洞披露页面

漏洞核心:三层缺陷叠加

漏洞位于 backend/internal/handler/auth_oauth_pending_flow.go 文件,由三个相互叠加的代码缺陷共同构成攻击链。

缺陷一:状态机拦截不完整

ExchangePendingOAuthCompletion 函数负责处理 OAuth 登录流程中的 pending session 交换。该函数只显式拦截了 email_completionbind_login_required 两个步骤,对于 choose_account_action_required(选择账户操作步骤)没有任何拦截逻辑。

这意味着任何未被显式拦截的步骤都会被隐式视为"可以安全执行绑定操作",直接进入后续的身份绑定流程。这一设计违反了安全工程中的 fail-closed(默认拒绝)原则——状态机应当只允许显式声明的安全状态通过,而不是默认放行所有未知状态。

缺陷二:绑定逻辑无条件执行

applyPendingOAuthAdoption 函数在处理"领养决策"时存在严重逻辑错误。该函数接收的 adopt_display_nameadopt_avatar 两个布尔值字段仅控制用户昵称和头像等显示属性,与账户所有权毫无关系。

然而函数内部的实现是:只要这两个字段中任一为 true,就会无条件调用 applyPendingOAuthBinding 执行身份绑定。绑定函数会将攻击者的 OAuth 身份信息写入 auth_identities 表,其中 user_id 字段被设为受害者的 ID。

缺陷三:登录意图绕过验证

shouldBindPendingOAuthIdentity 函数本应作为最后一道安全关卡,判断当前 session 是否满足绑定条件。但该函数对 intent == "login"(登录意图)的 session 无条件返回 true,不检查 session 是否处于可颁发令牌的安全状态,也不验证邮箱是否已完成验证。

这构成了最关键的设计反转:匿名、未验证的 login 状态反而比已登录的 bind_current_user 状态拥有更宽松的权限。在安全设计中,匿名 session 应当受到最严格的限制,这里恰好相反。

完整攻击流程

攻击分为四步,全程自动化,不需要受害者参与:

第一步:攻击者用自己的 OAuth 账号(如 linux.do 账号)发起正常登录流程。由于系统中没有该身份的记录,session 进入 choose_account_action_required 状态,提示选择"创建新账户"或"绑定已有账户"。

第二步:攻击者向 /auth/oauth/pending/create-account 接口提交受害者的注册邮箱。系统检测到该邮箱已存在,自动将 session 的 TargetUserID 设为受害者的用户 ID,并标记 existing_account_bindable = true

这一步的关键问题在于:系统将"邮箱在数据库中存在"这一中立事实,等同于"提交者拥有该邮箱"的授权声明。正确的所有权验证方式(发送验证码到邮箱)在这一路径上被完全跳过。

第三步:攻击者调用 /auth/oauth/pending/exchange 接口,提交仅包含显示属性的字段:

json
{"adopt_display_name": true, "adopt_avatar": true}

由于缺陷一未拦截该步骤,请求落入绑定流程(缺陷二),身份验证函数对 login 意图返回 true(缺陷三),攻击者的 OAuth 身份被永久写入受害者的账户记录。

第四步:攻击者再次使用同一 OAuth 账号登录时,系统在 auth_identities 表中查到先前写入的记录,将攻击者解析为受害者身份,直接颁发受害者的 access token 和 refresh token。

sub2api OAuth 漏洞攻击链

影响范围

漏洞影响所有通过 pending session 流程的 OAuth 提供商:linux.do、OIDC、企业微信、钉钉。GitHub 和 Google 因走验证邮箱路径可能不受影响,但需要额外审计确认。

攻击者完成账户接管后可以:

  • 读取受害者绑定的全部上游 API 密钥(Claude、OpenAI、Gemini、Grok 等)
  • 消耗预付费余额和订阅配额
  • 修改账户资料、支付绑定和密码
  • 若受害者在拼车群组中,还可能波及其他成员的数据和配额

该攻击具有持久性——绑定记录会一直存在直到手动删除,攻击者可随时重新登录。同时具备批量扩展能力,若获得邮箱列表可编写脚本批量接管。

CVSS 8.8 评分依据

维度说明
攻击向量网络通过 HTTP API 远程利用
攻击复杂度多步 HTTP 请求序列,无需竞态条件
所需权限需要任意 OAuth 账号
用户交互不需要受害者参与
机密性可读取全部 API 密钥和账户数据
完整性可修改账户、绑定新身份
可用性可耗尽订阅配额、停用服务

修复方案

修复 PR #5345 已提交,包含三层修复:

  1. ExchangePendingOAuthCompletion 改为 fail-closed 模式,只有 session 处于终端可验证状态时才执行绑定
  2. 邮箱已存在时强制发送验证码,移除 existing_account_bindable 快捷路径
  3. shouldBindPendingOAuthIdentity 对 login 意图要求同时满足 CanIssueTokenPairHasVerifiedEmail

此外,安全研究者还建议添加 pending session 单次消费机制(防止重放攻击)、IP 异常告警(单个 IP 在 1 小时内绑定 3 个以上不同用户 ID 时触发)、以及考虑彻底移除 choose_account_action_required 状态,改用需要先登录再绑定的显式流程。

时间线

日期事件
2026-08-04sub2api v0.1.171 发布(最新受影响版本)
2026-08-06漏洞通过 PR #5345 披露
2026-08-07安全公告发布

来源:GitHub Issue #5350修复 PR #5345