RFC 9700 OAuth 2.0 安全性 BCP 2025年1月
Lodderstedt 等 最佳当前实践 [页]
系列:
互联网工程任务组(IETF)
RFC:
9700
BCP:
240
更新:
6749, 6750, 6819
类别:
最佳当前实践
发布:
ISSN:
2070-1721
作者:
T. Lodderstedt
SPRIND
J. Bradley
Yubico
A. Labunets
独立研究员
D. Fett
Authlete

RFC 9700

OAuth 2.0 安全性最佳当前实践

摘要

本文档描述了 OAuth 2.0 当前最佳安全实践。它更新并扩展了 RFC 6749、6750 和 6819 中给出的威胁模型和安全建议,以纳入自 OAuth 2.0 发布以来积累的实践 经验,并涵盖由于 OAuth 2.0 更广泛 应用而产生的新威胁。此外,它还弃用了某些被认为 安全性较低甚至不安全的操作模式。

本备忘录的状态

本备忘录记录了一项互联网最佳当前实践。

本文档是互联网工程任务组 (IETF)的成果。它代表了 IETF 社区的共识。本文档已经 过公开评审,并已获互联网工程指导组 (IESG)批准发布。有关 BCP 的更多信息 可参见 RFC 7841 第 2 节。

有关本文档的当前状态、任何 勘误以及如何提供反馈的信息,可从 https://www.rfc-editor.org/info/rfc9700 获取。

▲

目录

1. 引言

自 [RFC6749] 和 [RFC6750] 发布以来,OAuth 2.0(在本文档中简称为 “OAuth”)已在市场上得到广泛采用, 并成为 API 保护的标准以及使用 OpenID Connect [OpenID.Core] 进行联合登录的基础。 虽然 OAuth 用于 各种场景和不同类型的部署,但可以观察到以下 挑战:

本文档提供了更新后的安全建议,以应对这些 挑战。它引入了超出现有 规范(例如 OAuth 2.0 [RFC6749] 和 OpenID Connect [OpenID.Core]) 所定义内容的新要求, 并弃用了一些被认为安全性较低甚至 不安全的操作模式。然而,本文档并不取代 [RFC6749]、[RFC6750] 和 [RFC6819] 中给出的安全建议,而是对这些文档进行补充。

自然,并非所有现有的生态系统和实现都 与这些新要求兼容,并且遵循本文档中描述的 最佳实践可能会破坏互操作性。尽管如此,仍建议 实现者在可行时尽快升级其实现和生态系统。

正在作为 [OAUTH-V2.1] 开发的 OAuth 2.1 将纳入 本文档中的安全建议。

1.1. 结构

本文档的其余部分组织如下:第 2 节 总结了每个 OAuth 实现者最重要的最佳实践。 第 3 节介绍更新后的 OAuth 攻击者 模型。第 4 节对 现实中(截至撰写本文时)发现的威胁和实现问题进行了 详细分析,并讨论了潜在的对策。

1.2. 约定和 术语

本文档中的关键词“必须”“不得” “要求”“须”“不得” “应该”“不应该” “建议”“不建议” “可以”和“可选”,仅当它们像 此处所示 以全大写形式出现时,才应按照 BCP 14 [RFC2119] [RFC8174] 中所述的含义 进行解释。

本规范使用 OAuth 2.0 [RFC6749] 定义的术语“访问令牌”“授权 端点”“授权许可”“授权服务器”“客户端” “客户端标识符”(客户端 ID)“受保护资源”“刷新 令牌”“资源所有者”“资源服务器”和“令牌端点”。

“开放重定向器”是 Web 服务器上的一个端点,它将用户的 浏览器转发到从查询参数中获取的任意 URI。

2. 最佳实践

本节描述了截至撰写本文时被认为属于 最佳实践的核心安全机制和措施。关于这些 安全机制和措施的详细信息(包括详细的攻击 描述),以及较少使用的选项的要求,可参见 第 4 节。

2.1. 保护 基于重定向的流程

将客户端重定向 URI 与预注册 URI 进行比较时, 授权 服务器必须使用精确字符串匹配,但原生应用的 localhost 重定向 URI 中的端口号除外(参见 第 4.1.3 节)。此 措施有助于防止授权码和 访问令牌泄露(参见 第 4.1 节)。它还可以帮助检测 混淆攻击(参见 第 4.4 节)。

客户端和授权服务器不得暴露 将用户浏览器转发到 从查询参数获取的任意 URI 的 URL(开放重定向器),如 第 4.11 节所述。开放 重定向器可能导致授权码和访问令牌 被窃取。

客户端必须防止跨站请求伪造 (CSRF)。在此 上下文中,CSRF 指向重定向端点发出的请求并非 源自授权服务器,而是源自恶意第三方 (详情参见 第 4.4.1.8 节,见 [RFC6819])。 已确认 授权服务器支持代码交换证明密钥(PKCE)[RFC7636] 的客户端可以 依赖 PKCE 提供的 CSRF 保护。在 OpenID Connect 流程中, nonce 参数提供 CSRF 保护。否则,必须使用 在 state 参数中携带并与用户代理安全 绑定的一次性 CSRF 令牌来提供 CSRF 保护(参见 第 4.7.1 节)。

当 OAuth 客户端可以与多个授权服务器交互时, 要求针对混淆攻击采取防御措施(参见 第 4.4 节)。为此,客户端 应该

  • 按照 [RFC9207] 使用 iss 参数作为对策;或者
  • 使用基于授权响应中 iss 值的替代对策 (例如 [OpenID.Core] 中 ID Token 的 iss claim 或 [OpenID.JARM] 响应中的 iss 值),并按照 [RFC9207] 中所述处理 该值。

在没有这些选项的情况下,客户端可以 改为使用不同的重定向 URI 来标识授权端点和令牌端点,如 第 4.4.2 节所述。

重定向可能包含用户凭据的请求的授权服务器 必须避免意外转发这些用户凭据(详情参见 第 4.12 节)。

2.1.1. 授权 码许可

客户端必须防止授权码 注入攻击(参见 第 4.5 节),并使用以下选项之一防止授权码被滥用:

  • 为此,公共客户端必须使用 PKCE [RFC7636],理由见 第 4.5.3.1 节。
  • 对于机密客户端,建议使用 PKCE [RFC7636],因为它 可以针对授权码的滥用和注入提供强有力的保护, 如 第 4.5.3.1 节所述。此外,作为一种附带效果, 即使存在 第 4.7.1 节 所述的强攻击者,它也能防止 CSRF。
  • 在采取 第 4.5.3.2 节所述额外预防措施的情况下, 机密 OpenID Connect [OpenID.Core] 客户端可以改为使用 nonce 参数以及 ID Token 中 对应的 Claim。

无论如何,PKCE challenge 或 OpenID Connect nonce 必须 针对具体事务,并与启动该事务的客户端和用户代理 安全绑定。 鼓励授权服务器作出合理努力,以检测并 防止对 PKCE challenge 或 OpenID Connect nonce 使用固定值。

注意:虽然 PKCE 被设计为保护原生 应用的机制,但此建议适用于所有类型的 OAuth 客户端,包括 Web 应用。

使用 PKCE 时,客户端应该使用 不会在授权请求中暴露 PKCE verifier 的 PKCE code challenge 方法。 否则,能够读取授权请求的攻击者(参见 第 3 节中的攻击者 (A4))便可以破坏 PKCE 提供的安全性。 目前,S256 是唯一这样的方法。

授权服务器必须支持 PKCE [RFC7636]。

如果客户端在 授权请求中发送有效的 PKCE code_challenge 参数,则授权服务器必须在令牌端点 强制正确使用 code_verifier。

授权服务器必须通过确保 仅当授权请求中存在 code_challenge 参数时,才接受包含 code_verifier 参数的令牌请求, 从而缓解 PKCE 降级攻击;详情参见 第 4.8.2 节。

授权服务器必须提供一种方式 来检测其是否支持 PKCE。建议授权服务器在其授权服务器元数据 [RFC8414] 中发布 code_challenge_methods_supported 元素, 其中包含受支持的 PKCE challenge 方法(客户端可以使用这些方法 检测是否支持 PKCE)。授权服务器可以 改为提供一种 特定于部署的方式,以确保或确定授权服务器是否支持 PKCE。

2.1.2. 隐式许可

隐式许可(响应类型 token)以及其他 会导致授权服务器在 授权响应中签发访问令牌的响应类型,容易遭受 第 4.1、4.2、4.3 和 4.6 节所述的访问令牌泄露和 访问令牌重放攻击。

此外,当访问令牌在 授权响应中签发时,不存在将访问令牌绑定到特定客户端的标准化 发送者约束方法(如 第 2.2 节所建议)。 这意味着攻击者可以在资源端点使用泄露或被盗的 访问令牌。

为避免这些问题,客户端不应该使用隐式 许可(响应类型 token)或其他在授权响应中签发 访问令牌的响应类型,除非能够防止授权响应中的访问令牌 注入,并缓解上述令牌泄露 途径。

客户端应该改为使用响应 类型 code(即授权 码许可类型),如 第 2.1.1 节所规定,或者使用任何其他 会导致授权服务器在令牌 响应中签发访问令牌的响应类型,例如 code id_token 响应类型。这使 授权服务器能够检测攻击者的重放尝试,并且 通常会减少攻击面,因为访问令牌不会 暴露在 URL 中。它还使授权服务器能够 对所签发的令牌实施发送者约束(参见 第 2.2 节)。

2.2. 防止令牌 重放

2.2.1. 访问令牌

发送者约束访问令牌将一个 访问令牌的适用范围限制到特定发送者。该发送者必须证明其知道 某个特定秘密,作为接收方(例如资源服务器) 接受该令牌的先决条件。

授权服务器和资源服务器应该 使用对访问令牌实施发送者约束的机制, 例如 OAuth 2.0 的双向 TLS [RFC8705] 或 OAuth 2.0 持有证明演示(DPoP)[RFC9449](参见 第 4.10.1 节),以防止 被盗和泄露的访问令牌遭到滥用。

2.2.2. 刷新令牌

公共客户端的刷新令牌必须 具有发送者约束,或者按照 第 4.14 节所述采用刷新 令牌轮换。[RFC6749] 已经 要求机密客户端的刷新令牌只能由 获得该令牌的客户端使用。

2.3. 访问令牌权限 限制

与访问令牌关联的权限应该限制为 特定应用或使用场景所需的最低限度。这样可以 防止客户端超出资源所有者授权的权限。 它还可以防止用户超出相应安全策略 授权给他们的权限。权限限制 还有助于降低访问令牌泄露造成的影响。

具体而言,访问令牌应该 将受众限制为特定资源 服务器,或者如果无法做到这一点,则限制为少量资源服务器。为实现这一点, 授权服务器将 访问令牌与特定资源服务器关联,并且每个资源 服务器都有义务对每个请求验证,该请求中发送的访问 令牌是否旨在供该特定 资源服务器使用。如果不是,资源服务器必须拒绝处理 相应请求。[RFC9068] 中定义的 aud claim可以 用于对访问令牌实施受众限制。客户端和授权服务器可以分别使用 [RFC6749] 和 [RFC8707] 中规定的 scope 或 resource 参数,确定它们希望访问的 资源服务器。

此外,访问令牌应该限制为 资源服务器上的特定资源 和操作。为实现这一点, 授权服务器将访问令牌与 相应资源和操作关联,并且每个资源服务器都有义务 对每个请求验证,该请求中发送的访问令牌 是否旨在用于对该 特定资源执行该特定操作。如果不是,资源服务器必须拒绝处理 相应请求。客户端和授权服务器可以使用 [RFC6749] 中规定的 scope 参数和 [RFC9396] 中规定的 authorization_details, 确定这些 资源和/或操作。

