跨站请求伪造与 OAuth2

工程 | Dave Syer | 2011年11月30日 | ...

在这篇短文中,我们将从 OAuth2 的角度来探讨跨站请求伪造,分析可能的攻击以及在使用 OAuth2 保护 Web 资源时如何应对这些攻击。

OAuth2 是一种协议,它允许客户端应用(通常是 Web 应用)在用户的许可下代表用户执行操作。客户端被允许执行的操作在资源服务器(另一个 Web 应用或 Web 服务)上进行,用户通过告知授权服务器他信任该客户端执行其请求的操作来批准这些操作。互联网上常见的授权服务器示例包括 FacebookGoogle,它们也都提供资源服务器(Facebook 的 Graph API 和 Google 的 Google API)。

跨站请求伪造(CSRF 或 “sea surf”)攻击是指恶意用户诱骗用户点击一个链接,该链接会改变目标系统上的某些状态。如果用户已经与目标系统进行了身份验证,他可能甚至不会注意到攻击,因为浏览器会自动发送认证头或 Cookie。

使用 OAuth2 保护资源和委托权限的系统本身就容易受到所有“正常”CSRF 攻击——用户会进行身份验证,并且状态可能会被改变。在这里,我们重点关注针对 OAuth2 协议的特定攻击,在这种情况下,恶意用户试图获取访问令牌,从而能够执行用户可以做的任何事情(在令牌的范围内)。

我们下面讨论的一些针对 CSRF 的防御措施来自于 OAuth2 客户端应用的精心实现,因此只要授权服务器实现了 OAuth2 规范,任何客户端都可以使用这些措施。有些防御措施位于 OAuth2 授权服务器本身,因此只能由服务器组件的开发者来实现。有些措施是规范强制要求或强烈建议的,有些则仅来自于协议的谨慎使用。

下面给出了一些 CSRF 尝试的示例,它们是内联链接。点击它们并不危险——这只是一个演示,没有数据会损坏或身份会泄露。您可以使用登录屏幕上建议的凭据(marissa/koala)登录授权服务器。这些攻击之所以能成功,是因为演示系统的实现很糟糕。您应该希望一个真正的 OAuth2 实现者会更小心。对于授权服务器来说,这可能不是徒劳的希望,但对于客户端则不能确定,并不是说这类应用有什么特别之处,而是客户端数量更多,来源更广泛。

对授权服务器的攻击

恶意用户试图通过诱骗用户在已进行身份验证时点击指向授权服务器的链接来获取访问令牌。这些示例依赖于授权服务器可能会将重定向发送到您要求的任何 URI 这一事实。规范允许这样做,除非已为相关客户端预先注册了特定的重定向。使用已注册的重定向,攻击不会奏效,但这对于某些系统可能过于严格,例如 Facebook 没有这样做,但 Google 做了。实际上,Facebook 将重定向 URL 限制为由预先注册的应用“拥有”(即以相同的主机、路径等开头),这相当合理,但不是规范的一部分。

要尝试这些攻击,您需要一个授权服务器,然后只需点击链接,查看重定向中泄露的秘密信息。重定向中有一个简单的脚本,它捕获浏览器窗口位置并将其镜像,以演示恶意用户如何提取所需信息并将其发送到别处。因此,如果您在浏览器中看到一些秘密信息,例如访问令牌,则攻击成功。这只是一个演示。真正的恶意用户会获取访问令牌并代表您(或演示中的 marissa)做坏事。服务器应运行在 http://oademo.cloudfoundry.com 上——如果不是,则实现是来自 Spring Security OAuthsparklr2 应用,如果您更改示例中的链接,可以在本地运行它。

隐式授权攻击

隐式授权攻击可以说是最恶毒的,因为恶意用户无需做额外工作即可获得令牌。但是,他确实需要知道客户端密钥以及有效的客户端 ID。

因此,此攻击成功。隐式授权:无密钥的客户端 点击此处

但此攻击失败。隐式授权:有密钥的客户端:点击此处

授权码攻击

这里是基本的授权码授权流程,在我们查看针对它的攻击之前,先回顾一下细节。

序列图,auth-code-flow

User->Client: GET /peek

participant AuthServer

activate User activate Client

Client->User: 302: location=auth/authorize deactivate User deactivate Client

User->AuthServer: GET /authorize activate User activate AuthServer AuthServer->User: {messages: "Do you approve?"} deactivate User deactivate AuthServer

User->AuthServer: approve activate User activate AuthServer AuthServer->User: 302: location=client/handle_code?code=dkshfjg deactivate User deactivate AuthServer

User->Client: GET /handle_code?code=dkshfjg activate User activate Client

Client->AuthServer: POST: /token?code=dkshfjg activate AuthServer AuthServer->Client: 200: {access_token:CNMBVCXKVY} deactivate AuthServer

Client->ResourceServer: GET /resource(access_token) activate ResourceServer ResourceServer->Client: 200: response deactivate ResourceServer

Client->User: 200: result deactivate Client deactivate User

授权码攻击允许恶意用户窃取授权码,然后可以将其交换为令牌。恶意用户可以独立于客户端密钥获取授权码,但要使用它,他需要密钥。因此,这两个链接都返回了一个有效的码,但其中一个受到客户端密钥保护,如果没有密钥,则无法用于模拟用户。

