csrf 跨站请求伪造
基本概念
是一种允许攻击者通过受害者发送任意HTTP请求的一类攻击方法。此处所指的受害者是一个不知情的同谋,所有的伪造请求都由他发起,而不是攻击者。这样,很你就很难确定哪些请求是属于跨站请求伪造攻击
攻击演示
如下表单
<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" />
由于buy.php脚本使用$_REQUEST而不是$_POST,这样每一个只要是登录在store.example.org商店上的用户就会通过请求该URL购买50支铅笔。
跨站请求伪造攻击的存在是不推荐使用$_REQUEST的原因之一。
防范方法
- 使用POST方式而不是使用GET来提交表单,在处理表单提交时使用$_POST而不是$_REQUEST
- 验证表单来自真正的页面,而不是伪造的。需要验证。
生成表单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>
一个跨站请求伪造攻击就必须包括一个合法的验证码以完全模仿表单提交。由于验证码的保存在用户的session中的,攻击者必须对每个受害者使用不同的验证码。这样就有效的限制了对一个用户的任何攻击,它要求攻击者获取另外一个用户的合法验证码。使用你自己的验证码来伪造另外一个用户的请求是无效的。
该验证码可以简单地通过一个条件表达式来进行检查:
<?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。可以更安全,同时也可以防止表单重复提交。
使用cookei验证
如果我们不考虑用户的Cookies很容易由于网站中存在XSS漏洞而被偷窃(我们已经知道这样的事情并不少见)这一事实,这是一个很好的应对对CSRF的解决方案。如果我们为用户的每一个表单请求中都加入随机的Cookies,那么这种方法会变得更加安全,但是这并不是十分合适
<?php // Hash the cookie $hash = md5($_COOKIE['cookie']); ?> <form method="POST" action="resolve.php"> <input type="text" name="first_name"> <input type="text" name="last_name"> <input type="hidden" name="check" value="<?=$hash;?>"> <input type="submit" name="submit" value="Submit"> </form> <?php // Check if the "check" var exists if(isset($_POST['check'])) { $hash = md5($_COOKIE['cookie']); // Check if the values coincide if($_POST['check'] == $hash) { do_something(); } else { echo "Malicious Request!"$$ } } else { echo "Malicious Request!"$$ } ?>
这个方案的思路是:每次的用户提交都需要用户在表单中填写一个图片上的随机字符串,厄....这个方案可以完全解决CSRF,但个人觉得在易用性方面似乎不是太好