跨站点请求伪造和 OAuth2

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

本文简要介绍了在 OAuth2 上下文中跨站点请求伪造,探讨了可能的攻击以及在使用 OAuth2 保护 Web 资源时如何抵御这些攻击。

OAuth2 是一种协议,允许客户端应用程序(通常是 Web 应用程序)代表用户执行操作,但需要用户的许可。客户端被允许执行的操作是在资源服务器(另一个 Web 应用程序或 Web 服务)上执行的,用户通过告诉授权服务器他信任客户端执行其请求的操作来批准这些操作。互联网上常见的授权服务器示例包括 FacebookGoogle,这两者也提供资源服务器(对于 Facebook 而言是 Graph API,对于 Google 而言是 Google API)。

跨站点请求伪造(CSRF 或“点击劫持”)攻击涉及攻击者诱骗用户点击一个链接,该链接更改目标系统上的某些状态。如果用户已通过目标系统进行身份验证,他甚至可能不会注意到攻击,因为浏览器会自动发送身份验证标头或 cookie。

使用 OAuth2 保护资源并委派权限的系统仍然容易受到所有“正常”CSRF 攻击的影响——用户进行身份验证,并且可能状态会被更改。在这里,我们专注于特定于 OAuth2 协议的攻击,在这种情况下,攻击者将尝试获取访问令牌,这将使他能够执行用户可以执行的任何操作(在令牌范围内)。

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

下面给出了一些 CSRF 尝试示例作为内联链接。点击它们不会造成危险——这只是一个演示,没有任何数据会被破坏或身份会被泄露。您可以使用它在登录屏幕上建议的凭据(marissa/koala)登录授权服务器。攻击之所以成功,是因为演示系统实现不当。您应该希望真正的 OAuth2 实现者会更加谨慎。对于授权服务器来说,这可能不是一个徒劳的希望,但您不能确定客户端是否会如此,并不是说这种类型的应用程序有什么特别之处,而是客户端数量更多,并且来源更加多样化。

针对授权服务器的攻击

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

要尝试这些攻击,您需要一个授权服务器,然后您可以点击链接并查看重定向中显示的哪些机密信息。重定向中有一个简单的脚本捕获浏览器窗口位置并将其镜像以演示攻击者如何剥离所需的信息并将其发送到其他地方。因此,如果您在浏览器中看到一些秘密信息(例如访问令牌),则攻击成功。这只是一个演示。真正的攻击者会获取访问令牌并利用它代表您(或在演示中代表 mariss)执行恶意操作。服务器应该在 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 攻击,不是为了窃取访问令牌,而是为了更改客户端或(更有可能)客户端用于管理其状态的资源服务器上的状态。在这种情况下,提供程序系统无法阻止攻击,但它们可以帮助客户端实施自己的保护。此机制的主要方法是客户端生成和管理的状态参数,并由授权服务器完整地传递。

这是带有状态参数的完整授权码授予流程的序列图。客户端通过在用户返回获取访问令牌时检查用户会话中是否存在状态来实现 CSRF 保护。在此设计中,状态参数是客户端应用程序中已认证用户的会话中会话属性的密钥。

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

如果状态的密钥是可以猜测的,那么在这个示例中 CSRF 攻击仍然可能成功,但是只要令牌授予后客户端清理了会话,攻击就必须在使用相同密钥的现有合法授予期间进行。

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

防御措施

以下是针对上述攻击可以使用的可能防御措施的摘要。所有这些都可以在使用 Spring SecuritySpring Security OAuth 构建的系统中实现,但并非所有措施都自动执行 - 与开发人员框架一样,开发人员必须选择使用提供的功能。

针对客户端

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

针对授权服务器

  • 始终使用 SSL,以便秘密和令牌不会被偶然观察者嗅探
  • 要求客户端使用密钥注册,并且可能还需要提供更强大的密钥管理功能
  • 不要将客户端密码授予公开给动态注册的客户端
  • 要求通过标头进行客户端身份验证,而不是通过表单参数。这使得欺骗用户进入 CSRF 链接变得非常困难,因为他们需要一个脚本,并且常规浏览器中可能存在同源策略阻止攻击。
  • 固定重定向:仅重定向到客户端注册的固定 URL,或者
  • 可变重定向:允许用户重定向,但要求客户端注册一个网站地址,并强制所有重定向都托管在那里

结论

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

获取 Spring 新闻通讯

与 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,以加速您的进步。

了解更多

获得支持

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

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部