Skip to content

7.5. 授权码的安全考量

7.5.1. 授权码注入

授权码注入是这样一种攻击,客户端在重定向 URI 中接收到了攻击者的授权代码,而不是合法授权服务器的授权代码。在没有保护措施的情况下,客户端没有办法知道攻击的发生。授权码注入既可能导致攻击者获取对受害者账户的访问权限,也可能导致受害者意外获取对攻击者账户的访问权限。

7.5.2. 对策

为防止向客户端注入授权码,code_challenge 和 code_verifier 的使用对于客户端而言是必需的。授权服务器也必须强制使用两者,除非同时满足下面两个条件:

  • 客户端是机密客户端。
  • 在特定部署和特定请求中,授权服务器能够合理保证客户端正确实现了 OpenID Connect 的验证码机制。

在这种情况下,仍然建议强制使用 code_challenge 和 code_verifier。

code_challenge 或 OpenID Connect 的 nonce 值必须针对特定事务,并与客户端和启动事务的用户代理安全绑定。如果事务出错,那么必须重新选择 code_challenge 或 nonce 值。

依靠客户端验证 OpenID Connect 的 nonce 参数意味着授权服务器无法确认客户端是否确实保护了自己免受授权码注入攻击。如果攻击者能够向客户端注入授权码,客户端仍会使用注入的授权码交换令牌。当之后验证 nonce 并发现其不匹配时,客户端才会拒绝 ID 令牌。相比之下,强制使用 code_challenge 和 code_verifier 参数的授权服务器提供了更高的安全性,因为授权服务器能够事先识别出授权码注入攻击,并从一开始就不颁发任何令牌。

历史原因说明:虽然 PKCE [RFC7636](code_challenge 和 code_verifier 参数最先在此提出)最初被设计为一种机制,用于保护原生应用免受授权码渗漏攻击,但是所有类型的 OAuth 客户端,包括网络应用和其它机密客户端,都容易受到授权码注入攻击。而 code_challenge 和 code_verifier 机制可以解决这个问题。

7.5.3. 授权码的重用

如果授权码能够被多次重用,那么可能发生多种攻击。

如第 4.1.3 节所述,当授权服务器接收到第二个有效的请求,并且其中包含的授权码已经被用于颁发访问令牌,那么授权服务器必须拒绝该令牌请求,并撤销所有颁发的令牌。如果攻击者能够渗透授权码,并抢在合法客户端之前使用它,那么攻击者就能够获得访问令牌,而合法客户端则不能。撤销所有颁发的令牌,意味着攻击者的令牌将被撤销,阻止进一步的攻击。

然而,仅当这一包含授权码的请求同样有效(包括其它参数,例如 code_verifier 和客户端认证信息)时,授权服务器才应该撤销颁发的令牌。当接收到包含无效参数的重放授权码时,授权服务器不应该撤销任何颁发的令牌。如果这样做,那么那些能够获取授权码、但无法获取客户端认证信息或 code_verifier 的攻击者,就可以抢在合法客户端之前发送无效的授权码请求,从而在合法客户机发出有效请求后撤销其令牌。

7.5.4. HTTP 307 重定向

授权服务器在重定向可能包含用户凭据的请求时,禁止使用 307 状态码 [RFC9110] 第 15.4.8 节)进行重定向。如果这种请求使用的是 HTTP 重定向(而非,例如,JavaScript),授权服务器应该使用状态码 303(见别处)。

在授权端点上,典型的协议流程是:授权服务器促使用户在表单中输入他们的凭据,然后表单被(通过 POST 方法)提交回到授权服务器。授权服务器检查凭据,如果成功,那么将用户代理重定向到客户端的重定向 URI。

如果重定向使用的是状态码 307,那么用户代理就会通过 POST 请求,将用户凭据发送给客户端。

这会将敏感凭据泄露给客户端。如果客户端是恶意的,那么它就可以使用这些凭据,在授权服务器处冒充用户。

这种行为可能出乎开发者的意料,但在 [RFC9110] 第 15.4.8 节中已有定义。该状态代码不要求用户代理将 POST 请求重写为 GET 请求,从而在 POST 请求内容中删除表单数据。

在 HTTP [RFC9110] 中,只有状态码 303 明确地将 HTTP POST 方法强制重写为 HTTP GET 方法。对于其它所有状态码,包括流行使用的 302,用户代理都可以选择不将 POST 请求重写为 GET 请求,从而将用户凭据暴露给客户端。(然而实际上,大多数用户代理只会在 307 重定向中做出这种行为。)

本站使用 Vitepress 构建