这篇文章的主要内容来源于另外一个作者,文章地址:移花接木:针对OAuth2的CSRF攻击
不过针对 OAuth2.0 的 CSRF 攻击我有两个疑问点,先列出来:
1.如果授权码(Authorization code)和 client_id、redirect_uri 是绑定的,即便攻击者把 Tonr 网站触发申请令牌的 HTTP 请求中的授权码替换了,Sparklr 认证服务器校验 code 的合法性时发现其对应的 client_id 等不一致,那么这个code会被认定为不合法的,攻击就会失败
2.有人会说获取授权码(Authorization code)的 HTTP 请求是 GET 方式,截获 client_id、redirect_uri 很容易,然后在 Tonr 网站申请令牌的 HTTP 请求中 client_id、redirect_uri 把也都替换掉,那这样的话,获取的 token 就不再是属于张三的了,攻击也会失败
下面是原文章内容
为了提升用户的帐号登录、注册体验,往往会在我们的 Web 网站、App 中提供如 QQ、WeChat、Sina等第三方社交帐号登录或者绑定的功能,通过上一篇文章 理解OAuth2.0 我们知道这背后使用到的关键技术是 OAuth2.0 认证,想要在自己的 Web 站点、App 中集成第三方帐号登录不是难事,第三方服务提供者都有详细的文档指南
这篇文章我们聚焦关注下 OAuth 安全方面的坑,被人经常谈起的一个 OAuth 安全问题就是开发者不规范化使用 state 而引起的 CSRF 攻击
我们看一个针对 OAuth2.0 的 CSRF 攻击的例子。假设有用户张三、攻击者李四,第三方应用 Tonr(该应用集成了第三方社交账号登录,并且允许用户将社交帐号和 Tonr 中的帐号进行绑定),以及 OAuth2.0 服务提供者 Sparklr
1 攻击者李四登录 Tonr 网站,使用 Sparklr 帐号授权登录 Tonr 网站,并且将 Sparklr 帐号与 Tonr 网站的帐号进行绑定
2 李四选择使用 Sparklr 帐号授权登录 Tonr 网站,根据 OAuth2.0 授权登录流程,此时 Tonr 网站会将李四的请求导向 Sparklr 的认证服务器,询问是否同意授权 Tonr 网站
3 李四在点击 同意授权 之后,截获 Sparklr 认证服务器返回的 code 参数
4 李四精心构造一个 Web 页面,它会触发 Tonr 网站向 Sparklr 发起申请令牌的请求,而这个请求中的 code 就是上一步截获的 code
5 李四将这个 Web 页面放到互联网上,等待或诱骗受害者张三来访问
6 张三之前登录过 Tonr 网站,只是没有把自己的帐号与其他社交帐号绑定起来。在张三访问李四准备的这个 Web 页面的时候,触发了令牌申请流程,Tonr 网站从 Sparklr 那里获取到 Access token ,但是这个 token 以及通过它进一步获取到的用户信息都是攻击者李四的
7 Tonr 网站将李四的 Sparklr 帐号同张三的 Tonr 帐号关联绑定起来,从此以后,李四就可以用自己的 Sparklr 帐号通过 OAuth 授权登录到张三在 Tonr 网站中的帐号,堂而皇之的冒充张三的身份执行各种操作
我们从不同的角度看看这其中发生了什么?
受害者张三(Resource Owner)视角
受害者张三访问了一个 Web 页面,然后就没有然后了,他在 Tonr 网站上的帐号就和攻击者李四在 Sparklr 上的帐号绑定在一起了。他根本不知道发生了什么
Tonr 网站视角
从 Tonr 网站来看,它收到的请求都是正常的,首先它收到了一个 HTTP 请求,代表着当前用户张三在 Sparklr 认证服务器上已经做了 同意授权 操作,其内容如下:
GET /bindingCallback?code=AUTHORIZATION_CODE
当 Tonr 网站收到这样的请求时,它以为用户张三已经同意授权,于是就发起后续的令牌申请请求,(注意此时申请令牌的请求中的 code 已经被替换成攻击者李四的 code),用收到的 Authorization code 向 Sparklr 网站换取 Access token,只不过最后拿到的是攻击者李四的 Access token
最后,通过 Access Token 换取到用户张三在 Tonr 网站的帐号信息并与攻击者李四在 Sparklr 网站的帐号进行绑定
Sparklr 网站(OAuth2.0 服务提供者)视角
对于 Sparklr 网站来说,只要收到的参数是正确的有效的,那就提供正常的认证服务,仅此而已
攻击者李四视角
在用户张三同意 Sparklr 授权 Tonr 网站的授权请求后,攻击者将 Sparklr 网站返回的授权码(Authorization code) 替换成自己提前获取到的 code。这样,当受害者访问到攻击者精心准备的 Web 页面并向 Sparklr 发起申请 Access Token 令牌的 HTTP 请求,实际上是在用张三在 Tonr 网站上的帐号和李四在 Sparklr 网站上的帐号进行绑定
攻击完成之后,李四在 Tonr 网站上可以使用自己在 Sparklr 网站上的帐号进行登录,而且登录进去的是张三在 Tonr 网站上的帐号,同理张三通过自己在 Tonr 网站上的帐号登录进去之后,看到的是李四在
Sparklr 网站上的数据
上帝视角
从整体上来看,这次攻击的时序图应该是下面这个样子:
前提条件
要想完成 CSRF 攻击也是不容易进行的
首选,用户张三必须在 Tonr 网站已经拥有了一个个人帐号
其次,整个攻击必须要在短时间内完成,因为第三方服务提供商的认证服务器颁发的授权码(Authorization code) 有效期很短,OAuth2.0 官方建议控制在 10分钟,一旦 Authorization code 过期后那么后续的攻击也就不能进行下去了
最后,一个 Authorization code 只能被使用一次,如果第三方服务提供商收到重复的 Authorization code ,它会拒绝当前令牌的申请请求。不仅如此,根据 OAuth2.0 官方推荐,它还可以把和这个已经使用过的 Authorization code 相关联的 Access Token 全部撤销掉,进一步降低安全风险
防御办法
要防止这样的攻击也不难,作为第三方应用的开发者,只需要在 OAuth 认证过程中加入 state 参数,并验证它的参数值即可,具体细节如下:
- 第三方 APP 在得到用户允许后,第三方 APP 会向服务提供商的认证服务器发起一个授权请求,在这个请求过程中为用户生成一个随机字符串 state 作为请求参数发送给认证服务器
如下例子:
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
- 在收到认证服务器返回的 Authorization Code 请求的时候,验证 state 参数值。如果是正确合法的请求,那么此时接收到的参数值应该和上一步提到的为该用户生成的 state 参数值完全一致,否则就是异常请求
- state 参数值具备以下几个特性:
- 不可预测性:state 参数值是随机数,使得攻击者难以猜到正确的数值
- 唯一性:每次请求生成 state 参数值都是唯一的
总结
state 参数在 OAuth2 认证过程中不是必选参数,因此第三方应用开发者在集成 OAuth2 认证的时候,容易忽略该参数的存在,导致容易受到 CSRF 攻击。并且,这种攻击比较巧妙,可以悄无声息的攻陷受害者的帐号,难以被察觉
所以,作为第三方应用开发者,我们只需要在 OAuth2.0 认证过程中明确提供 state 参数并验证其参数值,即可防御这样的攻击