OAuth 2.0 授权码劫持攻击如何做到?
授权测试 OAuth 2.0 登录流程:验证 redirect_uri、state 与 PKCE 配置
授权码劫持的本质,是攻击者在不接触用户凭据的前提下,截获授权服务器下发的临时授权码(authorization code),并抢在合法客户端之前将其兑换为访问令牌。这类攻击之所以能成立,几乎都源于授权码流程中三个校验环节的松动:redirect_uri 的匹配策略、state 的会话绑定,以及 PKCE 的强制性。理解这三点如何被绕过,就能理解攻击链是怎么拼接起来的。
最常见的入口是 redirect_uri 校验不严。RFC 9700 建议对回调地址采用精确匹配,但不少实现仍使用前缀匹配或模式匹配。一旦服务端接受非注册地址,攻击者便能利用 URL 解析差异构造变体,例如在合法地址后追加路径、片段,或借助 https://example.com@evil.com、https://evil.com#@example.com 这类歧义结构,以及子域名或双斜杠变体,诱导授权码被重定向到攻击者控制的域名。此时攻击者只需诱使已登录用户点击一个精心构造的授权链接,授权码就会被发送到其手中。
但拿到授权码不等于攻击成功。能否兑换,取决于授权码是否绑定了客户端与状态信息。这正是区分“配置缺陷”与“可证明安全影响”的关键:若授权码能被无条件兑换,才构成可利用漏洞;若兑换时的绑定校验生效导致失败,则只能算不符合最佳实践的配置问题。
state 参数的缺失则打开了另一条路径。state 的作用是在回调时校验其值与用户会话中存储的随机值一致,从而防御授权流程中的 CSRF。当服务端不校验 state,攻击者可以把受害者的授权码绑定到自己控制的会话,完成账号固定或账号绑定攻击。判断风险高低的依据,是授权码兑换端点是否要求携带正确的 state,以及令牌颁发后是否将其绑定到当前会话——两者都缺失才构成可利用漏洞,仅缺 state 但授权码与客户端会话强绑定时,风险等级会相应下降。
PKCE 则是针对授权码拦截的最后一道防线。它通过 code_verifier 与 code_challenge 的配对验证,确保即便授权码被截获,缺少对应 verifier 的攻击者也无法兑换。RFC 9700 已将其列为授权码流程的强制要求,但存量系统常留有绕过路径:若服务端在授权请求未携带 PKCE 参数时仍接受裸授权码兑换,且客户端为单页应用或移动应用这类公开客户端,拦截授权码即可直接导致令牌泄露;若强制了 PKCE 却仍接受安全性较弱的 plain 方法,攻击者则需进一步拦截 code_challenge 并伪造 verifier,利用条件更为苛刻。
把这三点串起来看,授权码劫持从来不是单一漏洞,而是一条由多个校验薄弱点叠加而成的链条。防御的思路同样清晰:redirect_uri 改为精确匹配并关闭模式匹配,state 采用高熵随机值并在回调中强制校验,PKCE 统一使用 S256 并拒绝无 PKCE 的兑换请求。任何一环收紧,整条攻击链都会断裂。

参与讨论
redirect_uri精确匹配这点太多人栽过