常见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的授权码流程:
[流程图]
参考文献
客户端凭据流程(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客户端使用单独设备上的用户代理获取用户授权以访问受保护资源。
在此流程中,首先客户端应用程序从授权服务器检索用户代码和验证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流程的攻击,其中使用授权码的浏览器与启动流程的浏览器不同。攻击者可以利用此攻击强迫受害者使用其授权码,导致受害者连接到攻击者的授权上下文。
考虑以下图表:
[图表]
根据应用程序的上下文,影响可能从低到高不等。无论如何,确保用户控制其操作的授权上下文并且不能被强迫进入另一个上下文至关重要。
缓解措施
OAuth规范建议使用state参数来防止CSRF攻击。
[state是]客户端用于在请求和回调之间维护状态的不透明值。授权服务器在将用户代理重定向回客户端时包含此值。该参数应用于防止跨站请求伪造(CSRF)。
以下方案说明了state参数如何防止攻击:
[方案]
参考文献
- Justin Richer, Antonio Sanso, (2017), OAuth 2 In Action
重定向攻击
良好实现的授权服务器在将用户代理重定向回客户端之前验证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的账户,该客户端使用电子邮件字段进行用户标识。
参考文献
- 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。为了接收这些Intents,Android应用程序需要导出一个类似于以下的活动:
|
|
Android系统在定义Intent过滤器时相当宽松。过滤器细节越少,网越宽,捕获的潜在URI越多。因此,例如,如果仅提供方案,则所有此方案的Intents都将被捕获,无论其主机、路径等如何。
如果有多个应用程序可能捕获给定Intent,系统将让用户决定使用哪个,这意味着重定向接管需要用户交互。但是,根据上述知识,可以尝试创建绕过,具体取决于合法应用程序的过滤器创建方式。矛盾的是,原始开发人员越具体,就越容易制作绕过并在没有用户交互的情况下接管重定向。详细来说,Ostorlab创建了以下流程图以快速评估是否可能:
[流程图]
建议
对于显式授权码流程不可行的情况,因为客户端不能被信任安全存储客户端密钥,创建了带代码交换证明密钥(PKCE)的授权码流程。我们建议使用此流程授权移动应用程序。
此外,为了恢复授权服务器和redirect_uri目标之间的信任关系,建议使用Android的可验证链接和iOS的关联域机制。
简而言之,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
由于该领域经常有新的研究和发展,我们不声称完全了解所有复杂性。如果您有改进此摘要的建议,请随时联系作者。我们很乐意更新此博客文章,以便它可以被视为对该主题感兴趣的