逆向工程实战手册 第 69 章

第 69 章 OAuth 2.0 / OIDC 攻击链

OAuth 是"第三方登录"的标准(用微信/GitHub/Google 登录),OIDC 是其身份层扩展。它跨越"客户端、授权服务器、资源服务器"三方,攻击面在信任边界的传递:redirect_uri、state、PKCE、scope、token 处理。本章覆盖完整攻击链与测试方法。

📍 知识点地图 | 主题:OAuth/OIDC | 前置:第58章 | 后续:— | 核心概念:redirect_uri、state、PKCE、token泄漏

69.1 OAuth 2.0 核心流程(先懂四角色)

角色:
  资源所有者(用户)
  客户端(应用,如你的 App)
  授权服务器(AS,如微信/GitHub)
  资源服务器(RS,持有用户数据)

Authorization Code Grant(授权码模式,最主流):
  1. 用户访问客户端 → 重定向到 AS 授权页
     https://as.com/authorize?client_id=X&redirect_uri=https://app.com/callback&state=S
  2. 用户授权 → AS 重定向回客户端(带授权码)
     https://app.com/callback?code=AUTH_CODE&state=S
  3. 客户端用 code+client_secret 换 token(后端换,code 不可见)
  4. 客户端用 token 访问 RS

安全关键点(攻击面都在这些环节):
  redirect_uri(回调地址可控?)
  state(防 CSRF 的随机值)
  PKCE(授权码拦截防护)
  client_secret(客户端凭证泄露)
  scope(权限范围)
  token 处理(泄漏/重放)

69.2 攻击面总览

┌──────────────────────────────────────────────────┐
│ 1. redirect_uri 操控(最经典)                    │
│    → 授权码/令牌被送到攻击者控制的地址             │
│ 2. state 缺失/可预测(CSRF 绑定攻击)              │
│    → 攻击者的 code 绑定受害者的会话                │
│ 3. PKCE 缺失/可绕过(授权码拦截)                  │
│ 4. client_secret 泄露(前端/移动端硬编码)         │
│ 5. scope 过度/提升(权限放大)                     │
│ 6. token 泄漏(Referer/日志/URL)                  │
│ 7. 重放与跨租户(token 复用)                      │
│ 8. 开放重定向联动(授权服务器信任的重定向)         │
└──────────────────────────────────────────────────┘

69.3 redirect_uri 操控(核心攻击)

变体 1:开放重定向 + redirect_uri

正常: redirect_uri=https://app.com/callback
攻击:
  redirect_uri=https://app.com/callback@evil.com      # 解析差异
  redirect_uri=https://app.com/callback?redirect=https://evil.com   # 参数注入
  redirect_uri=https://evil.com/?redirect=https://app.com/callback  # 开放重定向

流程: 用户授权 → AS 把 code 发给 evil.com → 攻击者拿到 code

变体 2:严格校验的绕过

