常见OAuth漏洞
OAuth2的流行使其成为攻击者的主要目标。虽然它简化了用户登录流程,但其复杂性可能导致错误配置,从而产生安全漏洞。一些较为复杂的漏洞不断重现,因为协议的内部工作机制并不总是被充分理解。为了改变这一现状,我们决定编写一份关于OAuth实现已知攻击的全面指南。此外,我们还创建了一份全面的检查清单,对测试人员和开发人员快速评估其实现是否安全都将非常有用。
立即下载OAuth安全备忘单! Doyensec_OAuth_CheatSheet.pdf
OAuth简介
OAuth术语
OAuth是一个具有多个参与者和移动部件的复杂协议。在深入探讨其内部工作机制之前,让我们先回顾一下其术语:
- 资源所有者(Resource Owner):能够授予受保护资源访问权限的实体。通常是最终用户。
- 客户端(Client):代表资源所有者请求访问受保护资源的应用程序。
- 资源服务器(Resource Server):托管受保护资源的服务器。这是您想要访问的API。
- 授权服务器(Authorization Server):对资源所有者进行身份验证并在获得适当授权后颁发访问令牌的服务器。例如,Auth0。
- 用户代理(User Agent):资源所有者用来与客户端交互的代理(例如,浏览器或原生应用程序)。
参考资料
- OAuth 2.0 RFC: https://datatracker.ietf.org/doc/html/rfc6749
- OAuth术语: #oauth-2-0-terminology
OAuth常见流程
针对OAuth的攻击依赖于挑战授权流程所建立的各种假设。因此,理解这些流程对于有效攻击和防御OAuth实现至关重要。以下是最流行流程的高级描述。
隐式流程(Implicit Flow)
隐式流程最初设计用于无法安全存储客户端凭据的原生或单页应用程序。然而,现在不鼓励使用它,并且它未包含在OAuth 2.1规范中。尽管如此,它在Open ID Connect (OIDC) 中仍然是检索id_tokens的可行身份验证解决方案。
在此流程中,用户代理被重定向到授权服务器。在执行身份验证和同意后,授权服务器直接返回访问令牌,使其对资源所有者可访问。这种方法将访问令牌暴露给用户代理,可能通过XSS或存在缺陷的redirect_uri验证等漏洞而受到威胁。如果response_mode未设置为form_post,隐式流程会将访问令牌作为URL的一部分传输。
参考资料
- https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.2
- https://datatracker.ietf.org/doc/html/rfc6749#section-4.2
- https://auth0.com/docs/get-started/authentication-and-authorization-flow/implicit-flow-with-form-post
授权码流程(Authorization Code Flow)
授权码流程是Web应用程序中使用最广泛的OAuth流程之一。与直接向授权服务器请求访问令牌的隐式流程不同,授权码流程引入了一个中间步骤。在此过程中,用户代理首先检索授权码,然后应用程序将该授权码与客户端凭据一起交换为访问令牌。这个额外的步骤确保只有客户端应用程序能够访问访问令牌,防止用户代理看到它。
此流程仅适用于机密应用程序,例如常规Web应用程序,因为在代码交换请求中包含了应用程序客户端凭据,并且必须由客户端应用程序安全存储。
参考资料
- https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.1
- https://datatracker.ietf.org/doc/html/rfc6749#section-4.1
- https://cloudentity.com/developers/basics/oauth-grant-types/authorization-code-flow/
带PKCE的授权码流程(Authorization Code Flow with PKCE)
OAuth 2.0 提供了一个使用代码交换证明密钥(PKCE)的授权码流程版本。此OAuth流程最初设计用于无法存储客户端密钥的应用程序,例如原生或单页应用程序,但它已成为OAuth 2.1规范中的主要推荐。
在默认的授权码流程中增加了两个新参数:一个随机生成的值,称为code_verifier,及其转换版本code_challenge。
- 首先,客户端创建并记录一个秘密的code_verifier,并推导出一个转换版本t(code_verifier),称为code_challenge,该值连同所使用的转换方法t_m一起在授权请求中发送。
- 然后,客户端在访问令牌请求中发送授权码以及code_verifier秘密。
- 最后,授权服务器转换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的授权码流程:
[此处应有PKCE授权码流程图]
参考资料
客户端凭据流程(Client Credentials Flow)
客户端凭据流程设计用于机器对机器(M2M)应用程序,例如守护进程或后端服务。当客户端也是资源所有者时,它非常有用,无需用户代理身份验证。此流程允许客户端通过提供客户端凭据直接检索访问令牌。
下图说明了客户端凭据流程:
[此处应有客户端凭据流程图]
参考资料
- https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.4
- https://datatracker.ietf.org/doc/html/rfc6749#section-4.4
设备授权流程(Device Authorization Flow)
设备授权流程设计用于连接互联网但要么缺乏用于基于用户代理授权的浏览器,要么在授权流程期间输入受限而无法进行基于文本的身份验证的设备。
此流程允许设备(如智能电视、媒体控制台、数码相框或打印机)上的OAuth客户端使用单独设备上的用户代理来获取用户授权以访问受保护资源。
在此流程中,首先客户端应用程序从授权服务器检索用户代码(User Code)和验证URL(Verification URL)。然后,它指示用户代理使用提供的用户代码和验证URL在另一台设备上进行身份验证和同意。
下图说明了设备授权码流程:
[此处应有设备授权流程图]
参考资料
- https://datatracker.ietf.org/doc/html/rfc8628
- https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-flow
资源所有者密码凭据流程(Resource Owner Password Credentials Flow)
此流程要求资源所有者完全信任客户端,将其凭据提供给授权服务器。它设计用于无法使用基于重定向的流程的用例,尽管在最近的OAuth 2.1 RFC规范中已被删除,并且不推荐使用。
不是将资源所有者重定向到授权服务器,而是将用户凭据发送到客户端应用程序,然后客户端应用程序将其转发到授权服务器。
下图说明了资源所有者密码凭据流程:
[此处应有资源所有者密码凭据流程图]
参考资料
- https://datatracker.ietf.org/doc/html/rfc6749#section-4.3
- https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#name-differences-from-oauth-20
攻击
在本节中,我们将介绍针对OAuth的常见攻击以及基本的修复策略。
CSRF
OAuth CSRF是一种针对OAuth流程的攻击,其中使用授权代码的浏览器与启动流程的浏览器不同。攻击者可以利用此攻击诱使受害者使用其授权代码,导致受害者连接到攻击者的授权上下文。
考虑下图:
[此处应有CSRF攻击示意图]
根据应用程序的上下文,影响可能从低到高不等。在任何情况下,确保用户控制其操作的授权上下文并且不能被强制进入另一个上下文至关重要。
缓解措施
OAuth规范建议使用state参数来防止CSRF攻击。 [state是] 客户端用于在请求和回调之间保持状态的不透明值。授权服务器在将用户代理重定向回客户端时包含此值。该参数应用于防止跨站请求伪造(CSRF)。
以下方案说明了state参数如何防止攻击:
[此处应有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登录”功能并使用email字段来识别用户时,就会出现这种情况。在这种情况下,攻击者可能在自己的Azure上创建AD组织(本例中为doyensectestorg),然后可用于执行“使用Microsoft登录”。虽然对于给定用户,放置在sub中的对象ID字段是不可变的且无法伪造,但email字段完全由用户控制且不需要任何验证。
[此处应有可变声明攻击示例截图]
在上面的屏幕截图中,有一个创建的用户示例,可用于接管客户端中victim@gmail.com的账户,该客户端使用email字段进行用户识别。
参考资料
- https://learn.microsoft.com/en-us/entra/identity-platform/claims-validation#validate-the-subject
- https://www.descope.com/blog/post/noauth
客户端混淆攻击(Client Confusion Attack)
当应用程序实现OAuth隐式流程进行身份验证时,它们应验证最终提供的令牌是为该特定客户端ID生成的。如果未执行此检查,攻击者可能使用为不同客户端ID生成的访问令牌。
想象攻击者创建一个公共网站,允许用户使用Google的OAuth隐式流程登录。假设有数千人连接到该托管网站,攻击者将可以访问他们为攻击者网站生成的Google OAuth访问令牌。
如果这些用户中的任何一个在未验证访问令牌的易受攻击网站上已有账户,攻击者将能够提供为不同客户端ID生成的受害者访问令牌,并能够接管受害者的账户。
用于身份验证的安全OAuth隐式流程实现如下:
[此处应有安全隐式流程图]
如果未执行步骤8到10并且未验证令牌的客户端ID,则可能执行以下攻击:
[此处应有客户端混淆攻击示意图]
修复措施
值得注意的是,即使客户端使用更安全的流程(例如,显式流程),它也可能接受访问令牌 - 实际上允许降级到隐式流程。此外,如果应用程序将访问令牌用作会话cookie或授权头,则可能容易受到攻击。实际上,确保访问令牌永远不会从用户控制的参数中接受,可以尽早打破利用链。除此之外,我们建议按照上述步骤8到10执行令牌验证。
参考资料
范围升级攻击(Scope Upgrade Attack)
使用授权码授予类型,用户数据通过安全的服务器到服务器通信进行请求和发送。
如果授权服务器接受并隐式信任在访问令牌请求中发送的scope参数(注意,此参数在授权码流程的访问令牌请求的RFC中未指定),恶意应用程序可能尝试通过在访问令牌请求中发送更高特权的作用域来升级从用户回调检索到的授权码的范围。
一旦生成访问令牌,资源服务器必须验证每个请求的访问令牌。此验证取决于访问令牌格式,常用的有以下几种:
- JWT访问令牌:对于这种访问令牌,资源服务器只需要检查JWT签名,然后检索JWT中包含的数据(client_id、scope等)。
- 随机字符串访问令牌:由于这种令牌不包含任何附加信息,资源服务器需要从授权服务器检索令牌信息。
[此处应有范围升级攻击示意图]
缓解措施
遵循RFC指南,在授权码流程的访问令牌请求中不应发送scope参数,尽管它可以在其他流程(如资源所有者密码凭据授予)中指定。
授权服务器应忽略scope参数或验证其与授权请求中先前提供的作用域匹配。
参考资料
重定向方案劫持(Redirect Scheme Hijacking)
当需要在移动设备上使用OAuth时,移动应用程序承担OAuth用户代理的角色。为了使它们能够接收带有授权代码的重定向,开发人员通常依赖自定义方案的机制。但是,多个应用程序可以在给定设备上注册给定方案。这打破了OAuth关于客户端是唯一控制配置的redirect_uri的假设,并且如果受害者的设备中安装了恶意应用程序,可能导致授权代码被接管。
Android Intent URI具有以下结构:
<scheme>://<host>:<port>[<path>|<pathPrefix>|<pathPattern>|<pathAdvancedPattern>|<pathSuffix>]
例如,以下URI com.example.app://oauth
描述了一个具有scheme=com.example.app和host=oauth的Intent。为了接收这些Intent,Android应用程序需要导出一个类似于以下内容的Activity:
|
|
Android系统在定义Intent过滤器时相当宽松。过滤器细节越少,捕获的范围越广,可能捕获的URI越多。因此,例如,如果只提供了方案,则无论其主机、路径等如何,所有针对此方案的Intent都将被捕获。
如果有多个应用程序可能捕获给定的Intent,系统将让用户决定使用哪一个,这意味着重定向接管需要用户交互。但是,根据合法应用程序的过滤器创建方式,可以尝试创建绕过方法。矛盾的是,原始开发人员越具体,就越容易制作绕过方法并在没有用户交互的情况下接管重定向。详细来说,Ostorlab创建了以下流程图来快速评估是否可能:
[此处应有重定向方案劫持评估流程图]
建议
对于显式授权码流程不可行的情况(因为客户端不能被信任来安全存储客户端密钥),创建了带有代码交换证明密钥(PKCE)的授权码流程。我们建议使用此流程来授权移动应用程序。
此外,为了恢复授权服务器和redirect_uri目标之间的信任关系,建议使用Android的可验证链接(Verifiable Links)和iOS的关联域(Associated Domains)机制。
简而言之,Android宣布了Intent过滤器的autoVerify属性。详细来说,开发人员可以创建类似于以下内容的Intent过滤器:
|
|
当以上述方式定义Intent过滤器时,Android系统验证定义的主机是否实际由应用程序的创建者拥有。详细来说,主机需要发布一个/.well-known/assetlinks.json
文件到关联域,列出给定的APK,以便允许其处理给定的链接:
|
|
由于这种设计,恶意应用程序无法为已声明的主机注册自己的Intent过滤器,尽管这仅在被处理的方案不是自定义方案时才有效。例如,如果应用程序处理com.example.app://
方案,则无法给予额外的优先级,用户将必须在实现该特定方案处理程序的应用程序之间进行选择。
参考资料
- https://developer.android.com/guide/topics/manifest/data-element
- https://developer.android.com/training/app-links/verify-android-applinks
- https://developer.apple.com/documentation/xcode/supporting-associated-domains
- https://blog.ostorlab.co/one-scheme-to-rule-them-all.html
- https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-9.8-8
总结
本文提供了OAuth协议的攻击和防御的全面列表。与文章本身一起,我们发布了一份面向开发人员和测试人员的全面备忘单。
下载OAuth安全备忘单: Doyensec_OAuth_CheatSheet.pdf
由于该领域经常有新的研究和发展,我们不声称完全了解所有细节。如果您对如何改进此摘要有任何建议,请随时联系作者。我们很乐意更新此博客文章,使其成为任何对该主题感兴趣的人的全面资源。