Common OAuth Vulnerabilities
30 Jan 2025 - Posted by Jose Catalan, Szymon Drosdzol
OAuth2的流行使其成为攻击者的主要目标。虽然它简化了用户登录,但其复杂性可能导致配置错误,从而产生安全漏洞。一些更复杂的漏洞不断重现,因为协议的内部工作原理并不总是被很好理解。为了改变这种状况,我们决定编写一份关于OAuth实现已知攻击的综合指南。此外,我们还创建了一份全面的检查清单。这对于测试人员和开发人员快速评估其实现是否安全应该很有用。
立即下载OAuth安全速查表! Doyensec_OAuth_CheatSheet.pdf
OAuth简介
OAuth术语
OAuth是一个具有许多参与者和移动部件的复杂协议。在我们深入探讨其内部工作原理之前,让我们先回顾一下其术语:
- 资源所有者:可以授予受保护资源访问权限的实体。通常是最终用户。
- 客户端:代表资源所有者请求访问受保护资源的应用程序。
- 资源服务器:托管受保护资源的服务器。这是您想要访问的API。
- 授权服务器:对资源所有者进行身份验证并在获得适当授权后颁发访问令牌的服务器。例如,Auth0。
- 用户代理:资源所有者用来与客户端交互的代理(例如,浏览器或本机应用程序)。
参考资料
- OAuth 2.0 RFC:https://datatracker.ietf.org/doc/html/rfc6749
- OAuth术语:#oauth-2-0-terminology
OAuth常见流程
针对OAuth的攻击依赖于挑战授权流程所建立的各种假设。因此,理解这些流程对于有效攻击和防御OAuth实现至关重要。以下是最流行流程的高级描述。
隐式流程
隐式流程最初是为无法安全存储客户端凭据的本机或单页应用程序设计的。然而,现在不鼓励使用它,并且未包含在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
授权码流程
授权码流程是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的授权码流程
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的授权码流程:
参考资料
客户端凭据流程
客户端凭据流程专为机器到机器 (M2M) 应用程序设计,例如守护程序或后端服务。当客户端也是资源所有者时,它非常有用,无需用户代理身份验证。此流程允许客户端通过提供客户端凭据直接检索访问令牌。
下图说明了客户端凭据流程:
参考资料
- https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.4
- https://datatracker.ietf.org/doc/html/rfc6749#section-4.4
设备授权流程
设备授权流程专为连接互联网但要么缺乏用于基于用户代理的授权的浏览器,要么输入受限以致在授权流程期间无法进行基于文本的身份验证的设备而设计。
此流程允许设备(如智能电视、媒体控制台、数码相框或打印机)上的OAuth客户端使用单独设备上的用户代理来获取用户授权以访问受保护资源。
在此流程中,首先客户端应用程序从授权服务器检索用户代码和验证URL。然后,它指示用户代理使用提供的用户代码和验证URL在不同设备上进行身份验证和同意。
下图说明了设备授权码流程:
参考资料
- https://datatracker.ietf.org/doc/html/rfc8628
- https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-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
可变声明攻击
根据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
客户端混淆攻击
当应用程序为身份验证实现OAuth隐式流程时,它们应验证最终提供的令牌是否为该特定客户端ID生成的。如果未执行此检查,攻击者可能会使用为不同客户端ID生成的访问令牌。
想象攻击者创建一个公共网站,允许用户使用Google的OAuth隐式流程登录。假设有数千人连接到托管的网站,攻击者将可以访问他们为攻击者网站生成的Google的OAuth访问令牌。
如果这些用户中的任何一个在未验证访问令牌的易受攻击的网站上已有账户,攻击者将能够提供为不同客户端ID生成的受害者访问令牌,并能够接管受害者的账户。
为身份验证实现的安全OAuth隐式流程应如下所示:
如果未执行步骤8到10并且未验证令牌的客户端ID,则可能执行以下攻击:
修复
值得注意的是,即使客户端使用更安全的流程(例如显式流程),它也可能接受访问令牌 - 实际上允许降级到隐式流程。此外,如果应用程序使用访问令牌作为会话cookie或授权头,则可能容易受到攻击。实际上,确保访问令牌永远不会从用户控制的参数中接受会尽早打破利用链。除此之外,我们建议按照上述步骤8到10执行令牌验证。
参考资料
范围升级攻击
使用授权码授予类型,用户数据通过安全的服务器到服务器通信进行请求和发送。
如果授权服务器接受并隐式信任在访问令牌请求中发送的scope参数(注意,此参数未在RFC中为授权码流程中的访问令牌请求指定),恶意应用程序可能尝试通过在访问令牌请求中发送更高权限的范围来升级从用户回调检索的授权代码的范围。
一旦生成访问令牌,资源服务器必须验证每个请求的访问令牌。此验证取决于访问令牌格式,常用的有以下几种:
- JWT访问令牌:对于这种访问令牌,资源服务器只需要检查JWT签名,然后检索JWT中包含的数据(client_id、scope等)
- 随机字符串访问令牌:由于这种令牌不包含任何附加信息,资源服务器需要从授权服务器检索令牌信息。
缓解措施
遵循RFC指南,scope参数不应在授权码流程的访问令牌请求中发送,尽管它可以在其他流程中指定,例如资源所有者密码凭据授予。
授权服务器应忽略scope参数或验证其与授权请求中先前提供的范围匹配。
参考资料
重定向方案劫持
当需要在移动设备上使用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应用程序需要导出一个类似于以下的活动:
|
|
Android系统在定义Intent过滤器时相当宽松。过滤器细节越少,网越大,可能捕获的URI越多。因此,例如,如果仅提供方案,则无论其主机、路径等如何,都将捕获此方案的所有Intent。
如果有多个应用程序可能捕获给定的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
由于该领域经常有新的研究和发展,我们并不声称完全了解所有细节。如果您对如何改进此摘要有任何建议,请随时联系作者。我们很乐意更新此博客文章,以便它可以成为任何对该主题感兴趣的人的综合资源。