2.4. 资源所有者 密码凭据许可

[RFC6749] 中的资源所有者密码凭据许可不得 使用。此许可类型会以不安全的方式将资源 所有者的凭据暴露给客户端。即使客户端是善意的,使用这种许可也会增加 攻击面(即凭据可能在授权服务器之外的更多位置泄露), 并会使用户养成在授权 服务器之外的位置输入其凭据的习惯。

此外,资源所有者密码凭据许可并非设计用于 双因素认证以及需要 多个用户交互步骤的认证过程。使用加密凭据进行认证 (参见 WebCrypto [W3C.WebCrypto]、 WebAuthn [W3C.WebAuthn])可能 无法使用这种许可类型实现,因为它通常绑定到特定 Web 源。

2.5. 客户端认证

如果在 特定部署中可以建立向客户端签发/注册 凭据并确保这些凭据机密性的流程,则授权服务器应该强制执行客户端 认证。

建议使用非对称加密进行 客户端认证,例如 OAuth 2.0 的双向 TLS [RFC8705] 或依据 [RFC7521] 和 [RFC7523] 的签名 JWT (“私钥 JWT”)。后者在 [OpenID.Core] 中定义为客户端 认证方法 private_key_jwt)。 当客户端认证使用非对称加密时,授权 服务器无需存储敏感的对称密钥,因此这些 方法对密钥泄露具有更强的抵抗能力。

2.6. 其他建议

使用 OAuth 授权服务器元数据 [RFC8414] 有助于提高 OAuth 部署的安全性:

  • 它确保安全功能和其他新的 OAuth 功能可以由符合规范的软件库 自动启用。
  • 它降低配置错误的可能性——例如, 端点 URL 配置错误 (可能属于攻击者)或安全功能配置错误。
  • 它可以帮助促进加密 密钥轮换并确保 加密敏捷性。

因此,建议授权 服务器按照 [RFC8414] 发布 OAuth 授权服务器元数据,并且客户端使用 此授权服务器元数据(如果可用)来配置自身。

在 第 4.15.1 节所述条件下, 授权服务器不应该允许客户端影响其 client_id 或 任何其他可能导致其与真正资源所有者产生混淆的 claim。

建议客户端与 资源服务器之间按照 [BCP195] 使用端到端 TLS。如果 TLS 流量需要在中间设备处终止,请参阅 第 4.13 节了解进一步的安全 建议。

授权响应不得通过未加密的网络 连接传输。为此,授权服务器不得允许 使用 http 方案的重定向 URI,但按照 第 7.3 节,见 [RFC8252] 所述使用环回接口重定向的原生客户端除外。

如果授权响应使用类似 postMessage [WHATWG.postmessage_api] 的浏览器内通信技术 而不是 HTTP 重定向发送,则浏览器内消息的 发起方和接收方必须按照 第 4.17 节所述进行严格 验证。

为了支持基于浏览器的客户端,由此类客户端直接访问的端点, 包括令牌端点、授权服务器元数据端点、jwks_uri 端点和动态客户端注册端点,可以支持使用 跨源资源共享(CORS)[WHATWG.CORS]。 但是,授权端点不得 支持 CORS,因为客户端并不直接访问此 端点;相反,客户端会将用户代理重定向到该端点。

3. 更新后的 OAuth 2.0 攻击者模型

[RFC6819] 给出了一个威胁模型, 描述了 OAuth 部署 必须防范的威胁。在此过程中,[RFC6819] 对 攻击者及其能力作出了一些假设,即隐式地 建立了一个攻击者模型。下面将明确说明这一攻击者模型, 并对其进行更新和扩展,以考虑涉及多个参与方的潜在动态 关系(如 第 1 节所述), 纳入新的攻击者类型,并更清晰地定义攻击者模型。

本文档的目标是确保资源 所有者(使用用户代理)在授权服务器上的授权,以及随后在 资源服务器上对访问令牌的使用,尽实际 可能得到保护,至少能够防御以下攻击者。

(A1)

Web 攻击者可以建立和运行任意数量的 网络端点(除“诚实”端点外),包括浏览器和 服务器。Web 攻击者可以建立资源 所有者会访问的网站,运行自己的用户代理,并参与协议。

特别是,Web 攻击者可以运行已在 授权服务器注册的 OAuth 客户端,也可以运行自己的授权服务器和 资源服务器,而资源所有者和其他资源所有者可以 使用这些服务器(与“诚实”服务器并行)。

还必须假定 Web 攻击者可以诱骗用户在 任何时候让其浏览器导航到攻击者任意选择的 URI。在实践中,这 可以通过许多方式实现,例如将恶意 广告注入广告网络,或者发送 看似合法的电子邮件。

Web 攻击者可以使用自己的用户凭据创建新的 消息,也可以使用此前获知的任何秘密。例如, 如果 Web 攻击者通过配置错误的重定向 URI 得知了某个用户的授权码, 该 Web 攻击者随后可以 尝试用该授权码兑换访问令牌。

但是,他们无法读取或操纵并非 发给他们的消息(例如,发送给不受攻击者控制的 授权服务器 URL 的消息)。

(A2)

网络攻击者还完全控制 协议参与者用于通信的网络。他们可以 窃听、操纵和伪造消息,但这些消息 受到加密方法(例如 TLS)正确保护时除外。 网络攻击者还可以阻止任意消息。

例如,Web 攻击者可以是互联网 服务提供商的客户,而网络攻击者可以是互联网服务 提供商本身、在公共(Wi-Fi)网络中使用 ARP 欺骗的攻击者,或者能够访问互联网 交换点的国家支持攻击者。

上述攻击者 (A1) 和 (A2) 符合 OAuth 正式分析 工作 [arXiv.1601.01229] 所使用的攻击者模型。 这是一个最小攻击者模型。 实现者必须考虑其 OAuth 实现 所处环境中所有可能类型的攻击者。例如,在 [arXiv.1901.11520] 中, 使用了一个非常强的攻击者模型,其中包括完全 控制令牌端点的攻击者。这模拟了生态系统中 端点可能配置错误所产生的影响,而通过按照 第 2.6 节所述使用授权服务器元数据,可以避免这种情况。因此,此类攻击者未列在此处。

然而,以往针对 OAuth 的攻击表明,以下类型的 攻击者尤其值得关注:

(A3)

能够读取但无法修改 授权响应内容的攻击者(即授权响应可能 泄露给攻击者)。

此类攻击的示例包括开放重定向器攻击和 混淆攻击(参见 第 4.4 节),其中 客户端被诱骗 将凭据发送给攻击者控制的授权 服务器。

此外,这还包括利用以下情况的攻击:

  • 对重定向 URI 的检查不足(参见 第 4.1 节);
  • 移动操作系统中存在的问题, 即不同 应用可以在同一个 URI 上注册自身;以及
  • 浏览器(历史记录)、代理 服务器和操作系统所存储/记录的 URL。
(A4)

能够读取但无法修改 授权请求内容的攻击者(即授权请求可能 以与上述相同的方式泄露给攻击者)。

(A5)

能够获得授权服务器签发的访问令牌的攻击者。 例如,资源服务器可能遭到攻击者入侵, 访问令牌可能由于配置错误而被发送给攻击者控制的资源服务器, 或者攻击者可能利用社会工程诱使资源所有者 使用攻击者控制的资源服务器。另见 第 4.9.2 节。

(A3)、(A4) 和 (A5) 通常会与 (A1) 或 (A2) 同时出现。 攻击者可以相互协作以实现共同目标。

请注意,攻击者 (A1) 或 (A2) 本身可以是资源所有者,或者 充当资源所有者。例如,此类攻击者可以使用自己的浏览器,在客户端或资源服务器上 重放通过上述任一种攻击获得的 令牌或授权码。

本文档重点关注由攻击者 (A1) 至 (A5) 所产生的威胁。

4. 攻击与缓解措施

本节详细描述了针对 OAuth 实现的攻击, 以及潜在的对策。已经在 [RFC6819] 中涵盖的攻击和缓解措施不再列于此处,除非提出了新的 建议。

本节还针对某些情况和协议 选项定义了额外要求(超出 第 2 节中定义的要求)。

4.1. 重定向 URI 验证不足

一些授权服务器允许客户端注册重定向 URI 模式,而不是完整的重定向 URI。然后,授权服务器 会在运行时将授权 端点处的重定向 URI 参数值与已注册的模式进行匹配。这种方法 允许客户端将事务状态编码到额外的重定向 URI 参数中,或者为多个 重定向 URI 注册单一模式。

事实证明,与精确的重定向 URI 匹配相比,这种方法实现起来更复杂, 管理起来也更容易出错。现实中已经观察到多起 利用模式匹配 实现或具体配置缺陷的成功攻击 (例如,参见 [research.rub2])。 对重定向 URI 的验证不足实际上会破坏 客户端标识或认证(取决于许可和客户端 类型),并允许攻击者获得授权码或 访问令牌,其方式可以是

  • 直接将用户代理发送到攻击者 控制的 URI,或者
  • 利用客户端上的 开放重定向器,并结合用户 代理处理 URL 片段的方式,将 OAuth 凭据暴露给攻击者。

以下各小节详细展示了这些攻击。

4.1.1. 针对授权码许可的重定向 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 向授权端点发起以下授权请求(换行 仅用于显示):

GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=9ad67f13
     &redirect_uri=https%3A%2F%2Fattacker.example%2F.somesite.example
     HTTP/1.1
Host: server.somesite.example

授权服务器验证重定向 URI,并将 其与客户端 s6BhdRkqt3 已注册的重定向 URL 模式进行比较。 授权请求随后被处理并呈现给用户。

如果用户没有看到重定向 URI,或者没有识别出 攻击,则授权码会被签发并立即发送到攻击者的 域。如果启用了授权自动批准 (根据 [RFC6749],不建议公共客户端这样做),则甚至可以在无需用户 交互的情况下实施攻击。

如果攻击者冒充公共客户端,则攻击者可以 在相应的令牌端点将该授权码兑换为令牌。

对于机密客户端,这种攻击不会那么容易奏效,因为 授权码兑换需要使用合法客户端的 密钥进行认证。但是,攻击者可以通过执行授权码 注入攻击,使用合法的机密 客户端兑换授权码;参见 第 4.5 节。

需要特别指出的是,即使授权 服务器正确处理通配符,也可能存在重定向 URI 验证 漏洞。例如,假设客户端 注册了重定向 URL 模式 https://*.somesite.example/*,并且 授权服务器将其解释为“允许重定向 URI 指向 somesite.example 域中的任何主机”。如果 攻击者成功在 somesite.example 中建立了一个主机或子域,则攻击者可以冒充合法客户端。例如,这 可能由子域接管攻击 [research.udel] 引起,其中 一个过期的 CNAME 记录(例如 external-service.somesite.example) 指向一个已不存在的外部 DNS 名称(例如 customer-abc.service.example),而攻击者可以接管它 (例如,通过在外部服务中注册为 customer-abc)。

4.1.2. 针对隐式许可的重定向 URI 验证攻击

上述攻击同样适用于隐式许可。如果 攻击者能够将授权响应发送到攻击者控制的 URI, 攻击者将直接获得携带 访问令牌的片段。

