| RFC 9700 | OAuth 2.0 安全 BCP | 2025 年 1 月 |
| Lodderstedt, et al. | 最佳当前实践 | [页] |
本文档描述 OAuth 2.0 的最佳当前安全实践。它更新 并扩展了 RFC 6749、RFC 6750 和 RFC 6819 中给出的威胁模型和安全建议,以纳入自 OAuth 2.0 发布以来所积累的实践 经验,并涵盖因 OAuth 2.0 更广泛 应用而相关的新威胁。此外,它弃用了某些被认为安全性较低、甚至不安全的 运行模式。¶
本备忘录记录了一项互联网最佳当前实践。¶
本文档是互联网工程任务组 (IETF) 的产物。它代表了 IETF 社区的共识。它已经 接受公开审查,并已获 互联网工程指导组 (IESG) 批准发布。有关 BCP 的更多信息 可见 RFC 7841 第 2 节。¶
有关本文档当前状态、任何 勘误以及如何提供反馈的信息,可从 https://www.rfc-editor.org/info/rfc9700 获取。¶
Copyright (c) 2025 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
自 [RFC6749] 和 [RFC6750] 发布以来,OAuth 2.0(本文档中简称为 “OAuth”)在市场中获得了巨大牵引力, 并已成为 API 保护的标准,以及使用 OpenID Connect [OpenID.Core] 进行联合登录的基础。 虽然 OAuth 被用于 各种场景和不同类型的部署,但可以观察到以下 挑战:¶
OAuth 实现正通过已知的实现 弱点和反模式(即被认为 不安全的众所周知模式)受到攻击。尽管这些威胁大多已在 OAuth 2.0 威胁模型与安全考量 [RFC6819] 中讨论,但持续的利用 表明需要更具体的建议、更容易实现的 缓解措施,以及更深入的纵深防御。¶
OAuth 正被用于安全要求高于 最初所考虑水平的环境,例如开放银行、电子健康、电子政务和 电子签名。这些用例要求更严格的指南和 额外保护。¶
OAuth 正被用于比最初预期动态得多的设置中, 从而在安全方面带来新的挑战。这些挑战超出了 [RFC6749]、[RFC6750] 和 [RFC6819] 的原始范围。¶
OAuth 最初假定客户端、 授权服务器和资源服务器之间存在静态关系。服务器的 URL 在部署时为客户端所知,并构成这些参与方之间 信任关系的锚点。验证 客户端是否正在与合法服务器通信,是基于 TLS 服务器 身份认证(见 [RFC6819] 第 4.5.4 节)。随着 OAuth 的采用日益广泛, 这个简单模型逐渐瓦解,并在若干 场景中被一种动态建立关系的方式所取代, 即一侧的客户端与另一侧特定部署的授权服务器和资源服务器 之间动态建立关系。这样,同一个 客户端可用于访问不同提供者的服务(在 标准 API 的情况下,例如电子邮件或 OpenID Connect),或在多租户环境中 作为特定租户的前端。 OAuth 扩展,例如 OAuth 2.0 动态客户端注册 协议 [RFC7591] 和 OAuth 2.0 授权服务器元数据 [RFC8414],被开发出来以支持 在动态场景中使用 OAuth。¶
技术已经发生变化。例如,浏览器在 重定向请求时处理片段的方式已经改变,而隐式授权 底层的安全模型也随之改变。¶
本文档提供更新后的安全建议,以应对这些 挑战。它引入了超出现有 规范(例如 OAuth 2.0 [RFC6749] 和 OpenID Connect [OpenID.Core]) 中定义内容的新要求,并弃用某些被认为安全性较低、甚至 不安全的运行模式。但是,本文档并不取代 [RFC6749]、[RFC6750] 和 [RFC6819] 中给出的安全建议,而是对这些文档进行补充。¶
自然,并非所有现有生态系统和实现都 与新要求兼容,遵循本文档中描述的最佳实践 可能会破坏互操作性。尽管如此,仍建议 实现者尽快在可行时升级其实现和生态系统。¶
正在开发中的 OAuth 2.1 [OAUTH-V2.1] 将纳入 本文档中的安全建议。¶
本文档其余部分组织如下:第 2 节 总结了每个 OAuth 实现者最重要的最佳实践。 第 3 节 给出了更新后的 OAuth 攻击者 模型。第 4 节 对 在实际环境中(截至撰写时)可发现的威胁和实现问题 进行详细分析,并讨论潜在对策。¶
本文档中的关键词 “必须”、“不得”、 “要求”、“应”、“不应”、 “应该”、“不应该”、 “建议”、“不建议”、 “可以” 和 “可选” 应按照 BCP 14 [RFC2119] [RFC8174] 中的描述解释,但仅当它们以全大写形式出现时, 如此处所示。¶
本规范使用 OAuth 2.0 [RFC6749] 定义的术语 “access token”、“authorization endpoint”、“authorization grant”、“authorization server”、“client”、 “client identifier”(client ID)、“protected resource”、“refresh token”、“resource owner”、“resource server” 和 “token endpoint”。¶
“开放重定向器”是 Web 服务器上的一个端点,它将用户的 浏览器转发到从查询参数获得的任意 URI。¶
本节描述在撰写时被认为是最佳实践的 核心安全机制和措施集合。关于这些安全机制和措施的细节 (包括详细的攻击 描述)以及较少使用选项的要求,见 第 4 节。¶
在将客户端重定向 URI 与预注册 URI 进行比较时,
授权
服务器必须使用精确字符串匹配,但原生应用的
localhost 重定向 URI 中的端口号除外(见 第 4.1.3 节)。此
措施有助于防止授权码和
访问令牌泄露(见 第
4.1 节)。它还可帮助检测
混淆攻击(见 第 4.4 节)。¶
客户端和授权服务器不得暴露 将用户浏览器转发到 从查询参数获得的任意 URI(开放重定向器)的 URL,如 第 4.11 节 所述。开放 重定向器可能导致 授权码和访问令牌被外泄。¶
客户端必须防止跨站请求伪造
(CSRF)。在此
上下文中,CSRF 指发送到重定向端点的请求并非
源自授权服务器,而是源自恶意第三方
(详见 [RFC6819]
第 4.4.1.8 节)。已经
确保授权服务器支持 Proof Key for Code Exchange (PKCE) [RFC7636] 的客户端可以
依赖 PKCE 提供的 CSRF 保护。在 OpenID Connect 流程中,
nonce 参数提供 CSRF 保护。否则,必须使用
携带在 state 参数中、与用户代理安全绑定的一次性
CSRF 令牌来进行 CSRF 保护(见
第 4.7.1 节)。¶
当 OAuth 客户端可以与多个授权服务器交互时, 要求具备针对混淆攻击的防御(见 第 4.4 节)。为此,客户端 应该¶
iss
参数作为对策,或者¶
iss 值的替代对策
(例如 [OpenID.Core] 中 ID Token 的
iss claim,或 [OpenID.JARM] 响应中的该值),并按
[RFC9207] 中的描述处理
该值。¶
在没有这些选项的情况下,客户端可以 改用不同的重定向 URI 来标识授权端点和令牌端点,如 第 4.4.2 节 所述。¶
对于重定向可能包含用户 凭据的请求的授权服务器, 必须避免意外转发这些用户凭据(详见 第 4.12 节)。¶
客户端必须防止授权码 注入攻击(见 第 4.5 节),并使用以下选项之一防止授权码被滥用:¶
nonce 参数和
ID Token 中相应的 Claim。¶
无论如何,PKCE challenge 或 OpenID Connect nonce
必须
特定于事务,并与启动该事务的客户端和用户代理
安全绑定。
鼓励授权服务器作出合理努力,检测并
防止对 PKCE challenge 或 OpenID Connect
nonce 使用恒定值。¶
注意:虽然 PKCE 最初被设计为保护原生 应用的机制,但本建议适用于所有类型的 OAuth 客户端,包括 Web 应用。¶
使用 PKCE 时,客户端应该使用不会在授权请求中
暴露 PKCE verifier 的 PKCE
code challenge 方法。
否则,能够读取授权请求的攻击者(参见 攻击者 (A4),见 第 3 节)可以破坏 PKCE
提供的安全性。目前,S256 是唯一这样的方式。¶
如果客户端在
授权请求中发送有效的 PKCE code_challenge 参数,授权服务器必须在
令牌端点强制正确使用
code_verifier。¶
授权服务器必须通过确保
仅当授权请求中存在
code_challenge 参数时,才接受包含 code_verifier 参数的
令牌请求,来缓解 PKCE
降级攻击;详见
第 4.8.2 节。¶
授权服务器必须提供一种方式
来检测其对
PKCE 的支持。建议授权服务器在其授权服务器元数据
[RFC8414] 中发布
code_challenge_methods_supported 元素,
其中包含所支持的 PKCE challenge 方法(客户端可用它来检测
PKCE 支持)。授权服务器可以
改为提供一种特定于部署的方式,以确保或确定授权
服务器对 PKCE 的支持。¶
隐式授权(response type token)以及其他
导致授权服务器在
授权响应中颁发访问令牌的 response type,容易受到
第 4.1、
4.2、4.3 和
4.6 节所述的访问令牌泄露和
访问令牌重放攻击。¶
此外,对于在 授权响应中颁发的访问令牌,目前不存在 将访问令牌绑定到特定客户端的标准化发送者约束方法 (如 第 2.2 节 中所建议)。这意味着攻击者可以在资源端点 使用泄露或被盗的访问令牌。¶
为避免这些问题,客户端不应该
使用隐式
授权(response type token)或其他会在授权响应中颁发
访问令牌的 response type,除非已防止授权响应中的访问令牌注入,
并且已缓解前述令牌泄露
向量。¶
客户端应该改用
code 这个 response
type(即授权码授权类型),如 第
2.1.1 节 所规定,或任何其他
导致授权服务器在令牌
响应中颁发访问令牌的 response type,例如 code id_token response type。这使得
授权服务器能够检测攻击者的重放尝试,并且
通常会减少攻击面,因为访问令牌不会
暴露在 URL 中。它还允许授权服务器对所颁发的令牌
进行发送者约束(见 第 2.2 节)。¶
与访问令牌关联的权限应该限制为 特定应用或用例所需的最低权限。这 防止客户端超出 资源所有者授权的权限。它还防止用户超出其 相应安全策略所授权的权限。权限限制 还有助于降低访问令牌泄露的影响。¶
特别是,访问令牌应该
被受众限制到特定资源
服务器,或者在不可行时限制到一小组资源服务器。为实现这一点,
授权服务器将
访问令牌与某些资源服务器关联,并且每个资源
服务器都有义务针对每个请求验证,该请求中发送的访问
令牌是否意在用于该特定
资源服务器。如果不是,资源服务器必须拒绝
服务相应请求。[RFC9068] 中定义的 aud
claim 可以
用于对访问令牌进行受众限制。客户端和授权服务器可以利用
[RFC6749] 和
[RFC8707] 分别规定的参数
scope 或 resource,来确定它们想要访问的
资源服务器。¶
此外,访问令牌应该限制到
资源服务器或资源上的某些资源
和动作。为实现这一点,
授权服务器将访问令牌与
相应资源和动作关联,并且每个资源服务器都有义务
针对每个请求验证,该请求中发送的访问令牌是否
意在用于该特定资源上的
特定动作。如果不是,资源服务器必须拒绝服务
相应请求。客户端和授权服务器可以利用
[RFC6749] 中规定的参数 scope,以及
[RFC9396] 中规定的
authorization_details,
来确定这些资源和/或动作。¶
资源所有者密码凭据授权 [RFC6749] 不得 使用。此授权类型会以不安全的方式将资源 所有者的凭据暴露给客户端。即使客户端是良性的,使用此授权也会导致 攻击面增加(即凭据可能在授权服务器之外的更多地方泄露), 并训练用户在授权 服务器之外的位置输入其凭据。¶
此外,资源所有者密码凭据授权并非设计用于 配合双因素身份认证以及需要 多个用户交互步骤的身份认证流程。使用密码学凭据进行身份认证 (参见 WebCrypto [W3C.WebCrypto]、 WebAuthn [W3C.WebAuthn])可能 无法用这种授权类型实现,因为它通常绑定到特定的 Web origin。¶
如果在特定部署中可行,可以建立 客户端凭据颁发/注册流程,并确保这些凭据的机密性, 则授权服务器应该强制执行客户端 身份认证。¶
建议使用非对称密码学进行
客户端身份认证,例如用于 OAuth 2.0 的相互 TLS [RFC8705],或符合 [RFC7521] 和 [RFC7523] 的签名 JWT
(“Private Key JWT”)。后者在 [OpenID.Core]
中定义为客户端
身份认证方法 private_key_jwt)。
当使用非对称密码学进行客户端身份认证时,授权
服务器无需存储敏感的对称密钥,使这些
方法对密钥泄露更具鲁棒性。¶
使用 OAuth 授权服务器元数据 [RFC8414] 可以帮助提升 OAuth 部署的安全性:¶
因此,建议授权 服务器按照 [RFC8414] 发布 OAuth 授权服务器元数据, 并建议客户端使用该授权服务器元数据(可用时)来配置自身。¶
在 第
4.15.1 节 所述条件下,
授权服务器不应该允许客户端影响其
client_id 或
任何其他可能导致与真实资源所有者混淆的 claim。¶
建议在客户端与 资源服务器之间按照 [BCP195] 使用端到端 TLS。如果 TLS 流量需要在中间方终止,请参阅 第 4.13 节 以获取进一步的安全 建议。¶
授权响应不得通过未加密的网络
连接传输。为此,授权服务器不得允许
使用 http
scheme 的重定向 URI,但使用回环接口重定向的原生客户端除外,
如 [RFC8252] 第 7.3 节 所述。¶
如果授权响应通过类似 postMessage [WHATWG.postmessage_api] 的浏览器内通信技术 发送,而不是通过 HTTP 重定向发送,则浏览器内消息的发起方和接收方 必须如 第 4.17 节 所述进行严格 验证。¶
为支持基于浏览器的客户端,此类客户端直接访问的端点
(包括 Token Endpoint、Authorization Server Metadata Endpoint、jwks_uri
Endpoint 和 Dynamic Client Registration Endpoint)可以支持
使用
Cross-Origin Resource Sharing (CORS) [WHATWG.CORS]。
但是,授权端点不得
支持 CORS,因为客户端不会直接访问此
端点;相反,客户端会将用户代理重定向到该端点。¶
在 [RFC6819] 中,提出了一个 威胁模型,用于描述 OAuth 部署必须防护的威胁。在此过程中,[RFC6819] 对攻击者及其能力作出了 某些假设,也就是说,它隐含地 建立了一个攻击者模型。下面将显式说明该攻击者模型, 并对其进行更新和扩展,以考虑涉及多个参与方的潜在动态 关系(如 第 1 节 所述),纳入 新类型的攻击者,并更清晰地定义攻击者模型。¶
本文档的目标是确保资源 所有者(通过用户代理)在授权服务器上的授权,以及随后在 资源服务器上使用访问令牌的过程,在实际可行范围内 至少能抵御以下攻击者。¶
Web 攻击者可以建立并运行任意数量的 网络端点(除“诚实”的端点之外),包括浏览器和 服务器。Web 攻击者可以建立资源所有者会访问的网站, 运行自己的用户代理,并参与协议。¶
特别是,Web 攻击者可以运行在 授权服务器上注册的 OAuth 客户端,并且可以运行自己的授权服务器和 资源服务器,资源所有者和其他资源所有者可以(与“诚实”的服务器并行)使用这些服务器。¶
还必须假定 Web 攻击者可以随时诱使用户 将其浏览器导航到攻击者任意选择的 URI。实践中,这 可以通过多种方式实现,例如,向广告网络中注入恶意 广告,或发送 看似合法的电子邮件。¶
Web 攻击者可以使用自己的用户凭据来创建新 消息,也可以使用其先前获知的任何秘密。例如, 如果 Web 攻击者通过配置错误的重定向 URI 获知了用户的授权码, 则该 Web 攻击者随后可以 尝试将该授权码兑换为访问令牌。¶
但是,他们不能读取或操纵并非 发送给他们的消息(例如,发送到不受攻击者控制的授权服务器 URL 的消息)。¶
网络攻击者还对协议参与者通信所经由的 网络拥有完全控制权。他们可以 窃听、操纵和伪造消息,但当这些 消息受到密码学方法(例如 TLS)正确保护时除外。 网络攻击者还可以阻止任意消息。¶
Web 攻击者的一个例子可以是互联网 服务提供商的客户,而网络攻击者可以是该互联网服务 提供商本身、在公共(Wi-Fi)网络中使用 ARP 欺骗的攻击者,或例如能够访问互联网 交换点的国家支持攻击者。¶
上述攻击者 (A1) 和 (A2) 符合 OAuth 形式化分析 工作中使用的攻击者模型 [arXiv.1601.01229]。 这是一个最小攻击者模型。 实现者必须考虑其 OAuth 实现环境中的所有可能类型的攻击者。 例如,在 [arXiv.1901.11520] 中, 使用了一个非常强的攻击者模型,其中包括对 令牌端点拥有完全控制权的攻击者。这建模了 生态系统中端点可能配置错误所造成的影响,而这种情况可以通过使用 第 2.6 节 中所述的授权服务器元数据来避免。因此,此类攻击者未在此列出。¶
然而,以往针对 OAuth 的攻击表明,以下类型的 攻击者尤为相关:¶
能够读取但不能修改 授权请求内容的攻击者(即授权请求可能 以上述相同方式泄露给攻击者)。¶
(A3)、(A4) 和 (A5) 通常会与 (A1) 或 (A2) 同时出现。 攻击者可以协作以达成共同目标。¶
注意,攻击者 (A1) 或 (A2) 可以是资源所有者,或 扮演资源所有者。例如,此类攻击者可以使用自己的浏览器, 在客户端或资源服务器处重放通过上述任何攻击获得的 令牌或授权码。¶
本节详细描述针对 OAuth 实现的攻击, 并给出潜在对策。已在 [RFC6819] 中涵盖的攻击和缓解措施不会在此列出,除非提出了新的 建议。¶
本节还为某些情形和协议 选项定义了额外要求(超出 第 2 节 中定义的要求)。¶
某些授权服务器允许客户端注册重定向 URI 模式,而不是完整的重定向 URI。随后授权服务器 在运行时将授权 端点上的重定向 URI 参数值与已注册的模式匹配。这种方法 允许客户端将事务状态编码到额外的重定向 URI 参数中,或为多个 重定向 URI 注册单个模式。¶
事实证明,与精确重定向 URI 匹配相比,这种方法实现起来更复杂, 管理起来也更容易出错。实践中已经观察到多起 成功攻击,它们利用了模式匹配 实现或具体配置中的缺陷(例如,见 [research.rub2])。 对重定向 URI 验证不足会有效破坏 客户端识别或身份认证(取决于授权和客户端 类型),并允许攻击者通过以下方式获得授权码或 访问令牌:¶
这些攻击在以下小节中详细展示。¶
对于使用授权类型 code 的客户端,攻击可能
如下进行:¶
假设重定向 URL 模式
https://*.somesite.example/* 已
为客户端 ID 为 s6BhdRkqt3 的客户端注册。其
意图是允许 somesite.example 的任意子域成为该客户端的
有效重定向 URI,例如
https://app1.somesite.example/redirect。然而,授权服务器上的朴素实现可能会将
通配符 * 解释为
“任意字符”,而不是“域名中有效的任意字符”。因此,
授权服务器可能允许
https://attacker.example/.somesite.example 作为重定向 URI,
尽管 attacker.example 是一个不同的域,并可能
由恶意方控制。¶
随后可按如下方式实施攻击:¶
首先,攻击者需要诱骗用户在其浏览器中打开一个
被篡改的
URL,该 URL 会启动一个由攻击者
控制的页面,例如 https://www.evil.example(见 第 3 节 中的攻击者 A1)。¶
此 URL 使用合法客户端的客户端 ID 向授权端点发起以下授权请求(换行 仅用于显示):¶
授权服务器验证重定向 URI,并将其与
客户端 s6BhdRkqt3 的已注册重定向 URL 模式进行比较。
授权请求被处理并呈现给用户。¶
如果用户没有看到重定向 URI,或没有识别出 该攻击,则会颁发授权码,并立即将其发送到攻击者的 域。如果启用了授权的自动批准 (根据 [RFC6749],不建议公共客户端这样做),则攻击甚至可以在没有用户 交互的情况下执行。¶
如果攻击者冒充公共客户端,则攻击者可以 在相应的令牌端点将授权码兑换为令牌。¶
对于机密客户端,此攻击不容易奏效,因为 授权码交换需要使用合法客户端的 秘密进行身份认证。但是,攻击者可以使用合法机密 客户端通过执行授权码 注入攻击来兑换授权码;见 第 4.5 节。¶
需要注意的是,即使授权
服务器正确处理通配符,仍可能存在重定向 URI 验证
漏洞。例如,假设客户端
注册重定向 URL 模式 https://*.somesite.example/*,且
授权服务器将其解释为“允许指向
somesite.example 域中任意主机的重定向 URI”。如果攻击者设法在
somesite.example 中建立一个主机或子域,则攻击者可以冒充合法客户端。
例如,这
可能由子域接管攻击造成 [research.udel],其中一个
过时的 CNAME 记录(例如 external-service.somesite.example)
指向一个不再存在的外部 DNS 名称(例如
customer-abc.service.example),并可被攻击者接管
(例如,通过在外部服务上注册为 customer-abc)。¶
上述攻击同样适用于隐式授权。如果 攻击者能够将授权响应发送到攻击者控制的 URI, 攻击者将直接访问携带 访问令牌的片段。¶
此外,隐式授权(以及使用
[OAuth.Responses] 中定义的
response_mode=fragment 时的其他授权)
也可能遭受另一类
攻击。该攻击利用了这样一个事实:如果 Location 标头不
包含片段,用户代理会将片段重新附加到重定向的目标 URL
上(见 [RFC9110] 第 17.11 节)。这里描述的攻击
将这种行为与作为开放
重定向器的客户端(见 第
4.11.1 节)结合起来,以获得访问令牌。这使得
即使非常狭窄的重定向 URI 模式也可能被绕过,但严格 URL
匹配不会被绕过。¶
假设客户端 s6BhdRkqt3 的已注册 URL 模式
是
https://client.somesite.example/cb?*,即允许向
https://client.somesite.example/cb 的重定向
携带任意参数。不幸的是,
该客户端暴露了一个开放重定向器。该端点支持一个
参数 redirect_to,它接受目标 URL,并会使用 HTTP Location 标头重定向 303 将
浏览器发送到该 URL。¶
现在可以按如下方式实施攻击:¶
首先,与上面一样,攻击者需要诱骗用户在其浏览器中打开
一个被篡改的 URL,该 URL 会启动一个由
攻击者控制的页面,例如 https://www.evil.example。¶
随后,该网站发起一个授权请求,它与针对 code flow 的攻击中的请求非常相似。
与上面不同的是,它通过将
redirect_to=https://attacker.example 编码进
重定向 URI 的参数来利用开放重定向器,并且使用 response type token
(换行仅用于显示):¶
然后,由于重定向 URI 匹配已注册模式, 授权服务器允许该请求,并在 303 重定向中发送生成的访问 令牌(为便于阅读,省略了一些响应参数):¶
在 client.somesite.example 上,请求到达开放
重定向器。该端点将
读取 redirect 参数,并向 URL https://attacker.example/ 发出 HTTP 303 Location 标头
重定向。¶
由于 client.somesite.example 上的重定向器没有在 Location 标头中包含
片段,用户代理会将原始
片段 #access_token=2YotnFZFEjr1zCsicMWpAA&... 重新附加到
URL,并导航到以下 URL:¶
https://attacker.example/#access_token=2YotnFZFEjr1z...¶
随后,位于 attacker.example 的攻击者页面可以
访问该
片段并获得访问令牌。¶
正确实现和管理模式匹配的复杂性显然
会造成安全问题。因此,本文档建议通过使用精确重定向 URI 匹配来简化所需的
逻辑和配置。这意味着
授权服务器必须确保两个 URI 相等;
详见 [RFC3986] 第 6.2.1 节,简单字符串比较。
唯一例外是
使用 localhost URI 的原生应用:在这种情况下,授权服务器
必须允许可变
端口号,如 [RFC8252] 第
7.3 节 所述。¶
其他建议:¶
#_),来阻止浏览器将片段
重新附加到重定向 URL。¶
如果可以验证包含 重定向 URI 的授权请求的来源和完整性,例如,在使用 [RFC9101] 或 [RFC9126] 并配合客户端 身份认证时,则授权服务器可以信任该 重定向 URI,而无需进一步检查。¶
授权请求 URI 或授权
响应 URI 的内容,可能会通过
Referer HTTP 标头(见 [RFC9110] 第 10.1.3
节)无意中披露给攻击者,
分别来自授权服务器或客户端的网站泄露。最
重要的是,授权码或 state 值可能以
这种方式被披露。尽管 [RFC9110] 第
10.1.3 节 中另有规定,
由于浏览器实现问题,通过 URI 片段传递的访问令牌也可能发生相同情况,
Chromium 项目中一个(现已修复的)问题对此作了说明
[bug.chromium]。¶
来自 OAuth 客户端的泄露要求客户端在成功授权请求的结果下 渲染一个页面,该页面¶
一旦浏览器导航到攻击者页面,或加载第三方内容,
攻击者就会收到授权响应
URL,并能够提取 code 或 state(以及可能的
access_token)。¶
通过 Referer 标头获知有效授权码或访问令牌的攻击者,
可以执行第
4.1.1、4.5 和
4.6 节所述的攻击。如果攻击者
获知 state,则使用 state 实现的 CSRF
保护会失效,从而导致
[RFC6819] 第
4.4.1.8 节 所述的 CSRF 攻击。¶
作为 OAuth 授权响应结果而渲染的页面,以及 授权端点不应该包含第三方 资源或指向外部站点的 链接。¶
以下措施可进一步降低攻击成功的 可能性:¶
Referrer-Policy:
no-referrer 会完全抑制从生成文档发起的所有请求中的 Referer
标头。¶
如 [RFC6749] 第 4.1.2 节 所述,授权码 必须在令牌端点首次使用后由授权服务器使其失效。 例如,如果授权服务器在合法客户端兑换授权码后使其失效, 则攻击者稍后兑换 该授权码将失败。¶
如果攻击者设法在合法客户端之前 将授权码兑换为令牌,则这不能缓解攻击。因此, [RFC6749] 进一步建议,当有人尝试两次兑换同一授权码时,授权服务器应该撤销此前基于该授权码颁发的所有 令牌。¶
state 值应该在客户端于重定向端点首次使用后使其失效。
如果实现了这一点,并且
攻击者通过来自
客户端网站的 Referer 标头收到令牌,则 state 已被使用并由
客户端使其失效,攻击者无法再次使用它。(如果 state 从
授权服务器网站泄露,这并无帮助,因为此时 state
尚未在客户端的重定向端点使用。)¶
对授权响应使用 form post response mode, 而不是重定向 (见 [OAuth.Post])。¶
授权码和访问令牌可能最终出现在浏览器的 已访问 URL 历史记录中,从而引发 以下所述的攻击。¶
如果客户端或已经拥有令牌的网站故意导航到类似
provider.com/get_user_profile?access_token=abcdef 的页面,访问令牌可能最终进入浏览器历史记录。
[RFC6750]
不鼓励这种做法,并建议通过标头传输令牌,
但实践中,网站经常在查询
参数中传递访问令牌。¶
在隐式授权的情况下,类似
client.example/redirection_endpoint#access_token=abcdef 的 URL
也可能因提供者授权端点的重定向而最终进入
浏览器历史记录。¶
对策:¶
混淆攻击可能发生在 OAuth 客户端与 两个或多个授权服务器交互,且至少一个授权 服务器受攻击者控制的场景中。例如,如果攻击者使用动态注册将 客户端注册到自己的授权服务器,或者如果某个授权服务器 被攻陷,就可能出现这种情况。¶
攻击的目标是获得未被攻陷的授权服务器的授权码或访问 令牌。实现方式是 诱骗客户端将这些凭据发送到被攻陷的 授权服务器(攻击者),而不是在 未被攻陷的授权/资源 服务器的相应端点上使用它们。¶
这里的描述遵循 [arXiv.1601.01229],并在下面概述攻击的 变体。¶
前置条件:为使此攻击变体生效,假定¶
下面进一步假设客户端已在 H-AS(URI:
https://honest.as.example,客户端 ID: 7ZGZldHQ)以及 A-AS
(URI:
https://attacker.example,客户端 ID: 666RVZJTA)注册。以下示例中的 URL
为便于展示而缩短,仅包含与
攻击相关的参数。¶
针对授权码授权的攻击:¶
https://attacker.example/authorize?response_type=code&client_id=666RVZJTA。¶
https://honest.as.example/authorize?response_type=code&client_id=7ZGZldHQ¶
用户授权客户端访问其在 H-AS 的 资源。(注意,警觉的用户此时可能会发现他们本来打算使用 A-AS 而不是 H-AS。列出的第一个攻击变体没有此 限制。)H-AS 颁发授权码,并将其(通过浏览器)发送回客户端。¶
由于客户端仍假定该授权码由 A-AS 颁发, 因此它会尝试在 A-AS 的令牌端点兑换该授权码。¶
因此,攻击者获得授权码,并可将 该授权码兑换为访问令牌(对于公共客户端),或执行 授权码注入攻击,如 第 4.5 节 所述。¶
变体:¶
当 OAuth 客户端只能与一个授权 服务器交互时,不需要混淆 防御。然而,在 OAuth 客户端与两个 或更多授权服务器交互的场景中,客户端必须防止 混淆攻击。下面讨论两种 不同方法。¶
对于两种防御,客户端必须针对每个授权请求存储 它发送授权请求到的 颁发者,并将该信息绑定到用户 代理。颁发者通过关联的元数据,作为 流程中要使用的授权端点和令牌端点组合的抽象 标识符。如果颁发者标识符不可用(例如,如果既未使用 OAuth 授权服务器元数据 [RFC8414],也未使用 OpenID Connect Discovery [OpenID.Discovery]),则可以 改用该元组的其他唯一标识符或该元组本身。 为简洁起见,下面将此类特定于部署的标识符 归入颁发者(或颁发者标识符)。¶
需要注意的是,仅存储授权服务器 URL 不足以识别 混淆攻击。攻击者可能将未被攻陷的授权服务器的 授权端点 URL 声明为 “自己的”授权服务器 URL,但声明一个受其自己 控制的令牌端点。¶
此防御要求授权服务器在授权响应中将 其颁发者标识符 发送给客户端。客户端收到授权 响应时,必须将收到的颁发者 标识符与已存储的 颁发者标识符进行比较。如果不匹配,客户端必须中止 交互。¶
可通过不同方式将此颁发者标识符 传输给客户端:¶
iss 传输,该参数定义于
[RFC9207]。¶
iss claim。¶
在这两种情况下,iss 值必须按照
[RFC9207] 进行评估。¶
虽然此防御可能需要部署新的 OAuth 功能来传输 颁发者信息,但它是一种稳健且相对简单的混淆 防御。¶
对于此防御,客户端必须 为其交互的每个颁发者使用不同的重定向 URI。¶
客户端必须通过将该颁发者的不同重定向 URI 与接收授权响应的 URI 进行比较,检查 授权响应是否来自正确 颁发者。如果不匹配,客户端 必须中止该流程。¶
虽然此防御建立在现有 OAuth 功能之上,但它不能用于 客户端只为使用许多不同 颁发者而注册一次的场景(例如某些开放银行方案),并且由于与 客户端注册紧密集成,自动部署更困难。¶
此外,攻击者可能通过在“诚实”的授权服务器上注册一个新客户端来绕过 此防御提供的保护,所使用的 redirect URI 是客户端分配给攻击者授权服务器的 URI。然后,攻击者可以按照上述方式运行 攻击,将 客户端 ID 替换为其新创建客户端的客户端 ID。¶
因此,只有在其他选项不可用时,才应该 使用此防御。¶
已获得授权响应中所含授权码访问权限的攻击者 (见 第 3 节 中的 攻击者 (A3)),可以尝试将该 授权码兑换为访问令牌,或以其他方式使用 授权码。¶
如果授权码是为公共客户端创建的, 攻击者可以将授权码发送到 授权服务器的令牌端点,从而获得访问令牌。该攻击已在 [RFC6819] 第 4.4.1.1 节 中描述。¶
对于机密客户端,或在某些特殊情况下,攻击者可以 执行授权码注入攻击,如下所述。¶
在授权码注入攻击中,攻击者试图将 被盗授权码注入到攻击者自己与客户端的会话中。其 目标是将攻击者在客户端的会话与受害者的 资源或身份关联起来,从而让攻击者至少获得对 受害者资源的有限访问。¶
除了绕过机密客户端的客户端身份认证之外,此攻击的其他 用例包括:¶
除这些特殊情况外,当授权码是为公共客户端创建时,授权码注入通常 并不有吸引力,因为如上所述,将授权码 发送到令牌端点是一种更简单且更强大的攻击。¶
授权码注入攻击的工作方式如下:¶
redirect_uri、客户端的客户端 ID 和
客户端秘密(或其他客户端身份认证方式)。¶
显然,如果授权码颁发给了 另一个客户端 ID,例如攻击者建立的客户端,则 check-in 步骤(步骤 5)会失败。如果授权码已被 合法用户兑换且仅可使用一次,该检查 也会失败。¶
如果授权服务器存储了授权请求中使用的
完整重定向 URI,并将其与
redirect_uri 参数进行比较,也应能检测到尝试注入通过被操纵重定向
URI 获得的授权码。¶
如果初始授权 请求中包含了 "redirect_uri" 参数(如第 4.1.1 节所述), 则确保 "redirect_uri" 参数存在; 如果包含该参数,则确保 它们的值完全相同。¶
在 第 4.5.1 节 所述的攻击场景中,合法 客户端会使用其始终用于 授权请求的正确重定向 URI。但此 URI 不会与攻击者使用的被篡改的 重定向 URI 匹配(否则重定向不会 落到攻击者页面)。因此,授权服务器会检测到 攻击并拒绝兑换授权码。¶
如果满足某些条件,此检查还可以检测尝试注入从同一客户端 在另一台设备上的另一个实例获得的授权 码:¶
但是,这种方法与在授权端点强制执行精确
redirect
URI 匹配的理念相冲突。此外,据观察,
提供者经常忽略此阶段的 redirect_uri 检查
要求,可能是因为从规范文本看它似乎并非
安全关键。¶
其他提供者只是将 redirect_uri
参数与已注册的重定向 URI 模式进行模式匹配。这避免了
授权服务器为每个事务存储实际 redirect
URI 与相应授权码之间的关联。然而,
这种检查显然不符合
规范的意图,因为它没有考虑被篡改的重定向 URI。因此,
任何尝试注入使用合法客户端的
client_id 获得的授权码,或通过在另一台设备上利用合法
客户端获得的授权码,都不会在相应
部署中被检测到。¶
还假定 [RFC6749] 第 4.1.3 节 中定义的要求会增加 客户端实现复杂性,因为客户端 需要存储或重构用于调用 令牌端点的正确重定向 URI。¶
用于客户端身份认证的非对称方法并不能阻止此 攻击,因为 合法客户端会在令牌端点进行身份认证。¶
因此,本文档建议改为使用下面描述的一种 机制,在某个事务的上下文中,将每个授权 码绑定到某台设备上(或某个 用户代理中)的特定客户端实例。¶
有两种良好的技术方案可将授权 码绑定到客户端 实例,如下所示。¶
[RFC7636] 中规定的 PKCE 机制可用作对策
(尽管它最初设计用于保护原生应用)。当
攻击者尝试注入授权码时,
code_verifier 检查会失败:客户端使用其正确的 verifier,但该
授权码关联的是与此
verifier 不匹配的 code_challenge。¶
PKCE 不仅能防护授权码
注入攻击,还能保护为公共客户端创建的授权码:PKCE 确保
攻击者在不知道 code_verifier 的情况下,无法在
授权服务器的令牌端点兑换被盗授权码。¶
OpenID Connect 现有的 nonce 参数可以
防护授权
码注入攻击。nonce 值一次性使用,并由
客户端创建。客户端应将其绑定到用户代理会话,并在初始请求中发送给 OpenID Provider (OP)。
OP 会将收到的
nonce 值放入作为令牌端点授权码交换一部分所颁发的 ID Token 中。
如果攻击者在授权响应中注入
授权码,则客户端会话中的 nonce 值与从令牌
端点收到的 ID Token 中的 nonce 值将不匹配,攻击会被
检测到。这里的假设是,攻击者无法获得受害者设备上的用户代理
状态(攻击者是从该设备窃取相应
授权码的)。¶
需要注意的是,只有当客户端
正确检查从令牌端点获得的 ID Token 中的 nonce 参数,并且在该检查成功之前不使用任何
颁发的令牌时,此对策才有效。更准确地说,使用 nonce 参数防护自身免受 code injection 的客户端¶
nonce,
即使已从授权响应获得了另一个 ID Token
(例如 response_type=code+id_token),并且¶
需要注意的是,nonce 无法保护
公共客户端的授权码,因为攻击者无需执行授权码
注入攻击。相反,攻击者可以直接携带
被盗授权码调用令牌端点。¶
如果攻击者能够修改受害者授权请求中使用的
nonce 或 code_challenge 值,
就可以绕过上述对策。攻击者可以将这些值
修改为与其自己会话中客户端所选择的值相同,
即上面攻击 步骤 2 中的值。
(这要求受害者与客户端的会话
在攻击者与客户端启动会话之后才开始。)如果攻击者随后能够从受害者捕获
授权码,则即使使用了 PKCE 或 nonce,攻击者也能够在 步骤
3 中注入被盗授权码。¶
这种攻击复杂,并要求攻击者与受害者会话之间有紧密交互。 尽管如此,仍需要采取措施防止 攻击者读取授权响应的内容, 如第 4.1、4.2、 4.3、4.4 和 4.11 节所述。¶
在访问令牌注入攻击中,攻击者试图将 被盗访问令牌注入到一个合法客户端中(该客户端不受 攻击者控制)。这通常发生在攻击者想要 利用泄露的访问令牌,在某个客户端中冒充用户时。¶
为实施该攻击,攻击者使用隐式授权与
客户端启动 OAuth 流程,并修改授权
响应,将授权
服务器颁发的访问令牌替换掉,或直接伪造一个包含
泄露访问令牌的授权服务器响应。由于响应包含
客户端为该特定事务生成的 state 值,客户端
不会将响应视为 CSRF 攻击,并会使用
攻击者注入的访问令牌。¶
在纯 OAuth 流程中无法检测这种注入攻击, 因为令牌在颁发时没有绑定到 事务或特定用户代理。¶
在 OpenID Connect 中,可以缓解该攻击,因为授权
响应还包含一个带有 at_hash claim 的 ID Token。因此,攻击者
需要同时替换响应中的访问令牌和 ID Token。
攻击者无法伪造 ID Token,因为它经过签名或加密
并具有身份认证。攻击者也无法注入与
被盗访问令牌匹配的泄露 ID Token,因为泄露 ID Token 中的 nonce claim
(以极高概率)会包含与授权响应中预期值不同的值。¶
注意,仍然需要进一步保护,例如发送者约束访问令牌, 以防止攻击者直接在资源 端点使用访问令牌。¶
攻击者可能试图在受害者设备上向 合法客户端的重定向 URI 注入请求,例如,使 客户端访问攻击者控制下的资源。这是 一种称为跨站请求伪造 (CSRF) 的攻击变体。¶
长期确立的对策是,客户端在 state 参数中传递一个随机
值,也称为 CSRF Token,用于按所述方式将请求和
重定向 URI 关联到用户代理会话。
此对策在 [RFC6819] 第
5.3.5 节 中详细描述。PKCE 或 OpenID Connect nonce 值提供相同的保护。¶
在使用 PKCE 而不是 state 或 nonce
进行 CSRF 保护时,需要
注意:¶
客户端必须确保
授权服务器支持 PKCE,然后才能使用 PKCE 进行
CSRF 保护。如果授权服务器不支持 PKCE,
则必须使用
state 或 nonce 进行 CSRF 保护。¶
如果 state 用于携带应用
状态,且其内容完整性值得关注,则客户端必须保护
state 免遭
篡改和调换。可通过将
state 内容绑定到浏览器会话和/或对
state 值进行签名/加密来实现这一点。过期的 Internet-Draft
[JWT-ENCODED-STATE] 中讨论了一个示例。¶
因此,授权服务器必须 提供一种方式来检测其对 PKCE 的支持。[RFC8414] 所规定的授权服务器元数据 建议使用,但授权服务器可以改为提供一种 特定于部署的方式,以确保或确定 PKCE 支持。¶
即使存在能够读取授权响应的攻击者(见
第 3 节 中的 攻击者 (A3)),PKCE 也能对 CSRF 攻击提供稳健保护。
当使用
state,或在授权响应中返回 ID Token(例如
response_type=code+id_token)时,攻击者要么获知
state 值并
能够将其重放到伪造的授权响应中,要么可以从 ID Token 中提取
nonce,
并在向授权服务器发起的新请求中使用它,以铸造具有相同 nonce 的 ID Token。
随后可将新的 ID Token 用于
CSRF 攻击。¶
支持 PKCE 但未要求所有流程 强制使用 PKCE 的授权服务器,可能容易受到 PKCE 降级攻击。¶
此攻击的第一个前提是授权请求中存在一个
攻击者可控制的
标志,用于为特定流程启用或禁用 PKCE。
code_challenge 参数的存在或缺失很适合此目的,即授权服务器在该
参数存在于授权请求中时启用并强制 PKCE,但在
参数缺失时不强制 PKCE。¶
此攻击的第二个前提是客户端完全未使用
state
(例如,因为客户端依赖 PKCE 来防止 CSRF),或客户端
未正确检查 state。¶
粗略地说,此攻击是 CSRF 攻击的一个变体。攻击者 达成的目标与 第 4.7 节 中描述的攻击相同:攻击者将绑定到攻击者 资源的授权码(以及由此得到的访问令牌) 注入到受害者与客户端之间的会话中。¶
code_challenge=hash(abc) 作为 PKCE code challenge(其中哈希
函数和参数编码按 [RFC7636] 定义)。客户端现在
等待从用户浏览器接收授权响应。¶
code_challenge=hash(xyz)。
攻击者拦截该请求,并从请求中移除整个
code_challenge 参数。由于此步骤在
攻击者设备上执行,攻击者可以完全访问请求内容,
例如使用浏览器调试工具。¶
code_verifier=abc 作为 PKCE code verifier。¶
code_verifier 参数的存在或内容。它会向用户控制下的客户端
颁发访问令牌(该令牌属于
攻击者的资源)。¶
正确使用 state 会阻止此攻击。
然而,实践表明
许多 OAuth 客户端没有正确使用或检查 state。¶
因此,授权服务器必须 缓解此攻击。¶
注意,从授权服务器的角度看,在上述攻击中,
令牌端点收到了
code_verifier 参数,尽管在颁发该授权码的
OAuth 流程的授权请求中并不存在
code_challenge 参数。¶
code_challenge,
则令牌请求中必须存在有效的 code_verifier。¶
除此之外,为防止 PKCE 降级攻击,授权
服务器必须确保:
如果授权请求中没有 code_challenge,则拒绝包含
code_verifier 的令牌端点请求。¶
强制使用 PKCE(一般性或针对特定客户端)的授权服务器 隐含实现了这一安全措施。¶
在某些情况下,访问令牌可能从资源服务器泄露。¶
攻击者可以建立自己的资源服务器,并诱骗客户端 向其发送对其他资源服务器有效的访问令牌 (见 第 3 节 中的攻击者 (A1) 和 (A5))。如果客户端向 这个假冒资源服务器发送有效访问令牌,攻击者随后可能使用该 令牌代表资源所有者访问其他服务。¶
此攻击假定客户端在开发时并未绑定到某一个特定资源 服务器(及其 URL),而是客户端实例在运行时 获得资源服务器 URL。 这种后期 绑定常见于客户端使用实现标准化 API 的服务的情形 (例如电子邮件、日历、电子健康或开放银行),并且客户端由用户或 管理员配置。¶
攻击者可以攻陷资源服务器,以访问相应部署的 资源。此类攻陷可能从 对系统的部分访问(例如其日志文件)到对相应服务器的完全 控制不等;在后一种情况下,所有控制都可能被 绕过,所有资源都可以被 访问。攻击者还能够获得受陷系统中持有的其他访问 令牌,这些令牌可能对访问其他资源服务器有效。¶
通过加固和监控服务器 系统来防止服务器被攻陷,被视为标准运维流程, 因此超出 本文档范围。 第 4.9 节 聚焦于 OAuth 相关攻陷的影响以及捕获访问令牌的重放。¶
实现者应考虑采取以下措施,以 应对恶意行为者对访问令牌的重放:¶
访问令牌可以通过多种方式被攻击者窃取,例如 通过第 4.1、 4.2、4.3、4.4 和 4.9 节所述的攻击。其中一些攻击可通过 相应章节中描述的 特定安全措施来缓解。 然而,在某些情况下,这些措施不足,或未被正确 实现。因此,授权服务器应该确保 访问令牌按下文所述进行发送者约束和受众限制。 架构和性能原因可能会 阻碍某些部署使用这些措施。¶
顾名思义,发送者约束访问令牌将 访问令牌的适用范围限定到某个发送者。该发送者有 义务证明其知道某个秘密,作为该令牌在资源服务器处被接受的前提。¶
典型流程如下:¶
OAuth 工作组已经定义并在实践中使用了两种 使用持有证明的发送者约束访问令牌方法:¶
注意,当攻击者获得令牌和密钥材料时, 发送者约束令牌的安全性会被削弱。这一点尤其适用于 被破坏的客户端软件和跨站 脚本攻击(当客户端运行在浏览器中时)。如果 密钥材料受到硬件或软件安全模块保护,或 只能间接访问(例如在 TLS 栈中),发送者约束 令牌至少能在客户端 离线时(即安全模块或接口对攻击者不可用时)防止令牌使用。 这既适用于访问令牌,也适用于刷新 令牌(见 第 4.14 节)。¶
受众限制实质上将访问令牌限制到 特定资源服务器。授权服务器将 访问令牌与特定资源服务器关联,然后资源 服务器应验证预期受众。如果访问令牌未通过 预期受众验证,则资源服务器拒绝 服务相应请求。¶
一般而言,受众限制会限制令牌 泄露的影响。 在假冒资源服务器的情况下,它还可能(如下所述) 防止钓鱼得到的访问令牌在 合法资源服务器处被滥用。¶
受众可以使用逻辑名称或 物理地址(如 URL)表示。为防止钓鱼,必须使用客户端将请求发送到的实际 URL。 在钓鱼情况下,该 URL 会指向假冒资源 服务器。如果攻击者试图在 合法资源服务器(其 URL 不同)使用访问令牌,资源 服务器将检测到不匹配(错误受众)并拒绝服务 该请求。¶
在授权服务器知道所有 资源服务器 URL 的部署中,授权服务器可以直接拒绝为未知资源服务器 URL 颁发 访问令牌。¶
为使其生效,客户端需要告知授权服务器其预期的 资源服务器。[RFC8707] 中的机制可用于此目的,或可将该 信息编码到 scope 值中([RFC6749] 第 3.3 节)。¶
除了 URL,也可以使用 资源服务器 X.509 证书的指纹作为受众值。该 变体还允许检测使用从不同 CA 获得的有效 TLS 证书来伪造合法 资源服务器 URL 的尝试。它也可被视为一种隐私收益,因为可以向授权服务器隐藏 资源服务器 URL。¶
受众限制似乎更容易使用,因为它不 要求客户端侧进行任何密码学操作。不过,由于每个访问令牌都 绑定到特定资源服务器,客户端在访问多个资源 服务器时,也需要获得单个面向资源服务器的访问令牌。 ([RFC8707] 中指定的 Resource Indicators 可帮助实现这一点。) [TOKEN-BINDING] 也具有相同属性,因为 不同 token-binding ID 必须与访问令牌关联。另一方面,使用 OAuth 2.0 的相互 TLS [RFC8705] 允许客户端在多个资源服务器上使用 同一访问令牌。¶
应注意,受众限制——或者更一般地说,客户端向授权服务器 表明其希望在何处使用 访问令牌——具有超出令牌泄露防护范围的额外好处。 它们允许授权服务器创建一个不同的访问令牌,其格式和内容 专门为相应服务器铸造。 在使用结构化访问令牌的部署中,这具有巨大的功能和隐私 优势。¶
授权服务器可以向客户端提供有关 可安全使用其访问 令牌的位置的额外信息。下面讨论此方法及其不被推荐的原因。¶
最简单的形式中,这要求授权服务器
发布其已知资源服务器列表,以下示例使用
非标准授权服务器元数据参数 resource_servers 进行说明:¶
授权服务器也可以在
令牌响应中返回访问令牌适用的 URL,示例如下,使用
非标准返回参数 access_token_resource_server:¶
这种缓解策略将依赖客户端执行
安全策略,并且只将访问令牌发送到合法
目的地。OAuth 相关安全研究结果(例如见
[research.ubc] 和
[research.cmu])表明,相当大一部分客户端实现没有或未能正确
实现安全控制,例如 state 检查。因此,
依赖客户端来防止访问令牌钓鱼也很可能失败。
此外,考虑到客户端与授权服务器和资源服务器的比例,
将尽可能多的
安全相关逻辑移到这些服务器上,被认为是更可行的方法。
显然,客户端
必须为整体安全作出贡献。然而,存在替代
对策,如第 4.10.1 和 4.10.2 节所述,它们在相关参与方之间提供了
更好的平衡。¶
当授权服务器或客户端具有开放重定向器时,可能发生以下 攻击。此类端点有时会被实现出来, 例如用于在用户随后被重定向到外部 网站之前显示一条消息,或将用户重定向回他们在被中断前(例如被登录提示中断前) 打算访问的 URL。¶
客户端不得暴露开放 重定向器。攻击者可能利用开放 重定向器生成指向客户端的 URL,并用它们 外泄授权码和访问令牌,如 第 4.1.2 节 所述。另一种 滥用情形是生成看似指向客户端的 URL。 这可能诱骗用户信任该 URL 并在浏览器中访问它。这可被用于钓鱼。¶
为防止开放重定向,客户端只有在 目标 URL 被允许,或请求的来源和完整性能够 进行身份认证时,才应重定向。OWASP [owasp.redir] 描述了针对开放重定向的对策。¶
在授权端点,一个典型协议流程是授权服务器 提示用户在表单中输入凭据,然后该表单 (使用 HTTP POST 方法)提交回授权 服务器。授权服务器检查凭据,并在成功后将 用户代理重定向到客户端的重定向端点。¶
在 [RFC6749] 中,HTTP 状态码 302 (Found) 用于此目的,但 “允许通过 user-agent 可用的任何其他方法完成此 重定向”。当使用状态码 307 进行重定向时,用户代理会通过 HTTP POST 将用户凭据发送给客户端。¶
这会将敏感凭据披露给客户端。如果客户端 是恶意的,它可以使用这些凭据在授权服务器处冒充用户。¶
该行为可能出乎开发者意料,但它在 [RFC9110] 第 15.4.8 节 中有定义。此状态码 (307) 不要求用户 代理将 POST 请求改写为 GET 请求,从而丢弃 POST 请求正文中的表单数据。¶
在 HTTP 标准 [RFC9110] 中,只有状态码 303 明确强制将 HTTP POST 请求改写为 HTTP GET 请求。 对于所有其他状态码,包括流行的 302,用户 代理可以选择不将 POST 改写为 GET 请求,从而 导致用户凭据泄露给客户端。(然而在实践中,大多数 用户代理只会对 307 重定向表现出这种行为。)¶
因此,重定向可能包含 用户凭据的请求的授权服务器不得使用 HTTP 307 状态码进行重定向。如果 对此类请求使用 HTTP 重定向(而不是例如 JavaScript),则授权服务器应该使用 HTTP 状态码 303 (See Other)。¶
HTTP 应用的常见部署架构是将 应用服务器隐藏在一个终止 TLS 连接并将传入请求分派到相应 应用服务器节点的反向代理后面。¶
本节强调这种部署 架构中与 OAuth 相关的一些攻击角度,并给出 安全控制建议。¶
在某些情况下,反向代理需要将安全相关 数据传递给上游应用服务器以进一步处理。 示例包括请求发起者的 IP 地址、token-binding ID,以及已认证的 TLS 客户端证书。此类数据通常通过添加到上游请求中的 HTTP 标头传递。 虽然这些标头通常是自定义的、特定于应用的标头,但 [RFC9440] 中定义了用于客户端证书和客户端证书链的标准化标头 字段。¶
如果反向代理透传任何来自
外部的标头,攻击者可能会尝试通过代理直接向应用服务器发送伪造的标头
值,以此绕过
安全控制。例如,反向代理接受 X-Forwarded-For 标头并仅
添加入站请求来源(使其成为列表)是一种标准
做法。根据应用服务器中执行的逻辑,攻击者可能
仅向该标头添加一个被允许的 IP 地址,从而使保护失效。¶
因此,反向代理必须净化任何 入站请求,以确保与 应用服务器安全相关的所有标头值的真实性和完整性。¶
如果攻击者能够访问代理与应用服务器之间的内部网络, 攻击者也可能尝试绕过现有 安全控制。因此,确保通信实体的真实性至关重要。此外, 反向代理与应用服务器之间的通信链路 必须受到保护,以防消息被窃听、注入和重放。¶
刷新令牌是一种便捷且用户友好的方式,用于获得新的访问 令牌。它们也有助于 OAuth 的安全性,因为它们允许授权服务器颁发 生命周期短且 scope 缩小的访问令牌,从而降低 访问令牌泄露的潜在影响。¶
刷新令牌对攻击者具有吸引力,因为它们 代表授予 某个客户端的完整访问范围,并且没有进一步约束到特定 资源。 如果攻击者能够外泄并成功重放 刷新令牌,则攻击者将能够铸造访问令牌,并代表资源所有者使用 它们访问资源服务器。¶
[RFC6749] 还通过定义相应的错误码和响应行为,为进一步的 (特定于实现的)安全措施奠定了基础,例如刷新令牌过期和 撤销,以及刷新令牌轮换。¶
授权服务器必须基于风险评估 确定是否向某个客户端颁发刷新令牌。如果 授权服务器决定不颁发刷新令牌,则客户端 可以通过利用其他授权类型(例如 授权码授权类型)来获得新的访问令牌。在这种情况下,授权 服务器可以利用 cookie 和持久授权来优化用户 体验。¶
如果颁发刷新令牌,则这些刷新令牌必须绑定到 资源所有者同意的 scope 和资源服务器。 这样做是为了防止合法客户端进行权限提升,并降低 刷新令牌泄露的影响。¶
对于机密客户端,[RFC6749] 已经要求刷新 令牌只能由其被颁发给的客户端使用。¶
对于公共客户端,授权服务器必须利用以下方法之一 检测恶意行为者对刷新令牌的重放:¶
刷新令牌轮换:授权服务器在每个访问令牌刷新响应中颁发新的 刷新令牌。 先前的刷新令牌被作废,但授权服务器保留有关 关系的信息。如果刷新 令牌被泄露,并随后被攻击者和合法客户端同时使用, 其中一方会提交一个已作废的 刷新令牌,这会通知授权服务器发生了 泄露。授权服务器无法确定是哪一方 提交了无效刷新令牌,但会撤销 活动刷新令牌。这会阻止攻击,代价是迫使 合法客户端获得新的授权 grant。¶
实现说明:刷新令牌 所属的 grant 可以被编码到刷新令牌本身。这可以使 授权服务器高效确定刷新 令牌所属的 grant,并由此确定所有需要被撤销的刷新令牌。 在这种情况下,授权服务器必须确保 刷新令牌值的完整性,例如, 使用签名。¶
在发生安全事件时,授权服务器可以自动撤销刷新 令牌,例如:¶
如果客户端已有一段时间不活跃, 即刷新令牌已有一段时间未用于获得新的访问 令牌,则刷新令牌应该过期。过期时间由 授权服务器自行决定。它可以是全局值,也可以基于 客户端策略或与刷新令牌关联的 grant (及其敏感性)来确定。¶
资源服务器可以基于访问令牌所代表的
资源所有者身份,或基于客户端凭据授权中客户端的身份来作出访问控制决策。
例如,[RFC9068](JSON Web
Token (JWT) Profile for OAuth 2.0 Access Tokens)描述了一种用于
访问令牌的数据结构,其中包含按如下方式定义的 sub claim:¶
在通过涉及资源所有者的 grant 获得访问令牌的情况下,例如授权码授权,"sub" 的值 应该对应于资源 所有者的主体标识符。 在通过不涉及 资源所有者的 grant 获得访问令牌的情况下,例如客户端凭据授权, "sub" 的值应该对应于授权服务器用来指示客户端应用的标识符。¶
如果两种选项都可能出现,资源服务器可能会将客户端
身份误认为资源所有者的身份。例如,如果客户端能够在授权服务器注册时选择
自己的 client_id,恶意客户端可能将其设置为标识资源所有者的值
(例如,如果使用 OpenID Connect,则为 sub 值)。如果资源服务器无法正确
区分通过涉及资源
所有者获得的访问令牌和不涉及资源所有者获得的访问令牌,
客户端可能会意外地访问属于资源
所有者的资源。¶
如 [RFC6819] 第 4.4.1.9 节 所述, 授权请求容易受到点击劫持攻击,也称为用户界面重绘攻击。 在此类攻击中,攻击者将授权端点用户界面嵌入到 一个无害上下文中。用户以为自己正在与该上下文交互, 例如点击按钮,却无意中与授权 端点用户界面交互。反过来也可以做到:用户 以为正在与授权端点交互,却可能无意中将 密码输入到攻击者提供的输入字段中,该字段覆盖在原始用户 界面之上。点击劫持攻击可以设计成让用户几乎 无法察觉,例如,使用几乎不可见的 iframe 覆盖在 其他元素之上。¶
攻击者可以利用此向量获取用户的身份认证 凭据, 更改授予客户端的访问 scope,并可能访问 用户资源。¶
授权服务器必须防止点击劫持 攻击。[RFC6819] 中描述了多种 对策,包括使用 X-Frame-Options HTTP 响应标头字段和 frame-busting JavaScript。除此之外,授权服务器应该还 使用 Content Security Policy (CSP) level 2 [W3C.CSP-2] 或更高版本。¶
为发挥效果,CSP 必须用于授权端点,并且 如适用,也用于其他用于对用户进行身份认证和 授权客户端的端点(例如设备授权端点、登录 页面、错误页面等)。这会在支持 CSP 的用户代理中防止未授权 origin 的框架嵌入。客户端可以允许被 与其重定向端点所用 origin 不同的其他 origin 嵌入。因此,授权服务器应该允许 管理员为特定客户端配置允许的 origin, 和/或允许客户端动态注册这些 origin。¶
使用 CSP 允许授权服务器在
单个响应标头字段中指定多个 origin,并使用灵活
模式约束它们(详见 [W3C.CSP-2])。
CSP level 2 通过使用
限制 frame 来源的策略(使用 frame-ancestors)
与限制 HTML 页面上允许执行脚本来源的策略
(使用 script-src)相结合,为防止点击劫持提供了
稳健机制。下面的列表展示了这种策略的一个非规范性
示例:¶
由于某些用户代理不支持 [W3C.CSP-2],此技术 应该与其他技术结合使用,包括 [RFC6819] 中描述的技术,除非授权服务器明确不支持此类旧版用户代理。 即使在这种情况下,仍应该采用额外 对策。¶
如果授权响应通过浏览器内通信 技术(例如 postMessage [WHATWG.postmessage_api])发送,而不是通过 HTTP 重定向发送,则消息 可能会无意中被发送到恶意 origin,或从恶意 origin 注入。¶
以下使用浏览器内 通信的攻击非规范性伪代码示例在 [research.rub] 中有描述。¶
在通过 postMessage 发送授权响应或令牌响应时,授权服务器将响应发送到通配符 origin "*",而不是客户端 origin。当接收响应的窗口由攻击者控制时, 攻击者可以读取 响应。¶
window.opener.postMessage(
{
code: "ABC",
state: "123"
},
"*" // any website in the opener window can receive the message
)
¶
在通过 postMessage 发送授权响应或令牌响应时,授权服务器可能不会将 接收方 origin 与重定向 URI 进行检查,而是例如可能将 响应发送到攻击者提供的 origin。这类似于 第 4.1 节 中描述的攻击。¶
window.opener.postMessage(
{
code: "ABC",
state: "123"
},
"https://attacker.example" // attacker-provided value
)
¶
期望通过 postMessage 接收授权响应或令牌响应的客户端,可能不会验证消息的发送方 origin。这 可能允许攻击者将授权响应或令牌响应 注入到客户端中。¶
在恶意注入授权 响应的情况下,该攻击是 第 4.7 节 所述 CSRF 攻击的变体。 第 4.7 节 中描述的 对策同样适用于此攻击。¶
在恶意注入令牌响应的情况下, 如 第 4.10.1 节 所述的发送者约束 访问令牌,在某些情况下可防止该攻击,但通常仍需要 第 4.17.2 节 中描述的其他 对策。¶
在将客户端接收方 origin 与预注册的 origin 进行比较时, 授权服务器必须使用 第 4.1.3 节 中描述的精确字符串匹配。 授权服务器必须将 postMessage 发送到 可信客户端接收方 origin,如以下非规范性示例所示:¶
window.opener.postMessage(
{
code: "ABC",
state: "123"
},
"https://client.example" // use explicit client origin
)
¶
postMessage 中不得使用 "*" 这样的通配符 origin,因为攻击者可以利用它们 将受害者的浏览器内消息泄露到恶意 origin。 这两项措施都有助于防止授权码和 访问令牌泄露(见 第 4.1 节)。¶
客户端必须防止客户端 接收端点上的浏览器内消息 注入。客户端必须使用精确字符串匹配来比较 浏览器内消息的发起方 origin 与授权 服务器 origin,如以下非规范性示例所示:¶
window.addEventListener("message", (e) => {
// validate exact authorization server origin
if (e.origin === "https://honest.as.example") {
// process e.data.code and e.data.state
}
})
¶
由于浏览器内通信流程只是使用了不同的 通信 技术(即 postMessage 而不是 HTTP redirect),因此 第 2.1 节 中列出的保护授权响应的所有措施都 必须同等适用。¶
我们感谢 Brock Allen、 Annabelle Richard Backman、 Dominick Baier、 Vittorio Bertocci、 Brian Campbell、 Bruno Crispo、 William Dennis、 George Fletcher、 Matteo Golinelli、 Dick Hardt、 Joseph Heenan、 Pedram Hosseyni、 Phil Hunt、 Tommaso Innocenti、 Louis Jannett、 Jared Jennings、 Michael B. Jones、 Engin Kirda、 Konstantin Lapine、 Neil Madden、 Christian Mainka、 Jim Manico、 Nov Matake、 Doug McDorman、 Karsten Meyer zu Selhausen、 Ali Mirheidari、 Vladislav Mladenov、 Kaan Onarioglu、 Aaron Parecki、 Michael Peck、 Johan Peeters、 Nat Sakimura、 Guido Schmitz、 Jörg Schwenk、 Rifaat Shekh-Yusef、 Travis Spencer、 Petteri Stenius、 Tomek Stojecki、 David Waite、 Tim Würtele 和 Hans Zandbelt 提供的宝贵反馈。¶