授权码授权:无密钥的客户端 点击此处

授权码授权:有密钥的客户端:点击此处

序列图,auth-code-csrf

participant User

participant BadClient

User->AuthServer: GET /authorize?client_id=good&redirect_uri=/bad/peek&state=poioiu

note right of AuthServer AuthServer checks validity of state end note

activate User activate AuthServer

AuthServer->User: 302: location=/bad/peek?code=dkshfjg&state=poioiu deactivate User deactivate AuthServer

User->BadClient: GET /peek?code=dkshfjg&state=poioiu activate User activate BadClient

BadClient->AuthServer: POST: /token?code=dkshfjg&state=poioiu

activate AuthServer

note right of BadClient Assume BadClient knows client secret end note

AuthServer->BadClient: 200: {access_token:CNMBVCXKVY}

deactivate AuthServer deactivate BadClient deactivate User

对客户端的攻击

客户端应用也容易受到 CSRF 攻击,目的不是窃取访问令牌,而是更改客户端或(更可能)客户端用于管理其状态的资源服务器上的状态。在这种情况下,提供者系统无法阻止攻击,但它们可以帮助客户端实现自己的保护。主要机制是客户端生成和管理的 state 参数,并由授权服务器原样传递。

这里是包含 state 参数的完整授权码授权流程的序列图。客户端通过检查用户回来获取访问令牌时 state 是否存在于用户会话中来实现 CSRF 保护。此设计中的 state 参数是客户端应用与已认证用户会话中某个会话属性的键。

User->Client: GET /peek

participant AuthServer

activate User activate Client

note right of Client: generate random key for state Client->Session: store: {request:/peek, poioiu:XASFDAS} activate Session Client->User: 302: location=auth/authorize?state=poioiu deactivate User deactivate Client

User->AuthServer: GET /authorize?state=poioiu activate User activate AuthServer AuthServer->User: 302: location=client/handle_code?code=dkshfjg&state=poioiu deactivate User deactivate AuthServer

User->Client: GET /handle_code?code=dkshfjg&state=poioiu activate User activate Client

note left of Client: check state Client->Session: get poioiu Session->Client: XASFDAS note left of Client: OK (state exists) Client->AuthServer: POST: /token?code=dkshfjg activate AuthServer AuthServer->Client: 200: {access_token:CNMBVCXKVY} deactivate AuthServer

Client->Session: get request Session->Client: /peek note left of Client: continue with /peek Client->ResourceServer: GET /resource(access_token) activate ResourceServer ResourceServer->Client: 200: response deactivate ResourceServer

Client->User: 200: result deactivate Client deactivate User

如果 state 的键是可以猜测的,那么此示例中的 CSRF 攻击仍然可能成功,但只要客户端在授予令牌后清理了会话,攻击就必须在具有相同键的现有合法授权期间发生。

那将是不幸的(且不太可能),但 OAuth2 规范仍然建议客户端使用不可猜测的(例如,包含随机组件的)state。规范不假定客户端本身是有状态的,这使得该建议更容易理解:如果 state 参数不仅仅是会话中的一个键,而是以某种不透明方式编码的整个状态本身,那么使其不可猜测就更加重要。

防御措施

以下是可用于防御上述攻击的可能防御措施的总结。所有这些措施都可以在使用 Spring SecuritySpring Security OAuth 构建的系统中实现,但并非所有都自动强制执行——与开发框架通常的情况一样,开发者必须选择使用提供的功能。

对于客户端

  • 使用客户端密钥
  • 使用注册的重定向,如果授权服务器允许,可以是固定的或可变的(见下文)
  • 向授权端点发送一个随机的 state 参数值,并在用户会话中以相同的键存储一些内容
  • 仅向带有 state 参数的授权端点发送请求
  • 当授权码请求到达时,检查用户会话中的 state
  • 如果客户端是无状态的(没有会话),则在 state 键本身中编码一些关于用户的信息并进行比较

对于授权服务器

  • 始终使用 SSL,以便普通观察者无法嗅探到秘密和令牌
  • 要求客户端使用密钥注册,并且可能提供更强的密钥管理功能
  • 不要向动态注册的客户端暴露客户端密码授权
  • 要求客户端通过头部(headers)而非表单参数(form parameters)进行认证。这使得诱骗用户点击 CSRF 链接变得相当困难,因为他们需要一个脚本,并且同源策略可能会阻止普通浏览器中的攻击。
  • 固定重定向:仅重定向到客户端注册的固定 URL,或者
  • 可变重定向:允许用户重定向,但要求客户端注册一个网站地址,并强制所有重定向都在该网站上托管

结论

我们研究了针对 OAuth2 系统的某些 CSRF 攻击以及可以采取的一些防御措施。总的结论是,有很多机会可以挫败此类攻击,其中一些来自规范,另一些则不是。与任何安全漏洞一样,系统是否能很好地防御 CSRF 取决于实现的细节以及密码和密钥的质量。即使符合规范的系统也可能受到攻击,但通过精心实现可以采取一些措施,使这些攻击成功的可能性降低。

订阅 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速前进。

了解更多

获取支持

Tanzu Spring 在一项简单的订阅中提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将到来的活动

查看 Spring 社区的所有即将到来的活动。

查看全部