!TIP 文章由 LongCat-2.0 生成。

背景

最近网上冲浪的时候发现了一个强行加入 K12 空间的脚本。因好奇其实现原理,遂进行分析,发现它利用了 ChatGPT 平台的一系列安全缺陷来实现「无限K12」的效果。

脚本概述

该脚本是一个浏览器 Userscript(通过 Tampermonkey 等扩展注入),运行在 https://chatgpt.com/* 页面上(@run-at document-idle)。安装后会自动在页面右上角注入一个浮层面板,提供以下功能:

  • 自动获取当前登录账号的 Session Token(AT)
  • 向指定的多个 Workspace 发送「加入申请」(request)
  • 自动接受已发出的邀请(accept)

默认配置中硬编码了十几个 Workspace ID,批量操作为母号(管理员)和子号(被加入的账号)之间建立关联。

核心漏洞分析

1. AT(Access Token)窃取

脚本的核心入口是 fetchSession() 函数:

async function fetchSession() {
  const res = await fetch("/api/auth/session", {
    headers: { accept: "*/*" },
    credentials: "include",
  });
  return await res.json();
}

这里利用了 ChatGPT 的 /api/auth/session 端点——该接口返回当前用户的完整会话信息,包括 accessToken(AT)。由于浏览器 Same-Origin Policy 允许同源页面发起带 credentials: "include" 的请求,任何注入到 chatgpt.com 的 JavaScript 代码都可以无感知地获取到当前登录用户的 Token。

漏洞本质: 服务端没有在 Token 传输层做足够的保护(如 httpOnly Cookie 绑定、Token 指纹绑定等),使得前端 JS 可以直接读取到高权限的 Access Token。

2. JWT 结构解析

获取到 AT 后,脚本进一步解析 JWT 载荷:

function decodeJwt(at) {
  const payload = at.split(".")[1];
  const json = JSON.parse(atob(payload.replace(/-/g, "+").replace(/_/g, "/")));
  const auth = json["https://api.openai.com/auth"] || {};
  const prof = json["https://api.openai.com/profile"] || {};
  return {
    account_id: auth.chatgpt_account_id || "",
    email: prof.email || "",
    plan_type: auth.chatgpt_plan_type || "",
    exp: json.exp || 0,
  };
}

这说明 AT 是一个标准的 JWT,载荷中包含:

  • https://api.openai.com/auth.chatgpt_account_id:账号唯一标识
  • https://api.openai.com/profile.email:用户邮箱
  • chatgpt_plan_type:订阅类型
  • exp:过期时间

3. 越权加入工作区

有了 AT 后,脚本直接调用 Workspace 管理 API:

async function sendOne(wsId, route, attempt = 0) {
  const url = `/backend-api/accounts/${wsId}/invites/${route}`;
  const headers = {
    authorization: `Bearer ${STATE.at}`,
    "oai-device-id": STATE.deviceId,
    "oai-language": navigator.language || "en-US",
  };
  const res = await fetch(url, {
    method: "POST",
    headers,
    body: null,
    credentials: "include",
  });
  // ...
}

route 参数可以是 "request"(申请加入)或 "accept"(接受邀请)。这里的关键问题在于:

漏洞一:AT 直接用于跨账号操作

Bearer Token 直接作为身份凭证,服务端只验证 Token 本身的有效性(是否过期、签名是否正确),而没有校验操作意图的合法性。这意味着:

  • 子号的 AT 可以用于向任意 Workspace 发起加入请求
  • 母号的 AT 可以通过 accept 接口直接接受邀请,无需二次确认
  • 整个流程没有任何 CSRF Token、确认步骤或人机验证

漏洞二:无速率限制与设备指纹绕过

脚本使用了 crypto.randomUUID() 生成固定的 oai-device-id,并在请求头中发送。这种客户端生成的设备标识符显然无法作为有效的风控依据,攻击者可以随意伪造。

4. Token 自动刷新机制

脚本还实现了自动刷新机制,保证 AT 持续有效:

setInterval(refreshSession, CONFIG.sessionPollMs); // 每20秒刷新一次

当检测到 401/403 响应时,脚本会主动清空 AT 并重新获取:

if (res.status === 401 || res.status === 403) {
  STATE.at = "";
  await refreshSession();
}

这意味着即使原始 Token 过期或被吊销,只要用户保持登录状态,脚本就能持续获取新的有效 Token 来维持攻击。

攻击链总结

用户在 chatgpt.com 登录
       ↓
Tampermonkey 注入 Userscript
       ↓
fetch("/api/auth/session") → 窃取 AT
       ↓
JWT 解析 → 提取 account_id、email、plan_type
       ↓
POST /backend-api/accounts/{workspaceId}/invites/request
       ↓
向多个目标 Workspace 批量发送加入申请
       ↓
POST /backend-api/accounts/{workspaceId}/invites/accept
       ↓
自动接受邀请,子号进入母号的工作区
       ↓
每 20 秒刷新 Session,维持持久访问

影响与危害

  1. 账号劫持风险:任何运行该脚本的页面都可以窃取用户的 AT,用于冒充用户身份执行任意 API 操作
  2. 工作区权限滥用:子号可以在不知情的情况下被批量加入工作区,母号可以无审核地扩张团队规模
  3. 批量注册/滥用:结合自动化注册工具,可以低成本地批量创建账号并加入工作区,绕过付费限制
  4. 供应链攻击面:Tampermonkey 脚本的 @grant none 模式意味着脚本运行在页面主上下文中,拥有与页面 JS 同等的权限

修复建议

从空间管理者角度:

  • 定期审查空间成员列表,移除不认识或不活跃的账号
  • 废弃被泄漏的空间
  • 关闭不需要的 Codex 权限

从平台角度:

  • /api/auth/session 返回的 AT 应限制为短期有效,或改用 httpOnly Cookie 方案
  • Workspace 操作 API 应增加人机验证和意图确认
  • 引入设备指纹绑定,使窃取的 Token 在其他设备上无法使用
  • 增加 API 速率限制和异常行为检测

从用户角度:

  • 不要安装来源不明的 Tampermonkey 脚本
  • 定期检查 ChatGPT 账号的登录活动和工作区成员列表
  • 使用浏览器容器隔离不同站点的登录状态