深入解析OAuth常见安全漏洞与防御策略

本文详细剖析了OAuth 2.0协议中常见的几种安全漏洞,包括CSRF、重定向攻击、可变更声明攻击、客户端混淆攻击、权限提升攻击和重定向方案劫持,并提供了相应的缓解措施和防御建议,旨在帮助开发者和测试人员构建更安全的OAuth实现。

Common OAuth Vulnerabilities

30 Jan 2025 - Posted by Jose Catalan, Szymon Drosdzol

OAuth2的流行使其成为攻击者的主要目标。虽然它简化了用户登录,但其复杂性可能导致配置错误,从而产生安全漏洞。一些更复杂的漏洞不断重现,因为协议的内部运作机制并不总是被充分理解。为了改变这种状况,我们决定撰写一份关于已知OAuth实现攻击的全面指南。此外,我们还创建了一份全面的检查清单。对于测试人员和开发人员来说,这应该有助于快速评估他们的实现是否安全。

立即下载OAuth安全速查表! Doyensec_OAuth_CheatSheet.pdf。

OAuth 简介

OAuth 术语

OAuth是一个具有多个参与方和活动部件的复杂协议。在我们深入其内部运作之前,先回顾一下其术语:

  • 资源所有者 (Resource Owner): 可以授予对受保护资源访问权限的实体。通常是最终用户。
  • 客户端 (Client): 代表资源所有者请求访问受保护资源的应用程序。
  • 资源服务器 (Resource Server): 托管受保护资源的服务器。这是您想要访问的API。
  • 授权服务器 (Authorization Server): 对资源所有者进行身份验证并在获得适当授权后颁发访问令牌的服务器。例如,Auth0。
  • 用户代理 (User Agent): 资源所有者用来与客户端交互的代理(例如,浏览器或本机应用程序)。

参考文献

OAuth 常见流程

针对OAuth的攻击依赖于挑战授权流程所建立的各种假设。因此,理解这些流程对于有效地攻击和防御OAuth实现至关重要。以下是最流行流程的高级描述。

隐式流程 (Implicit Flow)

隐式流程最初是为无法安全存储客户端凭据的本机或单页应用程序设计的。然而,现在不鼓励使用它,并且它没有包含在OAuth 2.1规范中。尽管如此,在Open ID Connect (OIDC)中,它仍然是检索id_tokens的可行身份验证方案。

在此流程中,用户代理被重定向到授权服务器。在执行身份验证和同意授权后,授权服务器直接返回访问令牌,使其对资源所有者可访问。这种方法将访问令牌暴露给用户代理,可能通过XSS或存在缺陷的redirect_uri验证等漏洞受到损害。如果response_mode未设置为form_post,隐式流程会将访问令牌作为URL的一部分传输。

参考文献

授权码流程 (Authorization Code Flow)

授权码流程是Web应用程序中使用最广泛的OAuth流程之一。与直接向授权服务器请求访问令牌的隐式流程不同,授权码流程引入了一个中间步骤。在此过程中,用户代理首先检索一个授权码,然后应用程序将该授权码与客户端凭据一起交换为访问令牌。这个额外的步骤确保只有客户端应用程序能够访问访问令牌,防止用户代理看到它。

此流程仅适用于机密应用程序(例如常规Web应用程序),因为应用程序客户端凭据包含在代码交换请求中,并且必须由客户端应用程序安全存储。

参考文献

带PKCE的授权码流程 (Authorization Code Flow with PKCE)

OAuth 2.0 提供了一种使用代码交换证明密钥 (PKCE) 的授权码流程版本。此OAuth流程最初是为无法存储客户端密钥的应用程序(例如本机或单页应用程序)设计的,但它已成为OAuth 2.1规范中的主要推荐方案。

