1. Introduction (简介)
OAuth 2.0 [RFC6749] 公共客户端 (Public Clients) 容易受到授权码拦截攻击 (Authorization Code Interception Attack) 的威胁.
在这种攻击中, 攻击者在未受传输层安全协议 (TLS) 保护的通信路径中拦截从授权端点返回的授权码, 例如客户端操作系统内的应用程序间通信.
一旦攻击者获得了授权码的访问权限, 就可以使用它来获取访问令牌 (Access Token).
图1以图形方式展示了这种攻击. 在步骤 (1) 中, 运行在终端设备 (如智能手机) 上的原生应用程序通过浏览器/操作系统发出 OAuth 2.0 授权请求. 在这种情况下, 重定向端点 URI (Redirection Endpoint URI) 通常使用自定义 URI 方案 (Custom URI Scheme). 步骤 (1) 通过无法被拦截的安全 API 进行, 尽管在高级攻击场景中可能被观察到. 然后, 请求在步骤 (2) 中被转发到 OAuth 2.0 授权服务器. 由于 OAuth 要求使用 TLS, 此通信受 TLS 保护且无法被拦截. 授权服务器在步骤 (3) 中返回授权码. 在步骤 (4) 中, 授权码通过步骤 (1) 中提供的重定向端点 URI 返回给请求者.
请注意, 恶意应用程序可能会将自己注册为自定义方案的处理程序, 同时还有合法的 OAuth 2.0 应用程序. 一旦这样做, 恶意应用程序现在能够在步骤 (4) 中拦截授权码. 这使得攻击者能够分别在步骤 (5) 和步骤 (6) 中请求并获得访问令牌.
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
| End Device (e.g., Smartphone) |
| |
| +-------------+ +----------+ | (6) Access Token +----------+
| |Legitimate | | Malicious|<--------------------| |
| |OAuth 2.0 App| | App |-------------------->| |
| +-------------+ +----------+ | (5) Authorization | |
| | ^ ^ | Grant | |
| | \ | | | |
| | \ (4) | | | |
| (1) | \ Authz| | | |
| Authz| \ Code | | | Authz |
| Request| \ | | | Server |
| | \ | | | |
| | \ | | | |
| v \ | | | |
| +----------------------------+ | | |
| | | | (3) Authz Code | |
| | Operating System/ |<--------------------| |
| | Browser |-------------------->| |
| | | | (2) Authz Request | |
| +----------------------------+ | +----------+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
Figure 1: Authorization Code Interception Attack
这种攻击需要满足以下前提条件:
-
攻击者设法在客户端设备上注册恶意应用程序, 并注册了一个也被另一个应用程序使用的自定义 URI 方案. 操作系统必须允许多个应用程序注册相同的自定义 URI 方案.
-
使用 OAuth 2.0 授权码许可 (Authorization Code Grant).
-
攻击者可以访问 OAuth 2.0 [RFC6749] 的
client_id和client_secret(如果已配置). 所有 OAuth 2.0 原生应用程序客户端实例使用相同的client_id. 在客户端二进制应用程序中配置的密钥不能被视为机密. -
满足以下条件之一:
4a. 攻击者 (通过已安装的应用程序) 只能观察到来自授权端点的响应. 当
code_challenge_method值为plain时, 只能缓解这种攻击.4b. 更复杂的攻击场景允许攻击者观察到对授权端点的请求 (除了响应). 但是, 攻击者无法充当中间人 (Man-in-the-Middle). 这是由操作系统中泄漏的 HTTP 日志信息导致的. 要缓解这种情况,
code_challenge_method值必须设置为S256或由加密安全的code_challenge_method扩展定义的值.
虽然这是一个很长的前提条件列表, 但所描述的攻击已经在实际环境中被观察到, 并且必须在 OAuth 2.0 部署中加以考虑. 虽然 OAuth 2.0 威胁模型 ([RFC6819] 第 4.4.1 节) 描述了缓解技术, 但不幸的是, 它们不适用, 因为它们依赖于每个客户端实例的密钥或每个客户端实例的重定向 URI.
为了缓解这种攻击, 本扩展利用了一个动态创建的加密随机密钥, 称为 "代码验证器" (Code Verifier). 为每个授权请求创建唯一的代码验证器, 并将其转换后的值 (称为 "代码挑战" (Code Challenge)) 发送到授权服务器以获取授权码. 然后将获得的授权码与 "代码验证器" 一起发送到令牌端点, 服务器将其与先前接收到的请求代码进行比较, 以便客户端可以执行 "代码验证器" 的所有权证明 (Proof of Possession). 这可以作为缓解措施, 因为攻击者不知道这个一次性密钥, 因为它是通过 TLS 发送的并且无法被拦截.
1.1 Protocol Flow (协议流程)
+-------------------+
| Authz Server |
+--------+ | +---------------+ |
| |--(A)- Authorization Request ---->| | |
| | + t(code_verifier), t_m | | Authorization | |
| | | | Endpoint | |
| |<-(B)---- Authorization Code -----| | |
| | | +---------------+ |
| Client | | |
| | | +---------------+ |
| |--(C)-- Access Token Request ---->| | |
| | + code_verifier | | Token | |
| | | | Endpoint | |
| |<-(D)------ Access Token ---------| | |
+--------+ | +---------------+ |
+-------------------+
Figure 2: Abstract Protocol Flow
本规范为 OAuth 2.0 授权和访问令牌请求添加了额外的参数, 如图 2 的抽象形式所示.
A. 客户端创建并记录一个名为 "code_verifier" (代码验证器) 的密钥, 并派生其转换版本 t(code_verifier) (称为 "code_challenge" (代码挑战)), 该版本与转换方法 t_m 一起在 OAuth 2.0 授权请求中发送.
B. 授权端点像往常一样响应, 但记录 t(code_verifier) 和转换方法.
C. 然后客户端像往常一样在访问令牌请求中发送授权码, 但包括在 (A) 处生成的 "code_verifier" 密钥.
D. 授权服务器转换 "code_verifier" 并将其与 (B) 中的 t(code_verifier) 进行比较. 如果它们不相等, 则拒绝访问.
在 (B) 处拦截授权码的攻击者无法将其兑换为访问令牌, 因为他们不拥有 "code_verifier" 密钥.