定义与原理

  • 定义

    CSRF(Cross Site Request Forgery,跨站点请求伪造)

    通过伪装成受信任用户请求受信任的网站。对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等

  • 例子

    假设请求这个url可以删除掉编号为156713012的搜狐博客文章

    1
    http://blog.sohu.com/mange/entry.do?m=delete&id=156713012

    假设这个url同时还存在csrf漏洞,我们可以尝试利用这个漏洞,删除掉这篇文章。

    攻击者可以首先在自己的域构造一个页面

    1
    http://www.a.com/csrf.html

    其中包含一个img标签,指向上面的删除博客文章的链接

    1
    <img src="http://blog.sohu.com/manage/entry.do?m=delete&id=156713012" />

    攻击者可以诱使博主访问自己构造的页面 http://www.a.com/csrf.html,博主看到的是一个无法显示的图片,此时博客文章已经被删除掉了。

    原因就是:博主在访问该页面的时候,图片标签向搜狐服务器发送了一次GET请求,删除了文章。

CSRF进阶

浏览器的Cookie策略

一般csrf攻击的目标需要使用cookie

  1. 浏览器所持有的Cookie分为两种

    • Session Cookie(临时Cookie)

      • 没有指定Expire时间,保存在内存中,所以浏览器关闭后,Session Cookie就失效了。
      • 浏览网站,若是一个网站设置了Session Cookie,那么在浏览器进程的生命周期内,即使浏览器新打开了Tab页,因为新Tab页在同一个浏览器进程中,Session Cookie也都是有效的,因此Session Cookie将被发送
    • Third-party Cookie(本地Cookie)

      • 服务器在Set-Cookie时指定了Expire时间,只有到了Expire时间后Cookie才会失效,所以这种Cookie会保存在本地
      • 如果浏览器从一个域的页面中,要加载另一个域的资源,由于安全原因,某些浏览器会阻止Third-party Cookie的发送

      例如,攻击者可以在自己的域上(b.com/csrf.html)构造 iframe<img><script><link>等标签

      1
      <iframe src="http://www.a.com"></iframe>

      抓包可以发现,发送出了 Session Cookie

  2. 每个浏览器的拦截策略不同

    • 火狐默认允许发送本地Cookie:用户访问b.com,利用本地直接发送即可成功

    • IE浏览器不允许发送本地Cookie:需要诱使用户在当前浏览器中先访问http://www.a.com,让Session cookie有效,然后再实施CSRF攻击。

P3P头的副作用