在默认的授权码流程中添加了两个新参数:一个随机生成的值,称为code_verifier,及其转换后的版本code_challenge

  1. 首先,客户端创建并记录一个秘密的code_verifier,并派生出一个转换后的版本t(code_verifier),称为code_challenge,该值随转换方法t_m一起在授权请求中发送。
  2. 然后,客户端在访问令牌请求中发送授权码以及code_verifier秘密。
  3. 最后,授权服务器转换code_verifier并将其与t(code_verifier)进行比较。

可用的转换方法 (t_m) 如下:

  • plain: code_challenge = code_verifier
  • S256: code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))

请注意,将默认的授权码流程与自定义的redirect_uri方案(如example.app://)一起使用,可能允许恶意应用程序与合法的OAuth 2.0应用程序一起将自身注册为此自定义方案的处理程序。如果发生这种情况,恶意应用程序可以拦截授权码并将其交换为访问令牌。更多详细信息,请参阅 OAuth 重定向方案劫持

使用PKCE,拦截授权响应将不允许先前的攻击场景,因为攻击者只能访问authorization_code,但他们无法获得访问令牌请求中所需的code_verifier值。

下图说明了带PKCE的授权码流程:

参考文献

客户端凭据流程 (Client Credentials Flow)

客户端凭据流程专为机器对机器 (M2M) 应用程序(如守护程序或后端服务)设计。当客户端本身也是资源所有者时,它非常有用,无需用户代理进行身份验证。此流程允许客户端通过提供客户端凭据直接检索访问令牌。

下图说明了客户端凭据流程:

参考文献

设备授权流程 (Device Authorization Flow)

设备授权流程专为互联网连接的设备设计,这些设备要么缺乏用于用户代理授权的浏览器,要么输入受限,在授权流程期间无法进行基于文本的身份验证。

此流程允许设备(如智能电视、媒体控制台、数码相框或打印机)上的OAuth客户端使用单独设备上的用户代理来获取用户授权,以访问受保护资源。

在此流程中,首先客户端应用程序从授权服务器检索用户码和验证URL。然后,它指示用户代理使用提供的用户码和验证URL在另一台设备上进行身份验证和同意授权。

下图说明了设备授权码流程:

参考文献

资源所有者密码凭据流程 (Resource Owner Password Credentials Flow)

此流程要求资源所有者完全信任客户端,将他们的凭据提供给授权服务器。它设计用于无法使用基于重定向的流程的用例,尽管在最近的OAuth 2.1 RFC规范中已被删除,不推荐使用。

用户不是被重定向到授权服务器,而是将用户凭据发送到客户端应用程序,然后客户端应用程序将其转发给授权服务器。

下图说明了资源所有者密码凭据流程:

参考文献

攻击

在本节中,我们将介绍针对OAuth的常见攻击及其基本缓解策略。

CSRF

OAuth CSRF是一种针对OAuth流程的攻击,其中消耗授权码的浏览器与发起流程的浏览器不同。攻击者可以利用此漏洞迫使受害者消耗其授权码,导致受害者连接到攻击者的授权上下文。

考虑下图:

根据应用程序的上下文,影响可能从低到高不等。无论如何,至关重要的是确保用户能够控制他们所处的授权上下文,并且不能被胁迫进入另一个上下文。

缓解措施 OAuth规范建议使用state参数来防止CSRF攻击。

[state是] 客户端用于在请求和回调之间维护状态的不透明值。授权服务器在将用户代理重定向回客户端时包含此值。该参数应用于防止跨站请求伪造 (CSRF)。

下图说明了state参数如何防止攻击:

参考文献

  • Justin Richer, Antonio Sanso, (2017), OAuth 2 In Action

重定向攻击 (Redirect Attacks)

实现良好的授权服务器在将用户代理重定向回客户端之前会验证redirect_uri参数。redirect_uri值的允许列表应按客户端配置。这样的设计确保用户代理只能被重定向到客户端,并且授权码只会泄露给给定的客户端。相反,如果授权服务器忽略或错误地实现了此验证,恶意行为者可以操纵受害者完成一个流程,从而将其授权码泄露给不受信任的一方。

在最简单的形式中,当完全缺少redirect_uri验证时,可以利用以下流程进行说明:

当验证实施不当时,此漏洞也可能出现。唯一正确的方法是精确比较redirect_uri,包括来源(方案、主机名、端口)和路径。

常见的错误包括:

  • 仅验证来源/域名
  • 允许子域名
  • 允许子路径
  • 允许通配符

如果给定的来源包含具有开放重定向漏洞的URL,或包含用户控制内容的页面,则可能被滥用以通过Referer标头或开放重定向窃取代码。

另一方面,以下疏忽:

  • 部分路径匹配
  • 误用正则表达式匹配URI

可能导致通过精心制作的恶意URL产生各种绕过,这些URL将导致不受信任的来源。

参考文献

  • Justin Richer, Antonio Sanso, (2017), OAuth 2 In Action

可变声明攻击 (Mutable Claims Attack)

根据OAuth规范,用户由sub字段唯一标识。然而,此字段没有标准格式。因此,根据授权服务器的不同,会使用许多不同的格式。一些客户端应用程序为了在多个授权服务器之间构建统一的用户识别方式,转而使用用户句柄或电子邮件。然而,根据所使用的授权服务器,这种方法可能是危险的。一些授权服务器不保证此类用户属性的不变性。更糟糕的是,在某些情况下,这些属性可以由用户自己任意更改。在这种情况下,可能发生账户接管。

当实现"使用Microsoft登录"功能并使用电子邮件字段来识别用户时,就会出现这种情况。在这种情况下,攻击者可以在Azure上创建自己的AD组织(本例中为doyensectestorg),然后用于执行"使用Microsoft登录"。虽然放在sub中的对象ID字段对于给定用户是不可变的且无法伪造,但电子邮件字段完全由用户控制且不需要任何验证。

在上面的屏幕截图中,有一个示例用户被创建,该用户可用于接管客户端中使用电子邮件字段进行用户识别的victim@gmail.com账户。

参考文献

客户端混淆攻击 (Client Confusion Attack)

当应用程序实现OAuth隐式流程进行身份验证时,它们应验证最终提供的令牌是否是为该特定客户端ID生成的。如果未执行此检查,攻击者将可能使用为不同客户端ID生成的访问令牌。

想象攻击者创建一个公共网站,允许用户使用Google的OAuth隐式流程登录。假设有数千人连接到该托管网站,攻击者将可以访问他们为攻击者网站生成的Google OAuth访问令牌。

如果这些用户中任何一个在未验证访问令牌的易受攻击网站上已有账户,攻击者将能够提供为不同客户端ID生成的受害者访问令牌,并能够接管受害者的账户。

用于身份验证的安全OAuth隐式流程实现应如下所示:

如果未执行步骤8到10并且未验证令牌的客户端ID,则可能执行以下攻击:

缓解措施 值得注意的是,即使客户端使用更安全的流程(例如,显式流程),它也可能接受访问令牌——实际上允许降级到隐式流程。此外,如果应用程序将访问令牌用作会话cookie或授权标头,则可能容易受到攻击。在实践中,确保访问令牌永远不会从用户控制的参数中获取可以提前打破利用链。除此之外,我们建议按照上述步骤8到10中所述执行令牌验证。

参考文献

权限提升攻击 (Scope Upgrade Attack)

使用授权码授权类型时,用户数据通过安全的服务器到服务器通信进行请求和发送。

如果授权服务器接受并隐式信任访问令牌请求中发送的scope参数(注意,在授权码流程的访问令牌请求的RFC中未指定此参数),恶意应用程序可能会尝试通过发送具有更高权限的scope来升级从用户回调中检索到的授权码的权限。

一旦生成访问令牌,资源服务器必须验证每个请求的访问令牌。此验证取决于访问令牌的格式,常用的有以下几种:

  • JWT 访问令牌: 使用这种访问令牌,资源服务器只需要检查JWT签名,然后检索JWT中包含的数据(client_idscope等)。
  • 随机字符串访问令牌: 由于这种令牌不包含任何附加信息,资源服务器需要从授权服务器检索令牌信息。

缓解措施 遵循RFC指南,scope参数不应在授权码流程的访问令牌请求中发送,尽管它可以在其他流程(如资源所有者密码凭据授权)中指定。

授权服务器应忽略scope参数,或验证其是否与先前授权请求中提供的scope匹配。

参考文献

重定向方案劫持 (Redirect Scheme Hijacking)

当需要在移动设备上使用OAuth时,移动应用程序承担OAuth用户代理的角色。为了使它们能够接收带有授权码的重定向,开发人员通常依赖于自定义方案的机制。然而,多个应用程序可以在给定设备上注册给定的方案。这打破了OAuth关于客户端是唯一控制已配置redirect_uri的假设,并且如果受害者的设备中安装了恶意应用程序,可能导致授权码被接管。

Android Intent URI具有以下结构:

1
<scheme>://<host>:<port>[<path>|<pathPrefix>|<pathPattern>|<pathAdvancedPattern>|<pathSuffix>]

例如,URI com.example.app://oauth 描述了一个scheme=com.example.apphost=oauth的Intent。为了接收这些Intent,Android应用程序需要导出一个类似以下的活动:

1
2
3
4
5
6
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:host="oauth" android:scheme="=com.example.app"/>
    </intent-filter>

Android系统在定义Intent过滤器时相当宽松。过滤器细节越少,覆盖范围越广,可能捕获的URI就越多。因此,例如,如果只提供了scheme,则该scheme的所有Intent都将被捕获,无论其hostpath等如何。

如果有多个应用程序可能捕获给定的Intent,系统将让用户决定使用哪一个,这意味着重定向接管需要用户交互。然而,根据上述知识,可以根据合法应用程序的过滤器创建方式尝试创建绕过。矛盾的是,原始开发人员定义得越具体,就越容易制作绕过并在无需用户交互的情况下接管重定向。具体来说,Ostorlab创建了以下流程图来快速评估是否可能:

建议 对于无法使用显式授权码流程的情况(因为客户端无法被信任以安全存储客户端密钥),创建了带代码交换证明密钥 (PKCE) 的授权码流程。我们建议使用此流程来授权移动应用程序。

此外,为了恢复授权服务器与redirect_uri目标之间的信任关系,建议使用Android的可验证链接 (Verifiable Links) 和iOS的关联域 (Associated Domains) 机制。

简而言之,Android为Intent过滤器宣布了autoVerify属性。具体来说,开发人员可以创建类似于以下的Intent过滤器:

1
2
3
4
5
6
7
8
<intent-filter android:autoVerify="true">
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <category android:name="android.intent.category.BROWSABLE" />
  <data android:scheme="http" />
  <data android:scheme="https" />
  <data android:host="www.example.com" />
</intent-filter>

当以上述方式定义Intent过滤器时,Android系统会验证定义的主机是否实际上由应用程序的创建者拥有。具体来说,主机需要发布一个/.well-known/assetlinks.json文件到关联域,列出给定的APK,以便允许其处理给定的链接:

1
2
3
4
5
6
7
8
9
[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example",
    "sha256_cert_fingerprints":
    ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
  }
}]

得益于这种设计,恶意应用程序无法为已声明的主机注册自己的Intent过滤器,尽管这仅在被处理的方案不是自定义方案时才有效。例如,如果应用程序处理com.example.app://方案,则无法给予额外的优先级,用户将必须在实现该特定方案处理程序的应用程序之间进行选择。

参考文献

总结

本文全面列出了针对OAuth协议的攻击和防御方法。随本博客文章一起,我们发布了一份面向开发人员和测试人员的全面速查表。

下载OAuth安全速查表: Doyensec_OAuth_CheatSheet.pdf。

由于该领域经常出现新的研究和发展,我们并不声称对所有细节都了如指掌。如果您对如何改进此总结有建议,请随时联系作者。我们很乐意更新这篇博客文章,使其成为任何对此主题感兴趣的人的全面资源。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计