CSRF全称Cross-Site Request Forgery,跨站请求伪造攻击。
其攻击原理是:
攻击者在用户浏览网页时,利用页面元素(例如img的src),强迫受害者的浏览器向Web应用程序发送一个改变用户信息的请求。
由于发生CSRF攻击后,攻击者是强迫用户向服务器发送请求,所以会造成用户信息被迫修改,更严重者引发蠕虫攻击。
CSRF攻击可以从站外和站内发起。
从站内发起CSRF攻击,需要利用网站本身的业务,比如“自定义头像”功能,恶意用户指定自己的头像URL是一个修改用户信息的链接,当其他已登录用户浏览恶意用户头像时,会自动向这个链接发送修改信息请求。
从站外发送请求,则需要恶意用户在自己的服务器上,放一个自动提交修改个人信息的htm页面,并把页面地址发给受害者用户,受害者用户打开时,会发起一个请求。
如果恶意用户能够知道网站管理后台某项功能的URL,就可以直接攻击管理员,强迫管理员执行恶意用户定义的操作。
攻击
下面我们举个例子。
一个没有CSRF安全防御的代码如下:
$userid=$_SESSION["userid"];
$email=$_REQUEST["email"];
$tel=$_REQUEST["tel"];
$realname=$_REQUEST["realname"];
$params = array();
$params[0] =$email;
$params[1] = $tel;
$params[2] = $realname;
$params[3] =$userid;
$sql = "update user set email=?,tel=?,realname=? where userid=?";
execUpdate($sql,$params);
代码中接收用户提交的参数“email,tel,realname”,之后修改了该用户的数据,一旦接收到一个用户发来的请求,就执行修改操作。
提交表单代码:
<form action="http://localhost/servlet/modify" method="POST">
<input name="email">
<input name="tel">
<input name="realname">
<input name="userid">
<input type="submit">
</form>
当用户点提交时,就会触发修改操作。
下面我们说说如何攻击这么一个网站。如果该网站啊alibaba.com的一个部分,那么我们可以构建两个html页面:
1. 第一个页面a.html,其中通过iframe指向b.html,把宽度和高度都设为0:
<iframe src="b.htm" width="0" height="0"></frame>
这是为了当攻击发生时,受害用户看不到提交成功结果页面。
2. 页面b.html中,有一个表单和一段脚本,脚本的作用是,当页面加载时自动提交这个表单:
<form id="modify" action="http://alibaba.com/servlet/modify" method="POST">
<input name="email">
<input name="tel">
<input name="realname">
<input name="userid">
<input type="submit">
</form>
<script>
document.getElementById("modify").submit();
</script>
注意表单的指向是alibaba.com/servlet/modify
3. 将a.html放在自己的web服务器上,发送给登录用户即可:
4. 用户打开a.html后,会自动提交表单,发送给alibaba.com下那个存在CSRF漏洞的Web应用,所以用户的信息,就被迫修改了。而且整个攻击过程中,受害者仅仅看到一个空白页面,且不知道自己信息已被修改。
防御
有攻击就有防御,要防御CSRF工具,必须遵循以下三步:
1. 在用户登录时,设置一个随机的TOKEN也就是令牌,同时种植在用户的cookie中,当用户浏览器关闭、或用户再次登录时,清除TOKEN:
<?php
//构造加密的Cookie信息
$value = “DefenseSCRF”;
setcookie(”cookie”, $value, time()+3600);
?>
植入令牌到cookie中。
2. 在表单中生成一个隐藏域,它的值就是cookie中随机TOKEN:
<?php
$hash = md5($_COOKIE['cookie']);
?>
<form action="http://localhost/servlet/modify"method="POST">
<input type=”hidden” name=”hash” value=”<?=$hash;?>”>
<input name="email">
<input name="tel">
<input name="realname">
<input name="userid">
<input type="submit">
</form>
通过md5加密将客户端cookie中令牌值传递到服务器端。
3. 表单被提交后,就可以判断表单中的TOKEN和用户cookie中的TOKEN是否一致,如果不一致或没有这个值,就是CSRF攻击:
<?php
if(isset($_POST['hash'])) {
$hash = md5($_COOKIE['cookie']);
if($_POST['hash'] == $hash) {
doJob();
} else {
//...
}
} else {
//...
}
?>
以上就是防御CSRF的基本方法了。那么还有几个问题:
1. 如果表单不是post而是get方式提交呢?
如果用get方式提交,TOKEN会出现在url中,攻击者可以引诱攻击者点击自己的网站,然后从http头部中的referrer 的url来获取TOKEN,再来攻击。
2. 为什么不直接验证referrer?
网站内部也可能出现CSRF攻击,而referrer 只能判断是否来自可信任网站。
3. 如果先发生XSS攻击,攻击者可以拿到用户页面的TOKEN怎么办?
CSRF防御是建立在XSS防御之后的防御,如果XSS防御失效,攻击者可以拿到用户页面所有信息,CSRF当然会失去效果。当然我们还是可以防止这种情况的发生,就是在表单提交的时候要求输入验证码。这样就可以防御啦。