此外,隐式许可(以及使用 [OAuth.Responses] 中定义的 response_mode=fragment 时的其他许可)还可能受到另一类 攻击。该攻击利用了这样一个事实:如果 Location 标头 不包含片段,则用户代理会把片段重新附加到 重定向的目标 URL(参见 第 17.11 节,见 [RFC9110])。这里描述的攻击 将这种行为与客户端充当开放 重定向器(参见 第 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。

随后,该网站发起一个授权请求,该请求 与针对授权码流程的攻击中的请求非常相似。与 上面不同的是,它通过将 redirect_to=https://attacker.example 编码到 重定向 URI 的参数中来利用开放重定向器,并使用响应类型 token(换行仅用于 显示):

GET /authorize?response_type=token&state=9ad67f13
    &client_id=s6BhdRkqt3
    &redirect_uri=https%3A%2F%2Fclient.somesite.example
     %2Fcb%26redirect_to%253Dhttps%253A%252F
     %252Fattacker.example%252F HTTP/1.1
Host: server.somesite.example

然后,由于重定向 URI 与已注册模式匹配, 授权服务器允许该请求,并在 303 重定向中发送生成的访问 令牌(为提高可读性,省略了一些响应参数):

HTTP/1.1 303 See Other
Location: https://client.somesite.example/cb?
          redirect_to%3Dhttps%3A%2F%2Fattacker.example%2Fcb
          #access_token=2YotnFZFEjr1zCsicMWpAA&...

在 client.somesite.example, 请求到达开放 重定向器。该端点将 读取重定向参数,并向 URL https://attacker.example/ 发出 HTTP 303 Location 标头 重定向。

HTTP/1.1 303 See Other
Location: https://attacker.example/

由于 client.somesite.example 上的重定向器没有在 Location 标头中包含片段,因此用户代理会将 原始片段 #access_token=2YotnFZFEjr1zCsicMWpAA&... 重新附加到 URL,并导航到以下 URL:

https://attacker.example/#access_token=2YotnFZFEjr1z...

攻击者位于 attacker.example 的页面随后可以 访问该 片段并获得访问令牌。

4.1.3. 对策

正确实现和管理模式匹配的复杂性显然 会引发安全问题。因此,本文档建议通过使用精确的重定向 URI 匹配来简化所需的 逻辑和配置。这意味着 授权服务器必须确保两个 URI 相等; 详情参见 第 6.2.1 节,见 [RFC3986] 中的“简单字符串比较”。 唯一的例外是 使用 localhost URI 的原生应用:在这种情况下,授权服务器 必须按照 第 7.3 节,见 [RFC8252] 所述允许可变 端口号。

其他建议:

  • 托管重定向 URI 的 Web 服务器不得暴露开放 重定向器(参见 第 4.11 节)。
  • 只有在 Location 标头中的 URL 尚未包含片段时,浏览器才会将 URL 片段重新附加到 Location 重定向 URL。 因此,服务器可以通过向 Location 标头中的 URL 附加任意片段标识符(例如 #_),阻止浏览器重新附加 片段。
  • 客户端应该使用 授权码响应类型,而不是 会导致在授权 端点签发访问令牌的响应类型。这样可以通过与授权 服务器进行兑换的过程来防止泄露 凭据被重复使用,并通过对访问 令牌施加发送者约束来防止令牌重放。

如果能够验证包含 重定向 URI 的授权请求的来源和完整性,例如使用 [RFC9101] 或结合客户端 认证使用 [RFC9126] 时,授权服务器可以信任 重定向 URI, 而无需进一步检查。

4.2. 通过 Referer 标头泄露凭据

授权请求 URI 或授权 响应 URI 的内容可能分别从授权 服务器的网站或客户端的网站中,经由 Referer HTTP 标头(参见 第 10.1.3 节,见 [RFC9110])无意泄露给攻击者。最重要的是, 授权码或 state 值可能以这种方式泄露。虽然 第 10.1.3 节,见 [RFC9110] 中另有规定, 由于浏览器实现问题,通过 URI 片段传递的访问令牌也可能出现 同样的情况,Chromium 项目中的一个(现已修复的)问题 [bug.chromium] 就说明了这一点。

4.2.1. 从 OAuth 客户端泄露

从 OAuth 客户端发生泄露要求客户端在 授权请求成功后呈现一个页面,该页面

  • 包含指向攻击者控制的其他页面的链接,并且 用户点击了这样的链接;或者
  • 包含第三方内容(iframe 中的广告、图片 等),例如页面包含用户生成的内容 (博客)时。

一旦浏览器导航到攻击者的页面或加载了 第三方内容,攻击者就会收到授权响应 URL,并可从中提取 code 或 state(以及可能的 access_token)。

4.2.2. 从 授权服务器泄露

类似地,如果授权服务器上的授权 端点包含上述链接或第三方内容,攻击者可以从授权 请求中获知 state。

4.2.3. 后果

攻击者如果通过 Referer 标头获知有效授权码或访问令牌,就可以执行第 4.1.1、4.5 和 4.6 节所述的攻击。如果攻击者 获知 state,则通过 state 获得的 CSRF 保护将失效,从而导致 第 4.4.1.8 节,见 [RFC6819] 所述的 CSRF 攻击。

4.2.4. 对策

作为 OAuth 授权响应结果而呈现的页面以及 授权端点不应该包含第三方 资源或指向外部站点的 链接。

以下措施可进一步降低攻击成功的 可能性:

  • 通过向文档应用适当的 Referrer Policy [W3C.webappsec-referrer-policy] 来禁止发送 Referer 标头 (可以作为 “referrer” meta 属性的一部分,或者通过设置 Referrer-Policy 标头)。例如,响应中的标头 Referrer-Policy: no-referrer 会完全禁止由生成的文档发出的所有请求中的 Referer 标头。
  • 使用授权码,而不是会导致从授权端点 签发访问 令牌的响应类型。
  • 将授权码绑定到机密 客户端或 PKCE challenge。在这种情况下,攻击者缺少请求 授权码兑换所需的秘密。
  • 如 第 4.1.2 节,见 [RFC6749] 所述,授权码 在令牌 端点首次使用后,授权服务器必须使其失效。例如,如果授权服务器在 合法客户端兑换授权码后使其失效,攻击者随后将无法再兑换 此授权码。

    如果攻击者设法在合法客户端之前 将授权码兑换成令牌,则这一措施无法缓解攻击。 因此,[RFC6749] 进一步建议,当 尝试重复兑换某个授权码时,授权服务器应该撤销此前基于该授权码签发的所有 令牌。

  • state 值在重定向端点 首次使用后,客户端应该使其失效。如果实现了这一点,并且 攻击者通过客户端网站的 Referer 标头收到令牌,则 state 已被使用并由 客户端使其失效,因此攻击者无法再次使用它。(如果 state 从 授权服务器的网站泄露,这种措施无效,因为此时 state 尚未在客户端的重定向端点使用。)

  • 对授权响应使用 form post 响应模式,而不是重定向 (参见 [OAuth.Post])。

4.3. 通过 浏览器历史记录泄露凭据

授权码和访问令牌可能最终出现在浏览器的 已访问 URL 历史记录中,从而导致下文所述的 攻击。

4.3.1. 浏览器历史记录中的授权 码

当浏览器由于提供商授权端点的 重定向而导航到 client.example/redirection_endpoint?code=abcd 时,包含 授权码的 URL 可能会进入浏览器历史记录。能够 访问该设备的攻击者可能获得该授权码并尝试 重放它。

对策:

4.3.2. 浏览器历史记录中的访问令牌

如果已经拥有令牌的客户端或 网站故意导航到类似 provider.com/get_user_profile?access_token=abcdef 的页面,则访问令牌可能最终进入浏览器历史记录。[RFC6750] 不鼓励这种做法,并建议通过标头传输令牌, 但在实践中,网站经常通过查询 参数传递访问令牌。

在隐式许可的情况下,类似 client.example/redirection_endpoint#access_token=abcdef 的 URL 也可能 因提供商授权端点的重定向而最终 进入浏览器历史记录。

对策:

  • 客户端不得 以 第 2.3 节,见 [RFC6750] 所述的方式在 URI 查询参数中 传递访问令牌。可以使用授权 码许可或其他 OAuth 响应模式,例如 form post 响应模式 [OAuth.Post] 来达到此目的。

4.4. 混淆攻击

当 OAuth 客户端与 两个或更多授权服务器交互,并且至少一个授权 服务器受攻击者控制时,可能发生混淆攻击。例如, 如果攻击者使用动态注册将 客户端注册到其自己的授权服务器,或者某个授权服务器 遭到入侵,就可能出现这种情况。

攻击的目标是获得未受入侵的授权服务器所签发的授权码或访问 令牌。其实现方式是 诱骗客户端将这些凭据发送给受入侵的 授权服务器(攻击者),而不是在 未受入侵的授权/资源 服务器的相应端点使用这些凭据。

4.4.1. 攻击描述

这里的描述遵循 [arXiv.1601.01229],并在 下文概述了攻击的不同变体。

前提条件:为了使此攻击变体奏效,假设

  • 使用隐式许可或授权码许可,并涉及多个授权服务器, 其中一个被认为是“诚实的”(H-AS),另一个由 攻击者运行(A-AS);并且
  • 客户端将用户选择的授权服务器 存储在绑定到 用户浏览器的会话中,并对每个 授权服务器使用相同的重定向 URI。

下面进一步假设客户端已 在 H-AS(URI: https://honest.as.example,客户端 ID:7ZGZldHQ)和 A-AS (URI: https://attacker.example,客户端 ID:666RVZJTA)上注册。下面 示例中显示的 URL 为便于展示而进行了缩短,仅包含与 攻击相关的参数。

针对授权码许可的攻击:

  1. 用户选择使用 A-AS 启动许可(例如,通过 点击 客户端网站上的按钮)。
  2. 客户端在用户会话中存储用户 选择了 “A-AS”,并将用户重定向到 A-AS 的授权端点, 其中 Location 标头包含 URL https://attacker.example/authorize?response_type=code&client_id=666RVZJTA。
  3. 当用户的浏览器导航到攻击者的 授权端点时, 攻击者立即将浏览器重定向到 H-AS 的授权端点。 在授权请求中,攻击者将 客户端在 A-AS 上的客户端 ID 替换为客户端在 H-AS 上的 ID。因此,浏览器 会收到一个重定向(303 See Other),其 Location 标头指向 https://honest.as.example/authorize?response_type=code&client_id=7ZGZldHQ
  4. 用户授权客户端访问其 在 H-AS 上的资源。(请注意, 警觉的用户可能会在此时发现其原本打算使用 A-AS 而不是 H-AS。下面列出的第一个攻击变体不存在此 限制。)H-AS 签发授权码,并通过浏览器将其发送回客户端。

  5. 由于客户端仍然认为该授权码是由 A-AS 签发的, 因此会尝试在 A-AS 的令牌端点兑换该授权码。

  6. 因此攻击者获得了授权码,并且可以 将该授权码兑换为访问令牌(对于公共客户端),或者执行 第 4.5 节所述的 授权码注入攻击。

变体:

  • 带拦截的混淆攻击:此变体仅在攻击者能够 拦截并操纵用户 浏览器与客户端之间的第一个请求/响应对(其中用户选择某个授权服务器,随后 客户端将其 重定向到该授权服务器)时才能奏效,如 攻击者 (A2)(参见 第 3 节)。例如,这种能力 可能来自针对用户与客户端之间连接的中间人攻击。在此攻击中,用户使用 H-AS 启动流程。 攻击者拦截此请求,并将用户的选择更改为 A-AS。攻击的其余部分按照上面的 步骤 2及之后的步骤进行。
  • 隐式许可:在隐式许可中,攻击者在 步骤 4 中收到的是访问 令牌,而不是授权码。当客户端向 A-AS userinfo 端点(定义于 [OpenID.Core])发出请求,或者向 攻击者的资源服务器发出请求时(因为客户端认为其已 完成与 A-AS 的流程),攻击者的授权服务器就会收到访问令牌。
  • 每个 AS 使用不同的重定向 URI:如果客户端对 不同授权服务器使用不同的重定向 URI, 客户端不在用户会话中存储所选择的授权 服务器,并且授权服务器 没有正确检查重定向 URI,则攻击者可以发动一种 称为“跨社交网络请求伪造”的攻击。这些攻击已经在 实践中被观察到。详情参见 [research.jcs_14]。
  • OpenID Connect:某些变体可用于 攻击 OpenID Connect。在这些攻击中,攻击者滥用 OpenID Connect Discovery [OpenID.Discovery] 机制的功能,或者重放访问 令牌或 ID Token 来实施混淆攻击。这些攻击在 [arXiv.1704.08539] 的附录 A 和 [arXiv.1508.04324v2] 的第 6 节(“恶意端点 攻击”)中有详细描述。

4.4.2. 对策

当 OAuth 客户端只能与一个授权 服务器交互时,不需要针对混淆攻击采取防御措施。但是,在 OAuth 客户端与两个 或更多授权服务器交互的场景中,客户端必须防止 混淆攻击。下面讨论两种 不同的方法。

对于这两种防御措施,客户端必须为每个授权请求 存储其发送授权请求的 发行者,并将此信息绑定到 用户代理。通过关联的元数据,发行者可作为授权端点和令牌端点组合的抽象 标识符, 该组合将在流程中使用。如果无法获得发行者标识符(例如, 既未使用 OAuth 授权服务器元数据 [RFC8414],也未使用 OpenID Connect Discovery [OpenID.Discovery]),则可以 改用该元组的其他唯一标识符,或者直接使用该元组本身。 为简化表述,下文将这种特定于部署的标识符 统一归入发行者(或发行者标识符)的概念。

需要注意的是,仅存储授权服务器 URL 不足以识别 混淆攻击。攻击者可能将未受入侵的授权服务器的 授权端点 URL 声明为 “自己的”授权服务器 URL,却声明一个由其自身 控制的令牌端点。

4.4.2.1. 通过发行者 标识防御混淆攻击

此防御要求授权服务器在授权响应中向客户端发送 其发行者标识符。 在收到授权 响应时,客户端必须将收到的发行者 标识符与已存储的 发行者标识符进行比较。如果不匹配,客户端必须中止 交互。

可以通过不同方式将该发行者标识符 传输给客户端:

  • 例如,可以通过 [RFC9207] 中定义的独立响应参数 iss 传输发行者信息。
  • 使用 OpenID Connect 且授权 响应中返回 ID Token 时,客户端可以评估 ID Token 中的 iss claim。

在这两种情况下,iss 值必须按照 [RFC9207] 进行评估。

虽然这种防御可能需要部署新的 OAuth 功能来传输 发行者信息,但它是一种稳健且相对简单的混淆 攻击防御措施。

4.4.2.2. 通过不同重定向 URI 防御混淆攻击

对于这种防御,客户端必须 为其交互的每个发行者使用不同的重定向 URI。

客户端必须通过将 发行者对应的不同重定向 URI 与实际接收 授权响应的 URI 进行比较,检查授权响应是否来自正确的 发行者。如果不匹配,客户端 必须中止该流程。

虽然这种防御基于现有 OAuth 功能,但在客户端仅注册一次即可使用多个不同 发行者的场景中(例如某些开放银行方案)无法使用;而且由于与 客户端注册紧密集成,它更难自动部署。

此外,攻击者可能能够绕过 此防御所提供的保护,方法是向“诚实的”授权服务器注册一个新客户端, 并使用客户端分配给攻击者授权服务器的重定向 URI。随后攻击者便可以按上述方式实施 攻击,将 客户端 ID 替换为其新创建客户端的客户端 ID。

因此,只有在其他选项不可用时才应该 使用这种防御。

4.5. 授权码 注入

获得授权响应中所含授权码的攻击者 (参见 第 3 节中的 攻击者 (A3))可以尝试将该 授权码兑换为访问令牌,或者以其他方式使用该 授权码。

如果授权码是为公共客户端创建的, 攻击者可以将授权码发送到 授权服务器的令牌端点,从而获得访问令牌。此攻击在 第 4.4.1.1 节,见 [RFC6819] 中已有描述。

对于机密客户端,或者在某些特殊情况下,攻击者可以 执行授权码注入攻击,如下所述。

在授权码注入攻击中,攻击者试图将 被盗的授权码注入攻击者自己与客户端的会话中。其 目的是将攻击者在客户端处的会话与受害者的 资源或身份关联起来,从而至少让攻击者获得对 受害者资源的有限访问权限。

除了绕过机密客户端的客户端认证之外,此 攻击的其他使用场景包括:

  • 攻击者希望访问此特定 客户端中的某些功能。例如,攻击者希望在某个应用 或某个网站上冒充其 受害者。
  • 授权服务器或资源服务器仅限于某些 攻击者无法直接访问的 网络。

除这些特殊情况外,当授权码是为公共客户端创建时,授权码注入通常并无 意义,因为如上所述,直接将授权码 发送到令牌端点是一种更简单且威力更大的攻击。

4.5.1. 攻击 描述

授权码注入攻击按如下方式进行:

  1. 攻击者获得一个授权码(参见 第 3 节中的 攻击者 (A3))。在攻击的其余部分中, 只需要 Web 攻击者 (A1) 的能力。
  2. 攻击者在自己的设备上与合法客户端启动一个 正常的 OAuth 授权 流程。
  3. 在授权服务器发给 合法客户端的响应中, 攻击者将新创建的授权码替换为被盗的 授权码。由于该响应经过攻击者的 设备,攻击者可以使用任何能够拦截和操纵 授权响应的工具来实现这一点。攻击者不需要控制 网络。
  4. 合法客户端将授权码发送到授权 服务器的令牌 端点,同时发送 redirect_uri、客户端的客户端 ID 和 客户端密钥(或其他客户端认证方式)。
  5. 授权服务器检查客户端密钥、该 授权码是否签发给特定客户端,以及实际 重定向 URI 是否与 redirect_uri 参数匹配(参见 [RFC6749])。
  6. 所有检查均成功,授权服务器向客户端签发访问 令牌和 其他令牌。此时,攻击者已经将其 与合法客户端的会话关联到了受害者的资源 和/或身份。

4.5.2. 讨论

显然,如果授权码是签发给 另一个客户端 ID 的,例如攻击者建立的客户端,则检查步骤(步骤 5)会失败。如果授权码已经被 合法用户兑换并且只能使用一次,检查 同样会失败。

如果授权服务器存储了授权请求中使用的 完整重定向 URI,并将其与 redirect_uri 参数进行比较, 那么通过被篡改的重定向 URI 获得的授权 码注入尝试也应该会被检测到。

第 4.1.3 节,见 [RFC6749] 要求授权服务器

确保如果初始授权 请求中按照第 4.1.1 节所述包含了 “redirect_uri”参数,则“redirect_uri”参数必须存在, 并且如果包含,则确保 两者的值完全相同。

在 第 4.5.1 节所述的攻击场景中,合法 客户端会使用其在 授权请求中始终使用的正确重定向 URI。但是,该 URI 不会与攻击者使用的被篡改 重定向 URI 匹配(否则重定向不会 落到攻击者的页面上)。因此,授权服务器会检测到 攻击,并拒绝兑换授权码。

在满足某些条件时,此检查还可以检测从另一设备上的 同一客户端的另一个实例获得的授权 码注入尝试:

  • 重定向 URI 本身包含一个 nonce 或其他类型的 一次性秘密数据,并且
  • 客户端已将该数据绑定到 客户端的此特定实例。

但是,这种方法与在授权端点强制执行精确 重定向 URI 匹配的理念相冲突。此外,实际观察发现,提供商在此阶段经常忽略 redirect_uri 检查 要求, 可能是因为从规范文字来看,这一要求似乎并非 安全关键。

其他提供商只是将 redirect_uri 参数 与已注册的重定向 URI 模式进行模式匹配。这使 授权服务器无需为每个事务存储实际重定向 URI 与相应授权码之间的关联。但是, 这种检查显然没有实现 规范的意图,因为并未考虑被篡改的重定向 URI。因此, 任何试图注入使用合法客户端 client_id 获得的授权码,或通过在另一设备上使用合法 客户端而获得的授权码的行为,在相应 部署中都不会被检测到。

还假定 第 4.1.3 节,见 [RFC6749] 中定义的要求会增加 客户端实现复杂度,因为客户端 需要存储或重建调用 令牌端点所需的正确重定向 URI。

客户端认证的非对称方法无法阻止此 攻击,因为 是合法客户端在令牌端点进行认证。

因此,本文档建议改为使用下文所述的某种 机制,在特定事务的上下文中,将每个 授权 码绑定到特定设备上的某个客户端实例(或某个 用户代理中)。

4.5.3. 对策

有两种很好的技术方案可将授权 码绑定到客户端 实例,如下所述。

4.5.3.1. PKCE

[RFC7636] 中规定的 PKCE 机制可用作对策 (尽管它最初被设计用于保护原生应用)。当 攻击者试图注入授权码时,对 code_verifier 的检查会失败:客户端使用其正确的 verifier,但该 授权码 与一个不匹配此 verifier 的 code_challenge 关联。

PKCE 不仅能防止授权码 注入攻击, 还可以保护为公共客户端创建的授权码:PKCE 确保 攻击者在不知道 code_verifier 的情况下,无法在 授权服务器的令牌端点兑换被盗的授权码。

4.5.3.2. Nonce

OpenID Connect 现有的 nonce 参数可以 防止授权 码注入攻击。nonce 值只能使用一次,并由 客户端创建。客户端应将其绑定到用户代理会话,并在 初始请求中将其发送给 OpenID Provider(OP)。OP 将收到的 nonce 值放入 ID Token 中,该 ID Token 作为在令牌端点进行授权码兑换的一部分 签发。 如果攻击者在授权响应中注入 授权码,则客户端 会话中的 nonce 值与从令牌 端点收到的 ID Token 中的 nonce 值将不匹配,从而 检测到攻击。这里假定攻击者无法获取受害者设备上的用户代理 状态(攻击者正是从该设备中窃取了相应的 授权 码)。

需要注意的是,只有在客户端 正确检查从令牌端点获得的 ID Token 中的 nonce 参数,并且在此检查成功之前不使用任何 已签发令牌的情况下,此对策才有效。更准确地说,使用 nonce 参数防止 授权码注入的客户端

  1. 必须验证从令牌端点获得的 ID Token 中的 nonce, 即使已经从授权响应中获得了另一个 ID Token (例如 response_type=code+id_token);并且
  2. 必须确保除非且直到该检查 成功,否则所有令牌(ID Token 和访问令牌)都被忽略,并且不用于任何其他 用途。

需要注意的是,nonce 无法 保护公共客户端的授权码,因为攻击者不需要执行授权码 注入攻击。相反,攻击者可以直接使用 被盗的授权码调用令牌端点。

4.5.3.3. 其他 解决方案

其他解决方案,例如将 state 绑定到 授权码、使用加密方式对授权码施加发送者约束,或使用每个实例独有的客户端凭据,都是 可以设想的,但缺乏支持,并会带来新的安全要求。

PKCE 是 OAuth 客户端最显而易见的解决方案,因为在 撰写本文时它已经可用,而 nonce 则适用于 OpenID Connect 客户端。

4.5.4. 局限性

如果攻击者能够修改受害者授权请求中使用的 nonce 或 code_challenge 值,则攻击者可以绕过上述对策。 攻击者可以将这些值 修改为与客户端在攻击者自己的会话中所选择的值相同, 即上述攻击 步骤 2 中的值。(这要求受害者 与客户端的会话在攻击者已经启动其 与客户端的会话之后才开始。)如果攻击者随后能够捕获 受害者的授权码,那么即使使用 PKCE 或 nonce,攻击者仍然能够在 步骤 3 中注入被盗授权码。

这种攻击很复杂,并且需要攻击者与 受害者会话之间进行紧密交互。尽管如此,仍然需要采取措施阻止 攻击者读取授权响应的内容, 如第 4.1、4.2、 4.3、4.4 和 4.11 节所述。

4.6. 访问令牌注入

在访问令牌注入攻击中,攻击者试图将 被盗的访问令牌注入合法客户端(该客户端不受 攻击者控制)。通常,当攻击者希望 利用泄露的访问令牌在某个特定 客户端中冒充用户时,会发生这种攻击。

为实施攻击,攻击者使用隐式许可与 客户端启动 OAuth 流程,并通过替换授权 服务器签发的访问令牌来修改授权 响应,或者直接伪造一个包含 泄露访问令牌的授权服务器响应。由于响应包含 客户端为该特定事务生成的 state 值,因此客户端 不会将响应视为 CSRF 攻击,并使用攻击者 注入的访问令牌。

4.6.1. 对策

在纯 OAuth 流程中,没有办法检测此类注入攻击,因为令牌在签发时没有与 事务或特定用户代理进行任何绑定。

在 OpenID Connect 中,可以缓解该攻击,因为授权 响应 还包含一个带有 at_hash claim 的 ID Token。因此,攻击者 必须同时替换响应中的访问令牌和 ID Token。 攻击者无法伪造 ID Token,因为它已签名或使用认证 加密。攻击者也无法注入与 被盗访问令牌匹配的泄露 ID Token,因为泄露的 ID Token 中的 nonce claim 极有可能包含与授权响应中 预期值不同的值。

请注意,仍然需要进一步的保护,例如发送者约束访问令牌, 以防止攻击者直接在资源 端点使用访问令牌。

第 2.1.2 节中的建议由此而来。

4.7. 跨站请求 伪造

攻击者可能试图向受害者设备上 合法客户端的重定向 URI 注入请求,例如使 客户端访问受攻击者控制的资源。这是 一种称为跨站请求伪造(CSRF)的攻击变体。

4.7.1. 对策

一种长期使用的对策是客户端在 state 参数中传递一个随机 值,也 称为 CSRF Token,该值将发往重定向 URI 的请求与用户代理会话关联起来,如前所述。此 对策在 第 5.3.5 节,见 [RFC6819] 中有详细描述。 PKCE 或 OpenID Connect 的 nonce 值也提供 相同的保护。

使用 PKCE 代替 state 或 nonce 来提供 CSRF 保护时, 需要注意:

  • 客户端必须在使用 PKCE 进行 CSRF 保护之前,确保 授权服务器支持 PKCE。如果授权服务器不支持 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 攻击。

4.8. PKCE 降级攻击

支持 PKCE 但没有强制要求 所有流程都使用 PKCE 的授权服务器可能容易受到 PKCE 降级攻击。

此攻击的第一个前提条件是授权请求中存在一个 可由攻击者控制的 标志,用于为特定 流程启用或禁用 PKCE。code_challenge 参数是否存在正适合 用于此目的,即:如果该 参数存在于授权请求中,授权服务器便启用并强制执行 PKCE;而如果 缺少该参数,则不强制执行 PKCE。

此攻击的第二个前提条件是客户端完全没有使用 state (例如,因为客户端依赖 PKCE 防止 CSRF),或者 客户端没有正确检查 state。

概括来说,此攻击是 CSRF 攻击的一种变体。攻击者 实现了与 第 4.7 节所述攻击相同的目标:攻击者将一个 绑定到攻击者资源的授权码(以及由此得到的访问令牌)注入其受害者与客户端之间的 会话中。

4.8.1. 攻击 描述

  1. 用户已在某个授权服务器上使用某个客户端启动 OAuth 会话。在 授权请求中,客户端将参数 code_challenge=hash(abc) 设置为 PKCE code challenge(散列 函数和参数编码按照 [RFC7636] 中的定义)。客户端现在 正在等待从用户浏览器接收授权响应。
  2. 为实施攻击,攻击者使用自己的设备 与目标客户端启动 授权流程。客户端现在在授权 请求中使用另一个 PKCE code challenge,例如 code_challenge=hash(xyz)。攻击者拦截该请求,并从请求中删除整个 code_challenge 参数。由于此步骤是在 攻击者的设备上完成的,因此攻击者可以完全访问请求内容, 例如使用浏览器调试工具。
  3. 如果授权服务器允许不使用 PKCE 的流程,则它 会创建一个 未绑定到任何 PKCE code challenge 的授权码。
  4. 攻击者现在将用户的浏览器重定向到一个 授权响应 URL,其中包含攻击者与授权 服务器的会话所对应的授权码。
  5. 用户浏览器将授权码发送给 客户端,客户端随后 尝试在授权服务器处将该授权码兑换为访问令牌。客户端会 在令牌 请求中发送 code_verifier=abc 作为 PKCE code verifier。
  6. 由于授权服务器发现此授权码未 绑定到任何 PKCE code challenge,因此不会检查 code_verifier 参数是否存在或其内容。它将向用户控制下的客户端签发一个访问令牌 (该令牌属于 攻击者的资源)。

4.8.2. 对策

正确使用 state 可以防止此攻击。 但是实践表明, 许多 OAuth 客户端没有使用或没有正确检查 state。

因此,授权服务器必须 缓解此攻击。

请注意,从授权服务器的角度来看,在上述 攻击中, 令牌端点会收到一个 code_verifier 参数,尽管 签发该授权码的 OAuth 流程的授权请求中并不存在 code_challenge 参数。

可以利用这一事实缓解此攻击。[RFC7636] 已经规定

  • 支持 PKCE 的授权服务器 必须检查授权请求中是否包含 code challenge, 并将此信息绑定到所 签发的授权码;并且
  • 当授权码到达令牌端点时, 如果签发该授权码的授权请求中存在 code_challenge, 则令牌请求中必须存在 有效的 code_verifier。

除此之外,为防止 PKCE 降级攻击,授权 服务器必须确保: 如果授权请求中不存在 code_challenge,则必须拒绝 包含 code_verifier 的令牌端点请求。

强制要求使用 PKCE(无论是普遍要求还是 针对特定客户端)的授权服务器 已隐式实现了此安全措施。

4.9. 资源服务器处的访问令牌泄露

在某些 情况下,访问令牌可能从资源服务器泄露。

4.9.1. 伪造资源服务器进行的访问令牌 网络钓鱼

攻击者可能建立自己的资源服务器,并诱骗客户端 向其 发送对其他资源服务器有效的访问令牌 (参见 第 3 节中的攻击者 (A1) 和 (A5))。如果客户端将有效访问令牌发送给 这个伪造的资源服务器,则攻击者随后可能使用该 令牌代表资源所有者访问其他服务。

此攻击假定客户端在开发时没有绑定到某一个特定资源 服务器(及其 URL),而是客户端实例在运行时 获得资源服务器 URL。 这种后期 绑定在客户端使用实现标准化 API 的服务时很常见 (例如电子邮件、日历、电子健康 或开放银行),并且客户端由用户或 管理员进行配置。

4.9.2. 遭入侵的 资源服务器

攻击者可能入侵资源服务器,以访问相应部署的 资源。此类入侵的程度可以从 对系统的部分访问(例如其日志文件),到完全 控制相应服务器;在后一种情况下,所有控制措施都可以被 绕过,并且所有资源都可以 被访问。攻击者还能够获得遭入侵系统上持有的其他访问 令牌,而这些令牌可能对访问 其他资源服务器有效。

通过加固和监控服务器 系统来防止服务器遭到入侵 被视为标准运维过程,因此不属于 本文档的范围。 第 4.9 节重点讨论 OAuth 相关入侵产生的影响以及捕获的访问令牌被重放的问题。

4.9.3. 对策

为了应对恶意行为者进行的访问令牌重放,实现者 应考虑采取 以下措施:

  • 应该使用 第 4.10.1 节所述的发送者约束访问令牌, 以防止攻击者在其他资源服务器上重放访问 令牌。如果攻击者仅对遭入侵系统具有部分 访问权限,例如只能读取 Web 服务器日志,则发送者约束访问令牌也可能阻止 在遭入侵系统上进行重放。
  • 应该使用 第 4.10.2 节所述的受众限制, 以防止在其他资源 服务器上重放捕获的访问令牌。
  • 资源服务器必须像处理其他敏感秘密一样处理访问令牌, 不得以明文形式存储或传输它们。

第一项和第二项建议也适用于访问令牌发生泄露的其他场景 (参见 第 3 节中的 攻击者 (A5))。

4.10. 滥用被盗的 访问令牌

攻击者可以通过多种方式窃取访问令牌,例如, 通过第 4.1、 4.2、4.3、4.4 和 4.9 节所述的攻击。其中一些攻击可以通过 相应章节所述的 特定安全措施来缓解。 但是,在某些情况下,这些措施并不足够,或者没有 正确实现。因此,授权服务器应该确保 访问令牌具有发送者约束和受众限制,如 下文所述。出于架构和性能方面的原因, 某些部署可能无法使用这些措施。

4.10.1. 发送者约束访问令牌

顾名思义,发送者约束访问令牌将 访问令牌的适用范围限制到特定发送者。该发送者 必须证明其知道某个特定秘密,作为 资源服务器接受该令牌的先决条件。

典型流程如下:

  1. 授权服务器将数据与访问 令牌关联, 以便将该特定令牌绑定到某个客户端。这种绑定 可以使用客户端的身份,但在大多数情况下,授权服务器会使用 客户端已知的 密钥材料(或由密钥材料派生的数据)。
  2. 此密钥材料必须以某种方式分发。可以是 密钥 材料在授权服务器创建绑定之前就已经存在,也可以由 授权服务器创建临时密钥。预先存在的密钥材料如何 分发,会因不同方法而异。例如, 可以使用 X.509 证书,在这种情况下,分发 会在注册过程中显式发生。或者,密钥 材料在 TLS 层创建和分发,在这种 情况下,它可能在建立 TLS 连接期间自动完成。
  3. 资源服务器必须执行实际的 持有证明检查。此操作 通常在应用层完成,并经常与传输层提供的特定 材料(例如 TLS)绑定。资源服务器还必须 确保持有证明无法被重放。

OAuth 工作组已经定义并在实践中使用了两种 通过持有证明实现发送者约束访问令牌的方法:

  • “OAuth 2.0 双向 TLS 客户端认证 和证书绑定 访问令牌”[RFC8705]:本文档 中规定的方法允许将双向 TLS 同时用于客户端 认证和发送者约束访问令牌。就 发送者约束访问令牌而言,客户端通过其 公钥指纹向资源服务器进行标识。在处理访问令牌请求时, 授权服务器从 TLS 栈获取客户端公钥,并将其指纹与相应访问 令牌关联。资源服务器以同样方式从 TLS 栈获得公钥, 并将其指纹与 访问令牌关联的指纹进行比较。
  • “OAuth 2.0 持有证明演示 (DPoP)”[RFC9449]: DPoP 描述了一种 应用层机制,用于对访问令牌和刷新 令牌施加发送者约束。它使用 基于公钥/私钥对和 应用层签名的持有证明。DPoP 可用于公共客户端; 对于机密客户端,它可以与任意 客户端认证方法结合使用。

请注意,当 攻击者同时获得令牌和密钥材料时,发送者约束令牌的安全性就会受到破坏。特别是, 客户端软件遭到破坏以及跨站 脚本攻击(当客户端运行在浏览器中时)就属于这种情况。如果 密钥材料受到硬件或软件安全模块保护,或者 只能间接访问(例如在 TLS 栈中),那么发送者约束 令牌至少能够在客户端 离线时防止令牌被使用,即安全模块或接口无法被 攻击者使用时。这既适用于访问令牌,也适用于刷新 令牌(参见 第 4.14 节)。

4.10.2. 受众限制访问令牌

受众限制本质上将访问令牌限制为 某个特定资源服务器。授权服务器将 访问令牌与特定资源服务器关联,随后资源 服务器应验证预期受众。如果访问令牌未通过 预期受众验证,资源服务器就拒绝处理 相应请求。

一般来说,受众限制能够限制令牌 泄露的影响。 在伪造资源服务器的情况下,它还可能(如下所述) 防止被钓取的访问令牌在 合法资源服务器上遭到滥用。

受众可以使用逻辑名称或 物理地址(例如 URL)表示。为了防止网络钓鱼,必须 使用客户端实际要向其发送请求的 URL。在 网络钓鱼场景中,该 URL 将指向伪造的资源 服务器。如果攻击者试图在 合法资源服务器(其 URL 不同)上使用该访问令牌,则资源 服务器会检测到不匹配(受众错误),并拒绝处理 请求。

在授权服务器知道所有 资源服务器 URL 的部署中,授权服务器可以直接拒绝为 未知资源服务器 URL 签发 访问令牌。

为了实现这一点,客户端需要告知授权服务器 预期的 资源服务器。可以使用 [RFC8707] 中的机制来实现,或者将该 信息编码在 scope 值中(第 3.3 节,见 [RFC6749])。

除了 URL,还可以使用 资源服务器 X.509 证书的指纹作为受众值。此 变体还可以检测通过使用从不同 CA 获得的 有效 TLS 证书来伪造合法 资源服务器 URL 的尝试。隐藏 资源服务器 URL,不让授权服务器获知,也可能被视为一种隐私优势。

受众限制可能看起来更容易使用,因为客户端侧不需要 任何加密操作。但是,由于每个访问令牌都 绑定到特定资源服务器,因此客户端在访问多个资源 服务器时,也需要为每个资源服务器分别获得 一个专用访问令牌。([RFC8707] 中规定的资源指示符可帮助实现 这一点。) [TOKEN-BINDING] 具有同样的特性,因为不同的 token-binding ID 必须与访问令牌关联。另一方面,使用 OAuth 2.0 双向 TLS [RFC8705] 则允许客户端在多个资源服务器上使用同一个 访问令牌。

需要注意的是,受众限制——或者更一般地说, 客户端向授权服务器指明其希望在哪里 使用访问令牌——除了防止 令牌泄露之外,还有其他好处。它允许授权服务器创建 一个不同的访问令牌,其格式和内容是专门为 相应服务器签发的。在使用结构化访问令牌的部署中,这具有巨大的功能和隐私 优势。

4.10.3. 讨论: 通过元数据防止泄露

授权服务器可以向客户端提供有关 哪些位置可以安全使用其访问 令牌的额外信息。下面讨论这种方法以及为什么不建议 使用它。

最简单的形式是要求授权服务器 发布其已知 资源服务器的列表,以下示例使用非标准授权服务器元数据参数 resource_servers 进行说明:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "issuer":"https://server.somesite.example",
  "authorization_endpoint":
    "https://server.somesite.example/authorize",
  "resource_servers":[
    "email.somesite.example",
    "storage.somesite.example",
    "video.somesite.example"
  ]
  ...
}

授权服务器还可以在 令牌响应中返回访问 令牌适用的 URL,以下示例使用非标准返回 参数 access_token_resource_server 进行说明:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "access_token_resource_server":
    "https://hostedresource.somesite.example/path1",
...
}

这种缓解策略将依赖客户端执行 安全策略,并且只将访问令牌发送到合法 目标。OAuth 相关安全研究的结果(例如,参见 [research.ubc] 和 [research.cmu])表明,很大一部分客户端实现没有正确实现或根本没有 实现诸如 state 检查这样的安全控制。因此,依赖 客户端防止访问令牌网络钓鱼同样很可能失败。 此外,考虑到客户端与授权服务器和资源服务器之间的数量比例, 将尽可能多的 安全相关逻辑移到这些服务器上被认为是更可行的做法。 显然,客户端 必须为整体安全做出贡献。但是,还有替代 对策,如第 4.10.1 和 4.10.2 节所述,它们在相关参与方之间提供了 更好的平衡。

4.11. 开放重定向

当授权服务器或客户端具有 开放重定向器时,可能发生以下攻击。有时会实现此类端点, 例如,在随后将用户重定向到外部 网站之前显示消息,或者在用户受到 登录提示等中断后,将用户重定向回其原本想访问的 URL。

4.11.1. 客户端充当开放 重定向器

客户端不得暴露开放 重定向器。攻击者可能使用开放 重定向器生成指向客户端的 URL,并利用它们 窃取授权码和访问令牌,如 第 4.1.2 节所述。另一种 滥用情形是生成看起来 指向客户端的 URL。这可能会诱骗用户信任该 URL 并在浏览器中打开它。这可被用于网络钓鱼。

为防止开放重定向,客户端只应在 目标 URL 被允许,或者请求的来源和完整性 可以被认证时才执行重定向。OWASP [owasp.redir] 描述了针对开放重定向的对策。

4.11.2. 授权 服务器充当开放重定向器

与客户端一样,攻击者也可能尝试利用用户对 授权服务器(特别是其 URL)的信任来实施 网络钓鱼攻击。OAuth 授权服务器经常将用户重定向 到其他网站(客户端),但必须以安全方式进行。

第 4.1.2.1 节,见 [RFC6749] 已经通过规定在 client_id 与 redirect_uri 组合无效时,授权服务器不得自动 重定向用户代理来防止开放重定向。

但是,攻击者也可以利用正确注册的 重定向 URI 实施网络钓鱼攻击。例如,攻击者可以通过动态客户端注册 [RFC7591] 注册一个客户端,并执行以下攻击之一:

  1. 故意发送错误的授权请求,例如 使用 无效的 scope 值,从而让授权服务器将 用户代理重定向到其网络钓鱼站点。
  2. 故意发送一个有效的授权请求,其中 client_id 和 redirect_uri 由攻击者控制。用户认证后, 授权服务器提示用户同意该请求。如果 用户发现请求存在问题并拒绝该请求, 授权服务器仍会将用户代理重定向到网络钓鱼站点。在 这种情况下,无论用户采取何种操作,用户代理都会被重定向到网络钓鱼站点。
  3. 故意发送一个有效的静默认证请求 (prompt=none), 其中 client_id 和 redirect_uri 由 攻击者控制。在这种 情况下,授权服务器会自动将用户代理重定向到 网络钓鱼站点。

授权服务器必须采取 预防措施以防止这些威胁。授权服务器必须始终 首先认证用户,并且除静默认证 使用场景外,在重定向 用户之前,在需要时提示用户输入凭据。授权服务器需要根据风险评估决定是否 可以信任 重定向 URI。它可以考虑内部完成的 URI 分析,或者通过某些外部服务评估 URI 背后内容的可信度和 可靠性,以及重定向 URI 的来源和 其他客户端数据。

授权服务器应该仅在信任 重定向 URI 时 自动重定向用户代理。如果 URI 不可信,授权服务器可以通知用户,并依赖 用户作出正确决定。

4.12. 307 重定向

在授权端点,一个典型的协议流程是 授权服务器 提示用户在表单中输入其凭据,然后 将该表单(使用 HTTP POST 方法)提交回授权 服务器。授权服务器检查凭据,如果成功,则将 用户代理重定向到客户端的重定向端点。

在 [RFC6749] 中,HTTP 状态码 302(Found)用于此目的,但 “允许使用用户代理可用的任何其他方法完成此 重定向”。如果改用状态码 307 进行重定向,用户代理将通过 HTTP POST 将用户凭据发送给客户端。

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

这种行为可能出乎开发者的预期,但它在 第 15.4.8 节,见 [RFC9110] 中有明确定义。此状态 码(307)不要求用户 代理将 POST 请求改写为 GET 请求,因此也不会丢弃 POST 请求正文中的表单数据。

在 HTTP 标准 [RFC9110] 中,只有状态码 303 明确要求将 HTTP POST 请求改写为 HTTP GET 请求。 对于所有其他状态码,包括常见的 302,用户 代理可以选择不将 POST 请求改写为 GET 请求,从而 导致用户凭据泄露给客户端。(但是在实践中,大多数 用户代理只有在 307 重定向时才会表现出这种行为。)

因此,重定向可能包含 用户凭据的请求的授权服务器不得使用 HTTP 307 状态码进行重定向。如果对这类 请求使用 HTTP 重定向(而不是例如 JavaScript),授权服务器应该使用 HTTP 状态码 303 (See Other)。

4.13. TLS 终止 反向代理

HTTP 应用的一种常见部署架构是将 应用服务器隐藏在一个反向代理之后,由该代理终止 TLS 连接,并将传入请求分发到相应的 应用服务器节点。

本节重点介绍这种部署 架构中与 OAuth 相关的一些攻击面,并给出 安全控制方面的建议。

在某些情况下,反向代理需要将安全相关 数据传递给上游应用服务器以供进一步处理。 示例包括请求发起者的 IP 地址、token-binding ID 以及经过认证的 TLS 客户端证书。此类数据通常通过 添加到上游请求中的 HTTP 标头传递。虽然这些标头 通常是自定义的、特定于应用的标头,但 [RFC9440] 中定义了用于客户端证书和客户端证书链的标准化标头 字段。

如果反向代理直接传递任何来自 外部的标头,攻击者就可能尝试通过代理将伪造的标头 值直接发送给应用服务器,以 绕过安全控制。例如,反向代理通常会 接受 X-Forwarded-For 标头,并仅仅 添加传入请求的来源(形成一个列表)。根据 应用服务器中执行的逻辑,攻击者可以 简单地向标头中添加一个允许的 IP 地址,使保护措施失效。

因此,反向代理必须清理所有 传入请求,以确保 与应用服务器安全相关的所有标头值都具有真实性和完整性。

如果攻击者能够访问 代理与应用服务器之间的内部网络,那么攻击者还可能尝试 绕过已有安全控制。因此,必须 确保通信实体的真实性。此外, 反向代理与应用服务器之间的通信链路必须受到保护,以防止消息遭到窃听、注入和重放。

4.14. 刷新令牌 保护

刷新令牌是一种方便且对用户友好的获取新访问 令牌的方式。它们也有助于 提高 OAuth 的安全性,因为它们允许授权服务器签发 生命周期较短且范围缩小的访问令牌,从而降低 访问令牌泄露可能造成的影响。

4.14.1. 讨论

刷新令牌对攻击者很有吸引力,因为它们 代表授予 某个特定客户端的完整访问范围,并且没有进一步限制到某个特定 资源。 如果攻击者能够窃取并成功重放 刷新令牌,攻击者就能够生成访问令牌,并使用 它们代表资源所有者访问资源服务器。

[RFC6749] 已通过以下要求提供了稳健的基础保护:

  • 确保刷新令牌在 传输和存储期间的机密性;
  • 刷新令牌必须通过 授权服务器与客户端之间受 TLS 保护的连接传输;
  • 授权服务器维护并 检查刷新令牌 与某个客户端之间的绑定,并且如果可能,在刷新令牌时认证该客户端;以及
  • 刷新令牌无法被生成、 修改或猜测。

[RFC6749] 还通过定义相应的 错误码和响应行为,为进一步的 (特定于实现的)安全措施奠定了基础,例如刷新令牌过期和 撤销,以及刷新令牌轮换。

本规范给出了超出 [RFC6749] 范围的建议和澄清。

4.14.2. 建议

授权服务器必须根据风险评估确定 是否向某个客户端签发刷新令牌。如果 授权服务器决定不签发刷新令牌,则客户端可以通过使用其他许可类型(例如 授权码许可类型)获得新的访问令牌。在这种情况下,授权 服务器可以使用 Cookie 和持久许可来优化用户 体验。

如果签发刷新令牌,则这些刷新令牌必须绑定到 资源所有者同意的 scope 和资源服务器。 这样做是为了防止合法客户端提升权限,并降低 刷新令牌泄露的影响。

对于机密客户端,[RFC6749] 已经要求刷新 令牌只能由获得这些令牌的客户端使用。

授权服务器必须使用以下 方法之一来 检测恶意行为者针对公共客户端进行的刷新令牌重放:

  • 发送者约束刷新令牌:授权服务器 以加密方式将刷新令牌绑定到某个特定客户端 实例,例如使用 [RFC8705] 或 [RFC9449]。
  • 刷新令牌轮换: 授权服务器在每次访问令牌刷新响应中签发一个新的 刷新令牌。此前的刷新令牌会被使其失效,但有关二者 关系的信息仍由授权服务器保留。如果刷新 令牌遭到泄露,并随后被攻击者 和合法客户端同时使用,其中一方会提交一个已经失效的 刷新令牌,从而通知授权服务器发生了 泄露。授权服务器无法确定哪一方 提交了无效刷新令牌,但会撤销 当前有效的刷新令牌。这会阻止攻击,但代价是迫使 合法客户端重新获得新的授权许可。

    实现说明:刷新令牌所属的许可 可以编码到刷新令牌本身。这可以使 授权服务器高效确定某个刷新令牌所属的许可, 并进一步确定所有需要撤销的刷新令牌。 在这种情况下,授权服务器必须确保 刷新令牌值的完整性,例如 使用签名。

发生安全事件时,授权服务器可以自动撤销刷新 令牌,例如:

  • 密码更改;或者
  • 在授权服务器上注销。

如果客户端一段时间未活动,刷新令牌应该过期, 即该刷新令牌已有一段时间未用于获取新的访问 令牌。过期时间由 授权服务器自行决定。它可以是一个全局值,也可以根据 客户端策略或与刷新令牌关联的许可 (及其敏感程度)确定。

4.15. 客户端冒充 资源所有者

资源服务器可能依据获得访问令牌的 资源所有者的身份,或者依据 客户端凭据许可中的客户端身份作出访问控制决策。例如,[RFC9068](OAuth 2.0 访问令牌的 JSON Web Token(JWT)配置文件)描述了一种访问 令牌数据结构,其中包含一个 sub claim,其定义如下:

在通过涉及资源所有者的许可(例如授权码许可) 获得访问令牌的 情况下,“sub”的值应该对应资源 所有者的主体标识符。 在通过不涉及 资源所有者的许可(例如客户端凭据许可) 获得访问令牌的情况下,“sub”的值应该对应授权服务器用于指示 客户端应用的标识符。

如果两种情况都可能出现,资源服务器可能会将客户端的 身份 误认为资源所有者的身份。例如,如果客户端可以在向授权服务器注册时自行选择 client_id, 恶意客户端可能会将其设置为标识资源所有者的值(例如, 使用 OpenID Connect 时的 sub 值)。如果资源服务器无法正确 区分由资源 所有者参与而获得的访问令牌和没有资源所有者参与而获得的访问令牌,则客户端可能意外获得访问 资源所有者资源的能力。

这种攻击可能不仅影响使用 [RFC9068] 的实现, 还会影响类似的定制解决方案。

4.15.1. 对策

如果客户端 ID 和用户标识符共享一个命名空间, 例如上文 第 4.15 节所示的 [RFC9068] 中的 sub claim 示例, 授权服务器不应该允许 客户端影响其 client_id 或 任何其他可能导致其与真正资源所有者混淆的 claim。 如果无法避免这种情况,授权服务器必须 提供 其他方式,让资源服务器能够区分这两类 访问令牌。

4.16. 点击劫持

如 第 4.4.1.9 节,见 [RFC6819] 所述, 授权请求容易受到点击劫持攻击,也称为用户界面重绘攻击。在 此类攻击中,攻击者将授权端点用户界面嵌入 一个看似无害的上下文中。用户以为自己正在与该上下文交互, 例如点击按钮时,实际上却无意中与授权 端点用户界面交互。反过来也可以做到:用户 以为自己正在与授权端点交互,却可能无意中在覆盖于原始用户 界面之上的、由攻击者提供的输入字段中输入 密码。点击劫持攻击可以设计得让用户几乎无法 察觉,例如使用几乎不可见的 iframe 覆盖在 其他元素之上。

攻击者可以利用这种方式获取用户的认证 凭据, 更改授予客户端的访问范围,并可能访问 用户的资源。

授权服务器必须防止点击劫持 攻击。[RFC6819] 中描述了多种 对策,包括使用 X-Frame-Options HTTP 响应标头字段和防止框架嵌入的 JavaScript。除此之外,授权服务器还应该 使用内容安全策略(CSP)Level 2 [W3C.CSP-2] 或更高版本。

要使其生效,必须在授权端点以及 适用时用于认证用户和 授权客户端的其他端点(例如设备授权端点、登录 页面、错误页面等)上使用 CSP。这可防止支持 CSP 的用户代理 被未授权的 源嵌入框架。客户端可以允许被与其重定向 端点所使用源不同的其他源嵌入 框架。因此,授权服务器应该允许 管理员为特定客户端配置允许的源, 和/或允许客户端动态注册这些源。

使用 CSP 可以让授权服务器在 单个响应标头字段中指定多个源,并使用灵活的 模式限制这些源(详情参见 [W3C.CSP-2])。 CSP Level 2 通过同时使用 限制框架来源的策略(使用 frame-ancestors) 和限制允许在 HTML 页面上执行的脚本来源的策略 (使用 script-src),提供了一种 防止点击劫持的稳健机制。下面的清单展示了一个非规范性 策略示例:

HTTP/1.1 200 OK
Content-Security-Policy: frame-ancestors https://ext.example.org:8000
Content-Security-Policy: script-src 'self'
X-Frame-Options: ALLOW-FROM https://ext.example.org:8000
...

由于某些用户代理不支持 [W3C.CSP-2],除非授权服务器明确 不支持此类旧版用户代理,否则该技术 应该与其他技术结合使用,包括 [RFC6819] 中描述的技术。即使在这种情况下,仍应该采用额外 对策。

4.17. 针对浏览器内 通信流程的攻击

如果授权响应通过浏览器内通信 技术 (例如 postMessage [WHATWG.postmessage_api])而不是 HTTP 重定向发送,则消息可能 被无意发送到恶意源,或被恶意源注入。

4.17.1. 示例

以下使用浏览器内 通信进行攻击的非规范性伪代码示例在 [research.rub] 中有所描述。

4.17.1.1. 对接收方源限制不足

通过 postMessage 发送授权响应或令牌响应时,授权服务器将响应发送到通配符 源“*”,而不是客户端的源。如果响应发送到的窗口 受攻击者控制,则攻击者可以读取该 响应。

window.opener.postMessage(
  {
    code: "ABC",
    state: "123"
  },
  "*" // opener 窗口中的任何网站都可以接收该消息
)
4.17.1.2. URI 验证不足

通过 postMessage 发送授权响应或令牌响应时,授权服务器可能不会将 接收方源与重定向 URI 进行检查,而是例如将 响应发送给攻击者提供的源。这类似于 第 4.1 节中描述的攻击。

window.opener.postMessage(
  {
    code: "ABC",
    state: "123"
  },
  "https://attacker.example" // 攻击者提供的值
)
4.17.1.3. 发送方源验证不足后的 注入

期望通过 postMessage 接收授权响应或令牌响应的客户端可能不会验证消息的发送方源。这 可能允许攻击者向客户端注入授权响应或令牌响应。

在恶意注入授权 响应的情况下,该攻击 是 第 4.7 节所述 CSRF 攻击的一种变体。第 4.7 节中所述的 对策同样适用于此攻击。

在恶意注入令牌响应的情况下, 第 4.10.1 节所述的发送者约束 访问令牌在某些情况下可以防止攻击,但通常还需要 第 4.17.2 节中所述的额外 对策。

4.17.2. 建议

将客户端接收方源与预注册 源进行比较时, 授权服务器必须按照 第 4.1.3 节所述使用精确字符串匹配。 授权服务器必须将 postMessage 发送到 可信的客户端接收方源,如以下非规范性示例所示:

window.opener.postMessage(
  {
    code: "ABC",
    state: "123"
  },
  "https://client.example" // 使用明确的客户端源
)

postMessage 中不得使用类似“*”的通配符源, 因为攻击者可以利用它们 将受害者的浏览器内消息泄露到恶意源。 这两项措施都有助于防止授权码和 访问令牌泄露(参见 第 4.1 节)。

客户端必须防止 浏览器内消息被注入客户端 接收端点。客户端必须使用精确字符串匹配, 将浏览器内消息的发起方源与授权 服务器源进行比较,如以下非规范性示例所示:

window.addEventListener("message", (e) => {
  // 精确验证授权服务器源
  if (e.origin === "https://honest.as.example") {
    // 处理 e.data.code 和 e.data.state
  }
})

由于浏览器内通信流程只是采用了不同的 通信 技术(即使用 postMessage 而不是 HTTP 重定向),因此 第 2.1 节中列出的所有保护授权响应的措施必须 同样适用。

5. IANA 注意事项

本文档不要求 IANA 执行任何操作。

6. 安全考虑

安全考虑在第 2、3 和 4 节中描述。

7. 参考文献

7.1. 规范性参考文献

[BCP195]
最佳当前实践 195,<https://www.rfc-editor.org/info/bcp195>。
截至 撰写本文时,此 BCP 包含以下内容:
Moriarty, K. 和 S. Farrell,“弃用 TLS 1.0 和 TLS 1.1”,BCP 195,RFC 8996,DOI 10.17487/RFC8996,, <https://www.rfc-editor.org/info/rfc8996>。
Sheffer, Y.、Saint-Andre, P. 和 T. Fossati,“安全使用传输层安全性 (TLS)和数据报传输层安全性(DTLS)的建议”, BCP 195,RFC 9325,DOI 10.17487/RFC9325,,<https://www.rfc-editor.org/info/rfc9325>。
[RFC3986]
Berners-Lee, T.、Fielding, R. 和 L. Masinter,“统一资源标识符 (URI):通用语法”,STD 66,RFC 3986,DOI 10.17487/RFC3986,,<https://www.rfc-editor.org/info/rfc3986>。
[RFC6749]
Hardt, D.,编,“OAuth 2.0 授权 框架”,RFC 6749,DOI 10.17487/RFC6749,, <https://www.rfc-editor.org/info/rfc6749>。
[RFC6750]
Jones, M. 和 D. Hardt,“OAuth 2.0 授权框架:Bearer 令牌用法”,RFC 6750,DOI 10.17487/RFC6750,,<https://www.rfc-editor.org/info/rfc6750>。
[RFC6819]
Lodderstedt, T.,编,McGloin, M. 和 P. Hunt,“OAuth 2.0 威胁模型和安全 考虑”,RFC 6819,DOI 10.17487/RFC6819,, <https://www.rfc-editor.org/info/rfc6819>。
[RFC7521]
Campbell, B.、Mortimore, C.、Jones, M. 和 Y. Goland,“OAuth 2.0 客户端认证和授权 许可的断言框架”,RFC 7521,DOI 10.17487/RFC7521,,<https://www.rfc-editor.org/info/rfc7521>。
[RFC7523]
Jones, M.、Campbell, B. 和 C. Mortimore,“用于 OAuth 2.0 客户端认证和授权许可的 JSON Web Token(JWT)配置文件”,RFC 7523,DOI 10.17487/RFC7523,,<https://www.rfc-editor.org/info/rfc7523>。
[RFC8252]
Denniss, W. 和 J. Bradley,“用于原生应用的 OAuth 2.0”,BCP 212, RFC 8252,DOI 10.17487/RFC8252, ,<https://www.rfc-editor.org/info/rfc8252>。
[RFC8414]
Jones, M.、Sakimura, N. 和 J. Bradley,“OAuth 2.0 授权服务器 元数据”,RFC 8414,DOI 10.17487/RFC8414,,<https://www.rfc-editor.org/info/rfc8414>。
[RFC8705]
Campbell, B.、Bradley, J.、Sakimura, N. 和 T. Lodderstedt,“OAuth 2.0 双向 TLS 客户端认证和证书绑定访问 令牌”,RFC 8705,DOI 10.17487/RFC8705,, <https://www.rfc-editor.org/info/rfc8705>。
[RFC9068]
Bertocci, V.,“用于 OAuth 2.0 访问令牌的 JSON Web Token(JWT)配置文件”,RFC 9068,DOI 10.17487/RFC9068,,<https://www.rfc-editor.org/info/rfc9068>。

7.2. 资料性参考文献

[arXiv.1508.04324v2]
Mladenov, V.、Mainka, C. 和 J. Schwenk,“现代单点 登录协议的安全性:OpenID Connect 中的二阶漏洞”,arXiv:1508.04324v2,DOI 10.48550/arXiv.1508.04324,,<https://arxiv.org/abs/1508.04324v2/>。
[arXiv.1601.01229]
Fett, D.、Küsters, R. 和 G. Schmitz,“OAuth 2.0 的全面形式化安全 分析”,arXiv:1601.01229,DOI 10.48550/arXiv.1601.01229,,<https://arxiv.org/abs/1601.01229/>。
[arXiv.1704.08539]
Fett, D.、Küsters, R. 和 G. Schmitz,“Web SSO 标准 OpenID Connect:深入的形式化安全分析和安全指南”,arXiv:1704.08539,DOI 10.48550/arXiv.1704.08539,,<https://arxiv.org/abs/1704.08539/>。
[arXiv.1901.11520]
Fett, D.、Hosseyni, P. 和 R. Küsters,“OpenID 金融级 API 的广泛形式化安全 分析”,arXiv:1901.11520,DOI 10.48550/arXiv.1901.11520,,<https://arxiv.org/abs/1901.11520/>。
[bug.chromium]
“使用新 标签页打开链接时 Referer 标头包含 URL 片段”,Chromium 问题跟踪器,问题 ID:40076763, <https://issues.chromium.org/issues/40076763>。
[JWT-ENCODED-STATE]
Bradley, J.、Lodderstedt, T. 和 H. Zandbelt,“使用 JWT 在 OAuth 2 state 参数中编码 claim”,工作进行中,Internet-Draft,draft-bradley-oauth-jwt-encoded-state-09,,<https://datatracker.ietf.org/doc/html/draft-bradley-oauth-jwt-encoded-state-09>。
[OAUTH-V2.1]
Hardt, D.、Parecki, A. 和 T. Lodderstedt,“OAuth 2.1 授权 框架”,工作进行中,Internet-Draft,draft-ietf-oauth-v2-1-12,,<https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-12>。
[OAuth.Post]
Jones, M. 和 B. Campbell,“OAuth 2.0 表单提交响应模式”,OpenID 基金会,, <https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html>。
[OAuth.Responses]
de Medeiros, B.,编,Scurtescu, M.、 Tarjan, P. 和 M. Jones,“OAuth 2.0 多响应类型编码实践”,OpenID 基金会,,<https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html>。
[OpenID.Core]
Sakimura, N.、Bradley, J.、Jones, M.、de Medeiros, B. 和 C. Mortimore,“包含勘误集 2 的 OpenID Connect Core 1.0”,OpenID 基金会,,<https://openid.net/specs/openid-connect-core-1_0.html>。
[OpenID.Discovery]
Sakimura, N.、Bradley, J.、Jones, M. 和 E. Jay,“包含勘误集 2 的 OpenID Connect Discovery 1.0”, OpenID 基金会,,<https://openid.net/specs/openid-connect-discovery-1_0.html>。
[OpenID.JARM]
Lodderstedt, T. 和 B. Campbell,“金融级 API:用于 OAuth 2.0 的 JWT 安全授权响应模式 (JARM)”,OpenID 基金会,,<https://openid.net/specs/openid-financial-api-jarm.html>。
[owasp.redir]
OWASP 基金会,“未经验证的重定向和 转发速查表”,OWASP 速查表系列, <https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html>。
[research.cmu]
Chen, E.、Pei, Y.、Chen, S.、Tian, Y.、Kotcher, R. 和 P. Tague,“为移动应用开发者揭秘 OAuth”,CCS '14:2014 ACM SIGSAC 计算机和 通信安全会议论文集,第 892-903 页,DOI 10.1145/2660267.2660323,, <https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/OAuthDemystified.pdf>。
[research.jcs_14]
Bansal, C.、Bhargavan, K.、Delignat-Lavaud, A. 和 S. Maffeis, “通过形式化 分析发现网站授权中的具体攻击”,《计算机安全期刊》,第 22 卷,第 4 期,第 601-657 页,DOI 10.3233/JCS-140503,,<https://www.doc.ic.ac.uk/~maffeis/papers/jcs14.pdf>。
[research.rub]
Jannett, L.、Mladenov, V.、Mainka, C. 和 J. Schwenk,“DISTINCT:利用双窗口单点 登录中的浏览器内通信实施身份盗窃”,CCS '22:2022 ACM SIGSAC 计算机 和通信安全会议论文集,DOI 10.1145/3548606.3560692,,<https://dl.acm.org/doi/pdf/10.1145/3548606.3560692>。
[research.rub2]
Fries, C.,“实际 OpenID Connect 实现的安全分析”,硕士论文, 波鸿鲁尔大学(RUB),,<https://www.nds.rub.de/media/ei/arbeiten/2021/05/03/masterthesis.pdf>。
[research.ubc]
Sun, S.-T. 和 K. Beznosov,“魔鬼藏在(实现)细节中:OAuth SSO 系统的实证分析”,2012 ACM 计算机 和通信安全会议论文集(CCS '12),第 378-390 页,DOI 10.1145/2382196.2382238,, <https://css.csail.mit.edu/6.858/2012/readings/oauth-sso.pdf>。
[research.udel]
Liu, D.、Hao, S. 和 H. Wang,“你的所有 DNS 记录都指向我们: 理解悬空 DNS 记录的安全威胁”,CCS '16:2016 ACM SIGSAC 计算机和通信安全会议论文集,第 1414-1425 页,DOI 10.1145/2976749.2978387,,<https://dl.acm.org/doi/pdf/10.1145/2976749.2978387>。
[RFC2119]
Bradner, S.,“RFC 中用于 指示要求级别的关键词”,BCP 14,RFC 2119,DOI 10.17487/RFC2119,,<https://www.rfc-editor.org/info/rfc2119>。
[RFC7591]
Richer, J.,编,Jones, M.、Bradley, J.、Machulak, M. 和 P. Hunt,“OAuth 2.0 动态客户端注册 协议”,RFC 7591,DOI 10.17487/RFC7591,,<https://www.rfc-editor.org/info/rfc7591>。
[RFC7636]
Sakimura, N.,编,Bradley, J. 和 N. Agarwal,“OAuth 公共客户端的 代码交换证明密钥”,RFC 7636,DOI 10.17487/RFC7636,,<https://www.rfc-editor.org/info/rfc7636>。
[RFC8174]
Leiba, B.,“RFC 2119 关键词中大写与 小写的歧义”,BCP 14,RFC 8174,DOI 10.17487/RFC8174,,<https://www.rfc-editor.org/info/rfc8174>。
[RFC8707]
Campbell, B.、Bradley, J. 和 H. Tschofenig,“OAuth 2.0 的资源指示符”,RFC 8707,DOI 10.17487/RFC8707,, <https://www.rfc-editor.org/info/rfc8707>。
[RFC9101]
Sakimura, N.、Bradley, J. 和 M. Jones,“OAuth 2.0 授权 框架:JWT 安全授权请求(JAR)”,RFC 9101,DOI 10.17487/RFC9101,,<https://www.rfc-editor.org/info/rfc9101>。
[RFC9110]
Fielding, R.,编,Nottingham, M., 编 和 J. Reschke,编,“HTTP 语义”,STD 97,RFC 9110,DOI 10.17487/RFC9110,,<https://www.rfc-editor.org/info/rfc9110>。
[RFC9126]
Lodderstedt, T.、Campbell, B.、Sakimura, N.、Tonge, D. 和 F. Skokan,“OAuth 2.0 推送授权 请求”,RFC 9126,DOI 10.17487/RFC9126,, <https://www.rfc-editor.org/info/rfc9126>。
[RFC9207]
Meyer zu Selhausen, K. 和 D. Fett, “OAuth 2.0 授权服务器发行者标识”,RFC 9207,DOI 10.17487/RFC9207,,<https://www.rfc-editor.org/info/rfc9207>。
[RFC9396]
Lodderstedt, T.、Richer, J. 和 B. Campbell,“OAuth 2.0 富授权 请求”,RFC 9396,DOI 10.17487/RFC9396,,<https://www.rfc-editor.org/info/rfc9396>。
[RFC9440]
Campbell, B. 和 M. Bishop,“Client-Cert HTTP 标头字段”,RFC 9440,DOI 10.17487/RFC9440,,<https://www.rfc-editor.org/info/rfc9440>。
[RFC9449]
Fett, D.、Campbell, B.、Bradley, J.、Lodderstedt, T.、Jones, M. 和 D. Waite,“OAuth 2.0 持有证明演示(DPoP)”,RFC 9449,DOI 10.17487/RFC9449,,<https://www.rfc-editor.org/info/rfc9449>。
[TOKEN-BINDING]
Jones, M.、Campbell, B.、Bradley, J. 和 W. Denniss,“OAuth 2.0 令牌绑定”,工作进行中,Internet-Draft, draft-ietf-oauth-token-binding-08,,<https://datatracker.ietf.org/doc/html/draft-ietf-oauth-token-binding-08>。
[W3C.CSP-2]
West, M.、Barth, A. 和 D. Veditz,“内容安全策略 Level 2”,W3C 推荐标准,,<https://www.w3.org/TR/2016/REC-CSP2-20161215/>。 最新版本位于 <https://www.w3.org/TR/CSP2/>。
[W3C.webappsec-referrer-policy]
Eisinger, J. 和 E. Stark,“Referrer 策略”,,<https://www.w3.org/TR/2017/CR-referrer-policy-20170126/>。 最新版本位于 <https://www.w3.org/TR/referrer-policy/>。
[W3C.WebAuthn]
Hodges, J.、Jones, J.C.、Jones, M.B.、Kumar, A. 和 E. Lundberg,“Web 认证:用于 访问公钥凭据的 API Level 2”,W3C 推荐标准,, <https://www.w3.org/TR/2021/REC-webauthn-2-20210408/>。 最新版本位于 <https://www.w3.org/TR/webauthn-2/>。
[W3C.WebCrypto]
Watson, M.,编,“Web 加密 API”,W3C 推荐标准,,<https://www.w3.org/TR/2017/REC-WebCryptoAPI-20170126/>。 最新版本位于 <https://www.w3.org/TR/WebCryptoAPI/>。
[WHATWG.CORS]
WHATWG,“CORS 协议”,Fetch:现行标准,第 3.2 节,,<https://fetch.spec.whatwg.org/#http-cors-protocol>。
[WHATWG.postmessage_api]
WHATWG,“跨文档消息传递”, HTML:现行标准,第 9.3 节,,<https://html.spec.whatwg.org/multipage/web-messaging.html#web-messaging>。

致谢

我们感谢 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 提供的宝贵反馈。

作者地址

Torsten Lodderstedt
SPRIND
John Bradley
Yubico
Andrey Labunets
独立研究员
Daniel Fett
Authlete