有些CSRF攻击并不需要进行认证,不需要发送cookie

  1. P3P头的定义

    P3P Header(The Platform for Privacy Preferences)是W3C制定的一项关于隐私的标准。

  2. P3P头的作用

    • 如果网站返回给浏览器的HTTP头中包含有P3P头,则在某种程度上来说,将允许浏览器发送第三方Cookie。这种情况下即使是IE的<iframe><script>等标签也将不再拦截第三方Cookie的发送。

    • 在网站的业务中,P3P头主要用于类似广告等需要跨域访问的页面。但是P3P头设置后,对于Cookie的影响将扩大到整个域中的所有页面,因为Cookie是以域和path为单位的,不符合“最小权限”原则。

    • P3P头只需要由网站设置一次即可,滞后每次请求都会遵循此策略,不需要重复设置。

  3. 例子

    http://www.b.com/test.html 代码如下

    1
    <iframe width=300 height=300 src="http;//www.a.com/test.php"></iframe>

    http://b.com 中请求 test.html,它的 iframe 会告诉浏览器去跨域请求http://http://www.a.com/test.php

    http://http://www.a.com/test.php 的代码如下

    1
    2
    3
    <?php
    header{"Set-Cookie:test=axis;domain=.a.com;path=/"}; //临时cookie
    ?>

    而由于浏览器的同源策略,这里的set-cookie是不会成功的(无论是临时还是本地cookie都不会成功)

    注意这里和上面的例子的区别:

    同源策略主要限制跨域请求时设置和读取Cookie

    上面的例子是用户主动访问http://www.a.com,这个请求是同源的,因为请求的源(浏览器中当前访问的页面)和目标源(http://www.a.com)是相同的。由于是同源请求,服务器可以成功地设置Session Cookie。浏览器会接受并存储这个Cookie。

    当用户再访问攻击页面(http://www.b.com/csrf.html),这个页面包含一个指向http://www.a.com<iframe>或其他请求。虽然这个请求是跨域的,但因为Session Cookie已经存在于浏览器中,所以浏览器会自动附带这个Session Cookie发送请求。

    这里的例子是跨域请求直接尝试设置Cookie,所以浏览器会拒绝这个操作,这是同源策略的限制。

    但是,在加入P3P头的情况下:P3P头允许跨域访问隐私数据,从而可以跨域set-cookie成功。

    • 正因为P3P头目前在网站的应用中被广泛应用,因此在CSRF的防御中不能依赖于浏览器对第三方Cookie的拦截策略,不能心存侥幸。

    • 很多时候,如果测试CSRF时发现<iframe>等标签在IE中居然能发送Cookie,而又找不到原因,那么很可能就是因为P3P头在作怪。

GET和POST

大多数CSRF攻击发起时,使用的HTML标签都是<img><iframe><script> 等带“src”属性的标签,这类标签只能够发起一次GET请求,而不能发起POST请求。

但是,这并不表示只要把重要的操作改成只允许POST请求,就能防止CSRF攻击。

  • 情景1:服务器没有区分get和post请求

    • 对于很多网站的应用来说,一些重要操作并未严格地区分GET与POST,攻击者可以使用GET来请求表单的提交地址。比如在PHP中,如果使用的是$_REQUEST,而非$_POST变量,则会存在这个问题。

    • 例子:

      以下表单

      1
      2
      3
      4
      5
      <form action="/register" id="register" method="post">
      <input type=text name="username" value="" />
      <input type=password name="password" value="" />
      <input type=submit name="submit" value="" />
      </form>

      用户可以尝试构造一个GET请求

      1
      http://host/register?username=test&password=passwd

      如果服务器没有区分get和post请求,那么这个请求会通过。

  • 情景2:服务器区分get和post请求,可以在攻击代码中构造Post

    • 服务器区分get和post请求的情况下,仍然可以在一个页面中构造好一个form表单,然后使用JavaScript自动提交这个表单。

    • 例子:

      http://www.b.com/test.html

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      <form action="http://www.a.com/register" id="register" method="post" >
      <input type=text name="username" value="" />
      <input type=password name="password" value="" />
      <input type=submit name="submit" value="submit" />
      </form>
      <script>
      var f = document.getElementById("register");
      f.inputs[0].value = "test";
      f.inputs[1].value = "passwd";
      f.submit();
      </script>

      可以将这个页面隐藏在一个不可见的iframe窗口中,那么整个自动提交表单的过程,对于用户来说也是不可见的。可以成功实现攻击(前提是获取到了cookie)

CSRF worm

2008年9月,国内的安全组织80sec公布了一个百度的CSRF Worm。 漏洞出现在百度用户中心的发送短消息功能中:

1
http://msg.baidu.com/?ct=22&cm=MailSend&tn=bmSubmit&sn=用户账户&co=消息内容

只需要修改参数sn,即可对指定的用户发送短消息。

百度的另外一个接口则能查询出某个用户的所有好友:

1
http://frd.baidu.com/?ct=28&un=用户账户&cm=FriList&tn=bmABCFriList&callback=gotfriends

将两者结合起来,可以组成一个CSRF Worm——让一个百度用户查看恶意页面后,将给他的所有好友发送一条短消息,然后这条短消息中又包含一张图片,其地址再次指向CSRF页面,使得这些好友再次将消息发给他们的好友,这个Worm因此得以传播。

CSRF防御

验证码

  1. 概念

    验证码被认为是对抗CSRF攻击最简洁而有效的防御方法

    CSRF攻击的过程,往往是在用户不知情的情况下构造了网络请求。而验证码,则强制用户必须与应用进行交互,才能完成最终请求。

  2. 缺点

    • 但是很多时候,出于用户体验考虑,网站不能给所有的操作都加上验证码。

    因此,验证码只能作为防御CSRF的一种辅助手段,而不能作为最主要的解决方案。

Referer Check

  1. 概念

    常见的互联网应用,页面与页面之间都具有一定的逻辑关系,这就使得每个正常请求的Referer具有一定的规律。

    例,一个“论坛发帖”的操作,在正常情况下需要先登录到用户后台,或者访问有发帖功能的页面。在提交“发帖”的表单时,Referer的值必然是发帖表单所在的页面。如果Referer的值不是这个页面,甚至不是发帖网站的域,则极有可能是CSRF攻击。

  2. 缺点

    • 服务器并非什么时候都能取到Referer。
    • 很多用户出于隐私保护的考虑,限制了Referer的发送。
    • 在某些情况下,浏览器也不会发送Referer。例如:从HTTPS跳转到HTTP,出于安全的考虑,浏览器也不会发送Referer。

    无法依赖于Referer Check作为防御CSRF的主要手段,可以通过Referer Check来监控CSRF攻击的发生。

Anti CSRF Token

目前业界针对CSRF的防御,一致的做法是使用Token

  1. 为什么使用Token

    CSRF能够攻击成功,其本质原因是重要操作的所有参数都是可以被攻击者猜测到的。

    出于这个原因,可以想到一个解决方案:把参数加密,或者使用一些随机数,从而让攻击者无法猜测到参数值,这就是token

  2. 具体方法

    URL中,保持原参数不变,新增一个参数Token,值随机且不可预测:

    1
    http://host/path/delete?username=abc&item=123&token=[random(seed)]

    Token需要足够随机,必须使用足够安全的随机数生成算法,或者采用真随机数生成器,且为用户与服务器所共同持有,不能被第三者知晓。在实际应用时,Token可以放在用户的Session中,或者浏览器的Cookie中。

    由于Token的存在,黑客无法再构造出一个完整的URL实施CSRF攻击。

    在提交请求时,服务器只需验证表单中的Token,与用户Session(或Cookie)中的Token是否一致,如果一致,则认为是合法请求;如果不一致,或者有一个为空,则认为请求不合法,可能发生了CSRF攻击。

  3. Token的使用原则

    • Token的生成一定要足够随机

    • 设置一个用户的有效生命周期,在Token消耗掉前都使用同一个Token。

      如果用户已经提交了表单,则这个Token已经消耗掉,应该再次重新生成一个新的Token。

      如果Token保存在Cookie中,而不是服务器端的Session中,一个用户打开几个相同的页面同时操作,当某个页面消耗掉Token后,其他页面的表单内保存的还是被消耗掉的那个Token,因此其他页面的表单再次提交时,会出现Token错误。在这种情况下,可以考虑生成多个有效的Token,以解决多页面共存的场景

    • 保密性

      • Token如果出现在某个页面的URL中,则可能会通过Referer的方式泄露。

        在使用Token时,应该尽量把Token放在表单中。把敏感操作由GET改为POST,以form表单(或者AJAX)的形式提交。

        例子:

        以下是一个页面中删除操作的URL

        1
        http://host/path/manage?username=abc&token=[random] 

        如果这个页面包含了一张攻击者能指定地址的图片:

        1
        <img src="http://evil.com/notexist" />

        那么,当浏览器尝试加载这张图片时,会向 http://evil.com 服务器发送一个请求。在这个请求的HTTP头中,会包含Referer字段,其值为上面代表删除操作的URL。

        攻击者通过查看Referer字段的内容,即可获取到包含敏感Token的URL,从而导致Token泄露。

        url中不是已经泄露了token嘛,为什么还要靠referer字段来获取token?

        因为url中的token只有用户自己能看到,而后面的图片加载将url写入referer,让攻击者可以接收到对应操作中的token

      • 还有其他的可能导致token泄露的方法,如XSS、跨域漏洞等

        CSRF的Token仅仅用于对抗CSRF攻击,当网站还同时存在XSS漏洞时,这个方案就会变得无效,因为XSS可以模拟客户端浏览器执行任意操作。

        在XSS攻击下,攻击者完全可以请求页面后,读出页面内容里的Token值,然后再构造出一个合法的请求。 这个过程可以称之为XSRF,和CSRF以示区分。