简介:CSRF(Cross-site request forgery),CSRF即跨站请求攻击是指攻击者通过一些技术手段欺骗用户浏览器去访问一个自己以前认证过的站点并运行一些操作(如发邮件,发消息,如转账和购买商品甚至财产操作),因为浏览器之前认证过,所以被访问的站点会认为这是真正的用户操作而去运行,利用web中用户身份认证验证漏洞简单的身份验证仅仅能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。
原理:攻击者盗用了你的身份,以你的名义发送恶意请求,CSRF能够做的事情包含:以你的名义发送邮件;发消息;盗取你的账号;甚至于购买商品、虚拟货币转账......造成包含个人隐私泄露以及财产安全的泄露.
CSRF导图:
分析:1.登录受信任网站A,并在本地生成Cookie;2.在不登出A的情况下,访问危险网站B;3.你不能保证你登录了一个网站后,不再打开一个tab页面并访问另外的网站;4.你不能保证你关闭浏览器了后,你本地的Cookie立刻过期,你上次的会话已经结束;5.上图中所谓的攻击网站,可能是一个存在其他漏洞的可信任的经常被人访问的网站.
案例一:假如博客园有个加关注GET接口,blogUserGuid参数很明显是关注人ID:
http://www.cnblogs.com/mvc/Follow/FollowBlogger.aspx?blogUserGuid=4e8c33d0-77fe-df11-ac81-842b2b196315
攻击者在一篇博文内容里面写一个img标签:
案例二:假如博客园还是有个加关注的接口,不过已经限制了只获取POST请求数据,攻击者构建一个第三方的钓鱼页面,代码包含form提交代码,然后通过QQ、邮箱等社交工具传播,诱惑用户去打开链接,凡是打开过博客园的用户就已中招.
iframe页面:
document.forms.form1.submit();
注:由于同源策略,iframe内容根本加载不出来,所以里面form提交当然不会执行,尝试IE12、Chrome、Firefox,情况都是这样.
利用嵌多一层页面方式解决,第一个展示页面:
第二个隐藏页面:
document.forms.form1.submit();
注:加多一层iframe,因为不嵌iframe页面会重定向页面,这样就降低了攻击的隐蔽性,另外构造的test页面不使用XMLHTTPRequest发送POST请求,是因为有跨域问题,而form可以跨域post数据.
案例三:
假如博客园还是有个加关注的接口,已经限制POST,但博文内容是直接贴进未过滤的HTML,那就会遭受XSS攻击,直接把上面代码嵌入博文,那么只要有目标打开这篇博文,还是会自动关注我,这组合攻击方式称为XSRF.
1.GET类型CSRF请求伪造:仅仅须要一个HTTP请求,就能够构造一次简单的CSRF,案例一:
银行站点A,以GET请求来完毕银行转账操作,如:http://www.mybank.com/Transfer.php?toBankId=11&money=1000
危急站点B:里面有一段HTML代码例如以下:![]()
2.简单级别CSRF攻击:假设某游戏网站的虚拟币转账是采用GET方式进行操作,案例二:
http://www.game.com/Transfer.php?toUserId=11&vMoney=1000,此时恶意攻击者网站也构建一个相似的链接:采用图片隐藏,页面一打开就自动进行访问第三方文章:;采用JavaScript进行相应操作:
http://www.game.com/Transfer.php?toUserId=20&vMoney=1000 #toUserID为攻击账号ID,假若客户端已经验证并登陆www.game.com网站,此时客户端浏览器保存了游戏网站的验证cookie,客户端再tab另一个页面进行访问恶意攻击者的网站,并从恶意攻击者的网站构造的链接来访问游戏网站,浏览器将会携带该游戏网站的cookie进行访问,刷一下就没了1000游戏虚拟币.
3.中级别CSRF攻击,案例三:
游戏网站负责人认识到了有被攻击的漏洞,将进行升级改进,将由链接GET提交数据改成了表单提交数据.
//提交数据表单
Transfer.php
1
2 session_start();
3 if (isset($_REQUEST['toUserId'] && isset($_REQUEST['vMoney'])) #验证
4 {
5 //相应的转账操作
6 }
7 ?>
恶意攻击者将会观察网站的表单形式,并进行相应的测试,首先恶意攻击者采用http://www.game.com/Transfer.php?toUserId=20&vMoney=1000进行测试,发现仍然可以转账,那么此时游戏网站所做的更改没起到任何的防范作用,恶意攻击者只需要像上面那样进行攻击即可达到目的.
总结:1.网站开发者的错误点在于没有使用$_POST进行接收数据,当$_REQUEST可以接收POST和GET发来的数据,因此漏洞就产生.
4.高级别CSRF攻击,案例四:
游戏网站开发者又再一次认识到了错误,将进行下一步的改进与升级,将采用POST来接收数据.
Transfer.php
1
2 session_start();
3 if (isset($_POST['toUserId'] && isset($_POST['vMoney'])) #验证
4 {
5 //相应的转账操作
6 }
7 ?>
此时恶意攻击者就没有办法进行攻击了么?那是不可能的,恶意攻击者可以根据游戏虚拟币转账表单进行伪造了一份一模一样的转账表单,并且嵌入到iframe中.
嵌套页面,目标访问恶意攻击者主机的页面,即tab新页面:
function csrf()
{
window.frames['steal'].document.forms[0].submit();
}
表单页面:(csrf.html):
客户端访问恶意攻击者的页面一样会遭受攻击.
总结:CSRF攻击是源于Web隐式身份验证机制,Web身份验证机制虽然可以保证一个请求是来自于某个用户的浏览器,但却无法保证该请求是用户批准发送的.
5.POST类型CSRF,案例五:
银行网站A,它以GET请求来完成银行转账操作,如:http://www.mybank.com/Transfer.php?toBankId=11&money=1000;危险网站B,里面有一段HTML代码如下:![]()
首先,你登录了银行网站A,然后访问危险网站B,噢,这时你会发现你的银行账户少了1000块......为什么会这样呢?原因是银行网站A违反了HTTP规范,使用GET请求更新资源,在访问危险网站B的之前,你已经登录了银行网站A,而B中的以GET的方式请求第三方银行网站,原本这是一个合法的请求,但这里被不法分子利用了,所以你的浏览器会带上你的银行网站A的Cookie发出Get请求,去获取“http://www.mybank.com/Transfer.php?toBankId=11&money=1000”,结果银行网站服务器收到请求后,认为这是一个更新资源转账操作,所以就立刻进行转账操作......
案例六:
为了杜绝上面的问题,银行决定改用POST请求完成转账操作,银行网站A的WEB表单如下:
后台处理页面Transfer.php如下:
session_start();
if (isset($_REQUEST['toBankId'] && isset($_REQUEST['money']))
{
buy_stocks($_REQUEST['toBankId'], $_REQUEST['money']);
}
?>
危险网站B,仍然那句包含HTML代码:
和案例六中的操作一样,你首先登录了银行网站A,然后访问危险网站B,结果你再次没了1000块~T_T,这次事故的主要原因是:银行后台使用了$_REQUEST函数去获取请求的数据,而$_REQUEST函数既可以获取GET请求的数据,也可以获取POST请求的数据,这就造成了在后台处理程序无法区分这到底是GET请求的数据还是POST请求的数据。在PHP中,可以使用$_GET函数和$_POST函数分别获取GET请求和POST请求的数据。在JAVA中,用于获取请求数据request一样存在不能区分GET请求数据和POST数据的问题.
案例七:
经过前面2个惨痛的教训,银行决定把获取请求数据的方法也改了,改用$_POST函数,只获取POST请求的数据,后台处理页面Transfer.php代码如下:
session_start();
if (isset($_POST['toBankId'] && isset($_POST['money']))
{
buy_stocks($_POST['toBankId'], $_POST['money']);
}
?>
危险网站B代码:
function steal()
{
iframe = document.frames["steal"];
iframe.document.Submit("transfer");
}
注:如果用户仍是继续上面的操作,很不幸,结果将会是再次不见1000块......因为这里危险网站B暗地里发送了POST请求到银行!
总结:上面三个例子当中,CSRF主要的攻击模式基本上是以上三种,其中以第一、二种最为严重,因为触发条件很简单,一 个就可以了,而第三种比较麻烦,需要使用JavaScript,所以使用的机会会比前面的少很多,但无论是哪种情况,只要触发了CSRF攻击,后果都有可能很严重,理解上面的三种攻击模式,其实可以看出,CSRF攻击是源于WEB的隐式身份验证机制,WEB身份验证机制虽然可以保证一个请求是来自于某个用户的浏览器,但却无法保证该请求是用户批准发送的!
防御CSRF:
1.提交验证码,在表单中添加一个随机的数字或字母验证码,通过强制用户和应用进行交互,来有效地遏制CSRF攻击;2.Referer Check,检查假设是非正常页面过来的请求,则极有可能是CSRF攻击;3.token验证:在HTTP请求中以参数的形式添加一个随机产生的token,并在服务器端建立一个拦截器来验证这个token,假设请求中没有token或者token内容不对,则有可能是CSRF攻击而拒绝该请求所致!5.token必须足够随机;6.敏感的操作应该使用POST,而不是GET,比如表单提交;7.在HTTP头中自定义属性并验证,使用 token并进行验证 这里并非把token以参数的形式置于HTTP请求之中,而是把它放到 HTTP头中自定义属性里,通过 XMLHttpRequest类一次性给全部该类请求加上csrftoken,这个HTTP头属性并把 token值放入当中,这样攻克上种方法在请求中添加token的不便,同时通过 XMLHttpRequest类请求的地址不会被记录到浏览器的地址栏,也不用操心token会透过Referer泄露到其它站点中去.
关于Web跨域Post使用token思路:
1.移动端登录时,服务端验证表单信息,登录成功后生成token,返回给client;2.client将token保存在localstorage/sessionstorage中,每次提交表单,必须要携带token;3.服务端获取请求,如果没有token,构造令牌类Token.calss.php,代码如下:
class Token
{
/**
* @desc 获取随机数
*
*@return string 返回随机数字符串
*/
private function getTokenValue()
{
return md5(uniqid(rand(), true).time());
}
/**
* @desc 获取秘钥
*
* @param $tokenName string | 与秘钥值配对成键值对存入session中(标识符,保证唯一性)
*
* @return array 返回存储在session中秘钥值
*/
public function getToken($tokenName) {
$token['name']=$tokenName;
#先将$tokenName放入数组中
session_start();
if(@$_SESSION[$tokenName])
#判断该用户是否存储了该session
{
#是,则直接返回已经存储秘钥 $token['value']=$_SESSION[$tokenName];
return $token;
}
else
#否,则生成秘钥并保存
{
$token['value']=$this->getTokenValue(); $_SESSION[$tokenName]=$token['value'];
return $token;
}
}
}
#测试
$csrf=new Token();
$name='form1';
$a=$csrf->getToken($name);
echo "
";";print_r($a);
echo "
echo "
";";die;print_r($_SESSION);
echo "
?>
表单中使用:
session_start();
include(”Token.class.php”);
$token=new Token();
$arr=$token->getToken(‘transfer’);
#保证唯一性标识符
?>