□ 加前缀/后缀(redirect_uri=https://app.com.evil.com)
□ 路径穿越(redirect_uri=https://app.com/callback/../../evil)
□ 端口变化(redirect_uri=https://app.com:8443/callback —— 有的校验忽略端口)
□ 大小写/编码(%2F、http://)
□ 子域白名单放宽(*.app.com → 攻击者注册 app.com.evil.com 或 test.app.com)

变体 3:二次跳转

redirect_uri=https://app.com/callback(白名单内)
但 callback 端点本身有开放重定向:
  https://app.com/callback?next=https://evil.com
→ 授权码最终到达 evil.com

69.4 state 缺失(CSRF 绑定攻击)

原理

state = 随机值,AS 原样返回,客户端校验"请求是自己的"
缺失/固定/可预测 → 攻击者能"用自己的授权"绑定受害者的会话

攻击流程(Login CSRF):
  1. 攻击者完成一次授权(拿到自己的 code)
  2. 诱导受害者点击: https://app.com/callback?code=ATTACKER_CODE
  3. 受害者浏览器登录了自己的账号(但 code 是攻击者的)
  4. 效果: 受害者账号被绑定到攻击者的身份(账号混淆/接管)

测试

□ 登录流程里 state 是否存在
□ state 是否随机(多次登录对比)
□ 删掉 state 还能完成吗
□ 不校验 state(任意值都接受)

69.5 PKCE 缺失/绕过(授权码拦截)

原理

PKCE = 授权码模式的安全增强(防"授权码被拦截后兑换")
  客户端先发 code_challenge(随机值的哈希)→ 兑换时带 code_verifier
  拦截者没有 verifier → 换不了 token

缺失 PKCE 的危害(配合劫持回调):
  攻击者拿到 code(通过开放重定向/中间人)→ 直接换 token

测试

□ 授权请求无 code_challenge 参数 → 无 PKCE
□ 有 PKCE 但服务端不校验 verifier
□ code_challenge 可预测(固定值)

69.6 client_secret 与 scope 问题

secret 泄露(移动端/前端硬编码)

□ 移动 App 逆向 → 找 client_id/client_secret(第 10 章)
□ 前端 JS → 找嵌入的 secret(第 13 章)
□ 泄露后果: 冒充客户端 → 配合 redirect_uri 攻击

scope 提升(权限放大)

测试: 请求更高的 scope
  scope=read → scope=read%20write
  scope=user:info → scope=user:admin
服务端可能: 不校验 scope 合法性/不校验客户端申请范围

OIDC 变体: 请求额外的 claims(profile/email/phone/address)

69.7 token 泄漏与重放

泄漏渠道

□ Referer 头: 回调页面加载外部资源 → Referer 带 token
□ URL fragment: implicit 模式(token 在 # 后)→ 浏览器历史/扩展
□ 日志: 服务端/代理记录完整 URL
□ 第三方脚本: 页面加载的统计/分析脚本可能看到

重放与跨租户

□ token 重放: 同一 token 多次使用(应只有效一次/短时效)
□ 跨租户: A 租户的 token 访问 B 租户资源(多租户 SaaS)
□ refresh token 滥用: 无限续期/永不过期
□ token 不绑定客户端/设备(可跨设备使用)

69.8 测试工具与流程

# 工具
# 1. Burp + OAuth Scanner 扩展(自动测试常见 OAuth 漏洞)
# 2. 手工: 拦截授权流程,逐环节篡改
# 3. jwt_tool(OAuth 拿到的 token 再测 JWT 层,第 58.7)
# 4. Postman: 复现完整 OAuth 流程做参数篡改

# 完整测试流程
1. 梳理 OAuth 集成(找所有第三方登录入口)
2. 记录完整授权流程(authorize  callback  token  API)
3. 逐环节测试:
    redirect_uri(五类绕过)
    state(缺失/可预测/不校验)
    PKCE(缺失/不校验)
    scope(提升)
    token(泄漏/重放/跨租户)
4. 联动测试: 开放重定向/前端逆向找 secret
5. 报告: 攻击链 + 每步证据

69.9 检查清单

□ OAuth 集成点枚举(第三方登录入口)
□ redirect_uri 操控(5 类绕过变体)
□ state 缺失/可预测
□ PKCE 缺失/不校验
□ client_secret 泄露(前端/移动端)
□ scope 提升
□ token 泄漏(Referer/日志/URL)
□ token 重放/跨租户/刷新滥用
□ 开放重定向联动
□ 报告: 完整攻击链

动手练习

  1. 在练习靶场(如 PortSwigger OAuth labs)完成 redirect_uri 绕过实验。
  2. 构造一个"state 缺失"的 Login CSRF PoC,验证账号绑定攻击。
  3. 用前端逆向找一个练习应用硬编码的 client_secret(第 13 章方法)。
  4. 画一张 OAuth 攻击链图:从 redirect_uri 到 token 被窃取的完整路径。

深入阅读

  • 仓库:skills/api-security/references/jwt-oauth-testing.md(OAuth 测试全量)
  • 仓库:CTF-Sandbox-Orchestrator/competition-oauth-oidc-chain/(OAuth CTF 子技能)
  • 仓库:CTF-Sandbox-Orchestrator/competition-jwt-claim-confusion/(JWT 声明混淆)
  • 仓库:skills/api-security/SKILL.md(API 安全方法论)
  • 仓库:skills/js-reverse/(前端找 secret/token)