为什么使用普通的 oauth2 进行身份验证是个坏主意?

信息安全 验证 oauth
2021-08-17 01:20:05

TL;DR:使用 oauth2 进行身份验证有哪些安全隐患?

我正在构建一个应用程序(站点 A),它允许用户通过更简单的界面在另一个网站(站点 B)上执行操作。

站点 B 提供了一个实现 OAuth2.0 的 API,用于授权我的应用程序。

我在想我可以通过捎带站点 B 的身份验证来避免存储密码并让用户在我的应用程序中获得另一个帐户。

当然,我已经阅读抨击使用普通 OAuth 进行身份验证的文献,但从安全角度来看,我看不出这是多么糟糕。

他们似乎主要关注解决方案的实用性和(缺乏)通用性。

这是我想到的方案:

  • 用户 Sally 加载站点 A 并单击“登录”
  • 她被重定向到站点 B 的授权页面,在那里她进行身份验证或有一个活动会话
  • 如果她授权站点 A 代表她访问站点 B,她将被重定向回站点 A,并带有授权代码
  • 站点 A 获取授权代码并通过站点 B 的 API 将其交换为访问令牌(和刷新令牌)。
  • 站点 A 向站点 B 询问 Sally 的 user_id 并使用该 ID 登录
  • 令牌存储在数据库中供后端使用,后端在站点 B 上完成所有实际工作。

我会注意到我在这里使用了所谓的“服务器端流程”。此外,authorization_code第三步返回的是一个短暂的一次性代码,它与 A 的 client_id 绑定,只能与相应的client_secret.

当 Sally 从另一台设备登录时,该过程会重复并存储新的令牌。

我看到的唯一问题是用户将被要求在每次登录时授权我的应用程序,而不仅仅是第一次,但目前这不是问题。当我实际上有一个有效的令牌时,我也会要求一个新的令牌。虽然有点不切实际,但目前这不是问题(*)

我没有看到从安全角度来看这是多么糟糕。

有了这样的方案:用户的安全问题是什么?对于应用程序?

我觉得我错过了什么。

(*)我不会有很大的用户群,只有几个白名单用户。当(如果)应用程序增长时,我计划将其集成到使用真正 OpenID Connect 提供程序的更大站点中。我只想在这次试点测试中保持简单、小巧和专注

3个回答

注意:如果您正在寻找类似 OAuth2 的东西,但要进行身份验证,则应
使用OpenId Connect代替。


OAuth2 旨在让用户授权应用程序从某个资源提供者加载用户的资源。换句话说:OAuth2 是一种授权机制。该协议不支持身份验证(尽管它通常被滥用)。

安全漏洞在于您在第 5 个要点中所做的假设。

你说:

站点A向站点B询问Sallyuser_id并使用该 ID 登录她

而实际上它应该是:

站点A向站点B请求授予访问权限user_id的用户数据。access_token

在此处输入图像描述 图 1:(机密)客户端的 OAuth 流程。

如果一切按计划进行,那么access_token确实是来自您重定向到B进行身份验证的用户。但是:不能保证确实如此。事实上,用户先前已授予访问用户数据的权利(使用带有Bauthorization_code的 OAuth2)的任何(恶意)网站,都可以从B获得有效的并将其发送给您,在要点 3 中。

换句话说,如果我运行一个网站,该网站要求用户允许他们使用 OAuth2 访问B处的资源,我可以模拟所有网站上所有滥用 OAuth2(使用B作为 OAuth2 授权服务器)进行身份验证的用户。

OAuth2 的“问题”authorization_code不是为特定的client_id. 因此,如果您收到一个authorization_code,您无法确定B是把authorization_code您收到的那个发给您,还是发给其他一些服务。这被认为可以接受授权,但绝对不能接受身份验证

更新
至于您的评论:

(并且我限制 A 只接受它之前重定向到 B 的用户的一个,但这仍然是未经身份验证的)

我相信您在这里添加了额外的预防措施,这在 OAuth 协议中不是强制性的。因此,它不能被依赖。

正如 Jacco 所解释的,在 oauth2 之上的简单身份验证实现有几个漏洞,其中最常见的是CSRF

鉴于有一个完美的身份验证协议可用而没有所有这些陷阱,因此推出自己的身份验证协议并不是一个好主意。

OTOH,通过这样做以及理解和解决这些问题,可以学到很多东西。

TL;DR:除非您这样做是为了了解为什么不应该这样做,否则不要使用 oauth2 进行身份验证。使用 OpenID 连接。

#OAuth 2.0 威胁模型和安全注意事项

首先,RFC6819中对 oauth2 的威胁模型进行了广泛的分析

oauth2 中有几种可能的“流程”。我为我的项目关注的一个是authorization_code流程。

#授权“代码”

以下是 RFC6819 对此的说明:

授权“代码”代表成功的最终用户授权过程的中间结果,客户端使用它来获取访问和刷新令牌。授权“代码”被发送到客户端的重定向 URI 而不是令牌,用于两个目的:

  1. 基于浏览器的流通过 URI 查询参数(HTTP 引用者)、浏览器缓存或日志文件条目向潜在攻击者公开协议参数,并且可以重放。为了减少这种威胁,短暂的授权“代码”被传递而不是令牌,并通过客户端和授权服务器之间更安全的直接连接交换令牌。

  2. 在客户端和授权服务器之间的直接请求期间对客户端进行身份验证比在间接授权请求的上下文中简单得多。后者需要数字签名。

所以授权码更安全,耶!

