前端工程师所需要知道的CSRF跨站请求伪造攻击

@kvkens 2016-08-14 13:57:18发表于 iuap-design/blog

CSRF(Cross-site request forgery跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,并且攻击方式几乎相左。XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。

基本概念

  1. 是一种允许攻击者通过受害者发送任意HTTP请求的一类攻击方法。此处所指的受害者是一个不知情的同谋,所有的伪造请求都由他发起,而不是攻击者。这样,很你就很难确定哪些请求是属于跨站请求伪造攻击
  2. 攻击演示如下表单:
<form action="buy.php" method="POST">
  <p>
  Item:
  <select name="item">
    <option name="pen">pen</option>
    <option name="pencil">pencil</option>
  </select><br />
  Quantity: <input type="text" name="quantity" /><br />
  <input type="submit" value="Buy" />
  </p>
</form> 
  • php请求处理
<?php

  session_start();
  $clean = array();

  if (isset($_REQUEST['item'] && isset($_REQUEST['quantity']))
  {
    /* Filter Input ($_REQUEST['item'], $_REQUEST['quantity']) */

    if (buy_item($clean['item'], $clean['quantity']))
    {
      echo '<p>Thanks for your purchase.</p>';
    }
    else
    {
      echo '<p>There was a problem with your order.</p>';
    }
  }

?>
  • 攻击者尝试使用get方式:http://store.example.org/buy.php?item=pen&quantity=1
    如果能成功的话,攻击者如果取得了当合法用户访问时,可以引发购买的URL格式。在这种情况下,进行跨站请求伪造攻击非常容易,因为攻击者只要引发受害者访问该URL即可。攻击者并不需要取得用户授权
  • 用户再访问其他网站的时候,如果这个网站引导用户发起了上面的请求,浏览器会带上用户相关的cookie,相当于非法网站取得了用户授权。
  • 虽然有多种发起跨站请求伪造攻击的方式,但是使用嵌入资源如图片的方式是最普遍的。为了理解这个攻击的过程,首先有必要了解浏览器请求这些资源的方式。
  • 当你访问 http://www.google.com ,你的浏览器首先会请求这个URL所标识的资源。你可以通过查看该页的源文件(HTML)的方式来看到该请求的返回内容。在浏览器解析了返回内容后发现了Google的标志图片。这个图片是以HTML的img标签表示的,该标签的src属性表示了图片的URL。浏览器于是再发出对该图片的请求,以上这两次请求间的不同点只是URL的不同。
  • 根据上面的原理,跨站请求伪造攻击可以通过img标签来实现。考虑一下如果访问包括下面的源代码的网页会发生什么情况:
<img src="http://store.example.org/buy.php?item=pencil&quantity=50" />

防范方法

  1. 使用POST方式而不是使用GET来提交表单,在处理表单提交时使用$_POST而不是$_REQUEST

  2. 验证表单来自真正的页面,而不是伪造的。需要验证。

    • 生成表单token

      <?php
      
        session_start();
        $token = md5(uniqid(rand(), TRUE));
        $_SESSION['token'] = $token;
        $_SESSION['token_time'] = time();
      
      ?>
      
    • 提交表单的时候,token也以前提交到服务器。

    <form action="buy.php" method="POST">
      <input type="hidden" name="token" value="<?php echo $token; ?>" />
      <p>
      Item:
      <select name="item">
        <option name="pen">pen</option>
        <option name="pencil">pencil</option>
      </select><br />
      Quantity: <input type="text" name="quantity" /><br />
      <input type="submit" value="Buy" />
      </p>
    </form>
    
  3. 一个跨站请求伪造攻击就必须包括一个合法的验证码以完全模仿表单提交。由于验证码的保存在用户的session中的,攻击者必须对每个受害者使用不同的验证码。这样就有效的限制了对一个用户的任何攻击,它要求攻击者获取另外一个用户的合法验证码。使用你自己的验证码来伪造另外一个用户的请求是无效的。

  4. 该验证码可以简单地通过一个条件表达式来进行检查:

<?php

  if (isset($_SESSION['token']) && $_POST['token'] == $_SESSION['token'])
  {
    /* Valid Token */
  }

?>
  • 你还能对验证码加上一个有效时间限制,如5分钟,并且让token只能使用一次:
<?php

  $token_age = time() - $_SESSION['token_time'];
  $token = $_SESSION['token'];
  unset($_SESSION['token']);
  if ($token_age <= 300)
  {
    /* Less than five minutes has passed. */
  }

?>
  • 通过在你的表单中包括验证码,你事实上已经消除了跨站请求伪造攻击的风险。可以在任何需要执行操作的任何表单中使用这个流程。

你也可以让token只能使用一次,在每次请求被请求后token通过后销毁这个token。可以更安全,同时也可以防止表单重复提交。

(完)