无限爽毛K12?GPT For Teachers工作区漏洞的小记
!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,维持持久访问
影响与危害
- 账号劫持风险:任何运行该脚本的页面都可以窃取用户的 AT,用于冒充用户身份执行任意 API 操作
- 工作区权限滥用:子号可以在不知情的情况下被批量加入工作区,母号可以无审核地扩张团队规模
- 批量注册/滥用:结合自动化注册工具,可以低成本地批量创建账号并加入工作区,绕过付费限制
- 供应链攻击面:Tampermonkey 脚本的
@grant none模式意味着脚本运行在页面主上下文中,拥有与页面 JS 同等的权限
修复建议
从空间管理者角度:
- 定期审查空间成员列表,移除不认识或不活跃的账号
- 废弃被泄漏的空间
- 关闭不需要的 Codex 权限
从平台角度:
/api/auth/session返回的 AT 应限制为短期有效,或改用httpOnlyCookie 方案- Workspace 操作 API 应增加人机验证和意图确认
- 引入设备指纹绑定,使窃取的 Token 在其他设备上无法使用
- 增加 API 速率限制和异常行为检测
从用户角度:
- 不要安装来源不明的 Tampermonkey 脚本
- 定期检查 ChatGPT 账号的登录活动和工作区成员列表
- 使用浏览器容器隔离不同站点的登录状态