1. 简介 (Introduction)
OAuth 2.0 [RFC6749] 公共客户端容易受到授权代码截获攻击 (authorization code interception attack) 的影响.
在这种攻击中, 攻击者会在未受传输层安全 (Transport Layer Security, TLS) 保护的通信路径中截获从授权端点返回的授权代码, 例如客户端操作系统内部的应用间通信.
一旦攻击者获得授权代码, 就可以用它获取访问令牌.
图 1 以图形方式展示了该攻击. 在步骤 (1) 中, 运行在终端设备上的原生应用程序 (例如智能手机应用) 通过浏览器/操作系统发起 OAuth 2.0 授权请求. 此时的重定向端点 URI 通常使用自定义 URI 方案. 步骤 (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. 更复杂的攻击场景允许攻击者观察发往授权端点的请求 (除响应外). 但是, 攻击者无法充当中间人. 这种情况由操作系统中的 HTTP 日志信息泄露造成. 为缓解此问题,
code_challenge_method值必须设置为S256, 或设置为由加密安全的code_challenge_method扩展定义的值.
虽然前置条件列表较长, 但上述攻击已经在真实环境中被观察到, 因此在 OAuth 2.0 部署中必须予以考虑. OAuth 2.0 威胁模型 ([RFC6819] 第 4.4.1 节) 描述了缓解技术, 但遗憾的是这些技术并不适用, 因为它们依赖每客户端实例密钥或每客户端实例重定向 URI.
为缓解该攻击, 本扩展使用一个动态创建的加密随机密钥, 称为 "code verifier". 每个授权请求都会创建唯一的代码验证器, 并将其转换后的值 (称为 "code challenge") 发送给授权服务器以获取授权代码. 随后, 获得的授权代码会与 "code verifier" 一起发送到令牌端点, 服务器将其与先前收到的请求代码进行比较, 从而验证客户端是否拥有 "code verifier". 该机制可以缓解攻击, 因为攻击者不知道这个一次性密钥; 该密钥通过 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" 秘密值.