简要回顾 OAuth 安全性
你对 OAuth 采用的保护措施有多了解?你的系统是否遵循 OAuth 的开放标准?你是否注意到在实现用户身份验证流程时可能出现的潜在风险?让我们简要回顾一下我们对 OAuth 的了解。
引言
几天前,我们看到了一篇关于 OAuth 漏洞的有趣文章。SALT实验室发布的这篇文章揭示了在Expo中发现的一个漏洞,Expo是一个广泛使用的框架,用于实现 OAuth 和其他功能。具体来说,这篇文章讨论了 expo-auth-session 库中的一个漏洞,该漏洞已经被分配并妥善解决。
如果你对 OAuth 感兴趣或正在开发与 CIAM 相关的产品(如我们一样),我们强烈推荐你阅读这篇文章。它非常有启发性,并提供了有用的见解。这些白帽报告提醒我们,即使是最简单的功能也可能导致漏洞。在网络安全和授权方面,我们必须非常谨慎,以确保用户信息的安全和隐私。如果这篇文章引起了你的注意,我相信你会与我们有同感。
这让我想起了我们刚开始的时候。我们花了很多时间学习和研究 OAuth 和 OIDC 协议的细节。这是一个痛苦而繁琐的过程,但收益也是巨大的。虽然我们团队中的每个人可能不是 OAuth 专家,但每个人都致力于为安全和细致工作不断努力。正是这些努力,使得 Logto 产品发展到今天的水平。
借此机会,我们想在这里重温一些 OAuth 的安全细节。
OAuth 授权码流程一瞥
OAuth 2.0 提供了各种授权流程,以满足不同客户端类型和需求。这些流程包括隐式流程、客户端凭证流程、资源所有者密码凭证流程和设备授权流程。然而,授权码流程因其安全性和广泛使用而脱颖而出。与其他流程不同,它将用户认证与客户端应用程序分离,并通过交换授权码获取令牌。这种方法提供了额外的安全层,因为敏感令牌不会暴露给客户端。此外,授权码流程支持服务器端令牌管理,适用于需要强大安全性和增强用户访问控制的Web应用程序。
以下是最简单的授权码流程图示:
让我们来看一下授权码流程中最关键的两个请求,以及其中看似微不足道但在防范欺诈中起关键作用的部分。
授权端点:
令牌交换端点:
客户端凭证
在 OAuth 中,客户端凭证是客户端应用程序用来向授权服务器进行身份验证和识别的凭证。这些凭证在客户端注册过程中获取,并在向授权服务器发出请求时用于验证客户端的身份。(当你在Logto的管理控制台中首次注册你的应用程序时,你可以找到你的客户端凭证。)
客户端凭证通常由两个组件组成:
- 客户端ID:授权服务器分配给客户端应用程序的唯一标识符。它是一个公共值,通常不被认为是敏感信息。
- 客户端密钥:一个仅客户端和授权服务器知道的机密且安全存储的值。它作为客户端应用程序的身份验证形式,用于在向授权服务器发出请求时验证客户端的身份。
如你所见,客户端ID和客户端密钥的组合在令牌请求期间用于验证客户端并获取访问令牌。
客户端凭证在确保 OAuth 流程的安全性方面起着至关重要的作用。它们帮助授权服务器验证客户端应用程序的真实性并控制对受保护资源的访问。重要的是要安全地处理客户端凭证,并防止其未经授权的访问。Logto将客户端应用程序按两种不同的安全级别分类:
- 机密客户端:包括服务器渲染的Web应用程序和机器对机器(M2M)应用程序。对于机密客户端,所有与授权相关的凭证,包括客户端 凭证,都在服务器端安全存储。此外,所有中间交换请求都经过加密以确保数据的保密性。机密客户端的凭证泄露风险非常低,因此默认情况下,机密客户端被视为更高的安全级别。 在令牌交换流程中,必须提供客户端密钥。
- 公共客户端:包括单页Web应用程序(SPA)和本地应用程序。对于公共客户端,客户端凭证通常硬编码在客户端,例如在JavaScript包或本地平台的应用程序包中。由于客户端凭证在客户端代码中的固有暴露,凭证泄露的风险较高。 在令牌交换流程中,提供客户端密钥是可选的。Logto默认不信任来自公共客户端的凭证。
状态参数
在 OAuth 流程中,state
参数是一个随机生成的值,包含在客户端发送给授权服务器的授权请求中。其目的是在整个授权过程中维护客户端请求的状态或上下文。
state
参数作为一种安全措施,用于防止跨站请求伪造(CSRF)攻击。当授权服务器在用户认证和授权后将用户重定向回客户端应用程序时,它会在响应中包含相同的state值。客户端应用程序必须将此值与其在授权请求中发送的原始state值进行比较。
通过验证state参数,客户端可以确保从授权服务器接收到的响应与其最初发出的请求相对应。这有助于防止攻击者试图欺骗客户端接受针对其他用户或应用程序的响应。
以下是一个虚构用例中的CSRF攻击示例:
CSRF攻击:欺诈绑定社交账户 - 问题
通过适当的state验证机制,客户端可以检测到攻击并防止用户被重定向到攻击者的网站:
CSRF攻击:欺诈绑定社交账户 - 解决方案
PKCE
如前所述,公共客户端(如SPA Web应用程序和本地应用程序)存在更高的认证凭证泄露风险,包括授权服务器颁发的授权码。
PKCE 代表代码交换证明密钥。它是 OAuth 2.0授权码流程的扩展,旨在增强公共客户端的安全性。
PKCE 的引入是为了减轻攻击者拦截授权码并在客户端不知情的情况下将其交换为访问令牌的风险。这种类型的攻击被称为授权码拦截攻击,在客户端应用程序无法安全存储客户端密钥的环境中更为常见。
为了实现 PKCE,客户端应用程序生成一个随机码验证器,并使用特定的哈希算法(通常是SHA-256)从中派生出代码挑战。代码挑战包含在发送给授权服务器的初始授权请求中。
当授权服务器颁发授权码时,客户端应用程序在令牌请求中包含原始码验证器。服务器验证码验证器是否与存储的代码挑战匹配,只有在匹配时才颁发访问令牌。
通过使用PKCE,客户端应用程序确保授权码本身不足以获取访问令牌。这种机制为授权流程增加了额外的安全层,特别是对于公共客户端而言,其中存储客户端密钥具有挑战性。
Logto将PKCE作为所有公共客户端类型应用程序的唯一授权流程。然而,对于机密客户端,PKCE可以省略。
重定向 URI
重定向 URI(统一资源标识符)是 OAuth 认证和授权过程中授权服务器在用户完成认证和授权后将用户重定向回的特定端点或URL。
在 OAuth 流程中,客户端应用程序在初始授权请求中包含重定向 URI 。这个 URI 作为回调URL,当用户成功认证并授予客户端权限后,用户将被重定向到该URL。
一旦用户完成认证过程,授权服务器生成的响应将包含授权码,并将用户重定向回指定的重定向 URI 。
重定向 URI 的验证是确保 OAuth 流程安全性和完整性的重要步骤。它涉及验证授权请求和后续重定向中使用的重定向 URI 是否有效且可信。
让我们回顾一下原始的OAuth 漏洞报告。(以下部分引用自原始文章)
当用户在 Expo Go 中使用移动应用程序点击“使用 Facebook 登录”时,它会将用户重定向到以下链接:
https://auth.expo.io/@moreisless3/me321/start?authUrl=https://www.facebook.com/v6.0/dialog/oauth?code_challenge=...&display=popup&auth_nonce=...&code_challenge_method=S256&redirect_uri=https://auth.expo.io/@moreisless3/me321&client_id=3287341734837076&response_type=code,token&state=gBpzi0quEg&scope=public_profile,email&returnUrl=exp://192.168.14.41:19000/--/expo-auth-session
在响应中,auth.expo.io 设置了以下 cookie:ru=exp://192.168.14.41:19000/--/expo-auth-session。值 RU 将在第 5 步中作为返回 URL 使用。然后它会向用户显示确认消息,如果用户批准 - 它会将用户重定向到 Facebook 登录以继续身份验证流程。
……
此页面读取查询参数“returnUrl”并相应地设置 cookie。
让我们将 returnUrl 更改为 hTTps://attacker.com
(https 不被允许,所以我尝试插入大写字母并且成功了),这会将 cookie 中的 RU(返回 URL)设置为 https://attacker.com
。
……
在上述情况下,丢弃了原始的 redirect_uri
参数,Expo 引入了一个新的参数 returnUrl 而没有进行适当的验证。这一疏忽为攻击者提供了获取 Facebook 返回的授权码的机会。更多详情,请参阅原始 文章。
重定向 URI 验证具有以下几个重要目的:
- 防止网络钓鱼攻击:通过验证重定向 URI,授权服务器确保用户被重定向回受信任和授权的端点。这有助于防止攻击者将用户重定向到恶意或未经授权的位置。
- 防止开放重定向:开放重定向是一种可以被利用来将用户重定向到恶意网站的漏洞。通过验证重定向 URI,授权服务器可以确保重定向保持在授权域或一组受信任域的范围内。
- 确保授权响应的正确路由:验证重定向 URI 有助于确保授权服务器将用户重定向回预期的客户端应用程序。它确保响应(如授权码或访问令牌)被传送到正确的目的地。
在 Logto 中,redirect_uri
注册对所有类型的应用程序都是强制性的。我们会将接收到的值与 Logto 服务器中注册的值进行比较和匹配,包括任何自定义搜索参数。如果授权请求由于缺失、无效或不匹配的 redirect_uri
值而未能通过验证,将返回无效的重定向 URI 错误到注册的 redirect_uri
。
总结
由于其复杂和微妙的性质,这些细节常常被忽视。有些只是像 state
这样的随机字符串。
然而,重要的是要注意这些安全措施为用户授权增加了多层保护,减轻了 CSRF 攻击、授权码截取和未经授权的重定向等风险。
这些只是 OAuth 协议提供的全面安全功能的一小部分。OAuth 提供了一个用于安全认证和授权的强大框架。它还提供灵活和开放的端点,以满足现实世界产品应用中的各种需求。
作为开发者和服务提供商,持续优先考虑用户授权流的安全性至关重要。保持警惕,遵循最佳实践,并跟上 OAuth 生态系统中的最新发展,对于确保用户身份和敏感数据的完整性和保护至关重要。我们将继续致力于在 OAuth 的实施中保持最高的安全标准,并保护用户的隐私和信任。