authorization_code流漏洞在RFC6819的4.4.1 节中进行了分析。

本节涵盖了很多内容。我将只关注一些威胁。

##CSRF

从第 4.4.1.8 节开始:

攻击者可以将授权“代码”授权给授权服务器上他们自己的受保护资源。然后,他在他的设备上中止重定向流回客户端,并诱使受害者执行重定向回客户端。客户端接收重定向,从授权服务器获取令牌,并将受害者的客户端会话与使用令牌可访问的资源相关联。

影响:用户代表攻击者访问资源。[...] 例如,用户可能会将私人项目上传到攻击者的资源

这也包含在RFC6749 的第 10.12 节中

客户端必须为其重定向 URI 实现 CSRF 保护。这通常通过要求发送到重定向 URI 端点的任何请求包含一个将请求绑定到用户代理的已验证状态的值(例如,用于验证用户代理的会话 cookie 的哈希)来实现。客户端在发出授权请求时应该使用“state”请求参数将此值传递给授权服务器。

因此,在重定向到 oauth2 提供程序时,您只需添加一个参数state,它只是一个 CSRF 令牌(应该是不可猜测的,存储在安全的 cookie 中等)。authorization_code当 oauth2 提供程序将用户重定向回来时,此令牌将与发送回。

这种攻击的对策必须由客户端和授权服务器共同实施,也可以由授权服务器强制执行。

state 参数也包含在这个 sec.SE question 中

##代码替换(OAuth登录)

这个(在 RFC6819 的第 4.4.1.13 节中介绍)专门针对 oauth2 场景的身份验证。

基本上,攻击者authorization_code通过恶意站点(我们称其为站点 C)为用户获取一个,并将其发送到合法站点(我们仍将其称为站点 A),该站点将其交换为一个 access_token,然后用于断言用户的身份通过资源服务器。这有效地让攻击者以站点 A 上的用户身份登录。

这是 Jacco 在他的回答中提到的。

这种攻击的对策必须由授权服务器实施:

所有客户端都必须在每个请求中指明其客户端 ID,以交换访问令牌的授权“代码”。授权服务器必须验证特定的授权“代码”是否已发给特定的客户端。如果可能,应事先对客户端进行身份验证。

其他

信不信由你,之前的攻击及其对策涵盖了使用代码流时对身份验证的大部分威胁。

还有许多其他威胁和对策,其中许多应始终实施:

从第 4.4.1.3 节开始:

基于句柄的令牌必须使用高熵对客户端进行身份验证;这增加了攻击者必须猜测的另一个值 将授权“代码”绑定到重定向 URI;这增加了攻击者必须猜测的另一个值 对令牌使用较短的到期时间

这些都应该由授权服务器实现。

从第 4.1.1.4 节开始:

授权服务器应验证客户端 授权服务器应根据预先注册的重定向 URI 验证客户端的重定向 URI

这些也应该由授权服务器实现。

从第 4.4.1.5、4.4.1.6 和其他部分:

客户端的重定向 URI 应指向受 HTTPS 保护的端点

这应该由客户端实现,并且可能由授权服务器强制执行。

#那么就可以使用oauth2登录了

不要这样做。使用 OpenID 连接。

还记得第 4.4.1.13 节的对策吗?好吧,还有一个我没有引用:

客户端应使用适当的协议,例如 OpenID (cf. [OPENID]) 或 SAML (cf. [OASIS.sstc-saml-bindings-1.1]) 来实现用户登录。两者都支持对客户端的受众限制。

你去吧。改用那个。

如果您仍然希望/需要针对 oauth2 提供者进行身份验证,请首先确保您的提供者实施上述所有对策。

如果确实如此,那么您也许可以将其拉下来。广泛测试并聘请安全团队对您的解决方案进行全面分析。

此外,请确保您依赖于安全性的所有提供商功能都记录在您的提供商的 API 中,否则它们可能会在没有事先通知的情况下被删除,您最终会得到一个非常破碎的产品。

就我而言:

  • 我很幸运,我的供应商在他们身边实施了所有这些对策。
  • 在应用程序的初始测试期之后,我不依赖此进行身份验证(这不是我的应用程序的必需功能,只是一个方便的占位符预启动)

此外,在整个实现过程中,我对 oauth2 的了解已经足够多,因此非常值得。

如果您想了解更多信息,请阅读 RFC6819 和 RFC6749。我还发现这个网站非常有用。

流程中的安全漏洞:

她被重定向到站点 B 的授权页面,在那里她进行身份验证或有一个活动会话

如果用户在站点 B 有一个活动会话,并且其他站点(站点 C、D 等)可以使用站点 B 的 OAuth 系统进行授权,则恶意站点(例如站点 x)可以从站点 B 获取访问令牌并可能使用令牌重放在您的网站上冒充用户并执行未经用户授权的操作。重新阅读链接中的 Facebook 示例,它解释了问题。

整个方案的挑战在于,您的站点只会像站点 B 的 OAuth API 一样安全。正如我们刚刚展示的,他们的 API 不安全,因为第三方可能会滥用它。在滥用的情况下,您唯一的资源是呼吁站点 B 禁止站点 x。就我个人而言,我不想把我的安全命运交给一个我不拥有的网站。

更安全的机制将根据站点 B 的已接受 OAuth 客户端列表对您的站点进行身份验证,最好使用公钥客户端证书,并通过限制对请求站点的令牌使用来防止令牌重放。您不能强制站点 B 采用这种访问方案,因此您受限于它们提供的任何安全级别。