CSRF的原理、利用及防护
0x01 前言
基础漏洞梳理
0x02 概念
CSRF(cross-site request forgery),跨站请求伪造, 尽管听起来像跨站脚本(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装成受信任用户的请求来利用受信任的网站。
与XSS不同,CSRF则更难被利用,因为首先CSRF需要用户存在登陆凭证,且用户被欺骗点击攻击者构造的恶意url,才有可能触发。
通俗地讲,XSS更像是偷盗之事,通过偷盗高权限COOKIE进行操作,CSRF则更像借刀杀人。
0x03 利用
CSRF的攻击原理是什么,简单说就是利用了高权限帐号(如管理员)的登录状态或者授权状态去做一些后台操作,但实际这些状态并没有被我们直接获取到(获取那是XSS干的事)。
GET方式演示CSRF攻击
我们这里假设有个网站,这个网站包含一个登录页面(login.php)和一个付款页面(pay.php)
login.php:
-->pay.php
//身份验证
if (!isset($_COOKIE['uid']) || $_COOKIE['uid']< 0) {
die('login error!');
}
//金额获取
if (!isset($_GET['money'])) {
die('no money');
}
//收款人获取
if (!isset($_GET['to_who'])) {
die('nobody');
}
$uid = $_COOKIE['uid'];
$money = $_GET['money'];
$to_who = $_GET['to_who'];
//此处应该还有相关数据库操作,省去一万字
echo "transfer {$money} yuan to {$to_who}!";
用户访问index.php?uid=1页面
此时cookie已经产生了,也就是说服务器记住了用户uid=1,
然后用户访问pay.php页面进行转账操作:
http://192.168.150.131/csrf/pay.php?money=1000&to_who=father
成功转账,但是这个过程是没有任何CSRF防护的,所以我们可以利用
我们将转账人设置为hacker
http://192.168.150.131/csrf/pay.php?money=1000&to_who=hacker
但是自己去访问这个链接肯定是没用的,所以我们要诱导受害者点击该链接。直接将链接发给受害者很大概率被看出有问题,聪明的人也不会这样做。所以我们这里有以下两种方法,第一是制作短链接。
https://u8b3.cn/uSPzZ
另一种是伪造一个html页面
这样被点击的几率就大很多了。如果受害人安全意识不高就很可能攻击。
那为什么会出现这种情况呢,这是因为我们的受害人在登陆了该网站后,已经信任了这个网站,而当用户点击我们构造好的链接时,该网站会识别当前cookie未过期,正常登陆,所以就会执行转账操作,但是他并不知道到底是谁让受害者点击的。
从上面这个实例可知,完成CSRF攻击流程:
1、用户登录了信任的网站A,并且保存登录状态,cookie未失效。
2、黑客找出网站A没有防御的链接,通过社会工程学伪装,诱导点击。
3、只要登录状态保持,用户主动访问目标链接,则攻击成功。
既然GET方式可以造成CSRF,那POST怎么样,POST依旧可以,隐藏表单的方式我们见得多了,但是只要抓一下包什么都有了。
POST的CSRF攻击演示
示例代码:我们将GET获取方式转化为POST
pay.php
if (!isset($_COOKIE['uid'])) {
die('login error!');
}
if (!isset($_POST['money'])) {
die('no money');
}
if (!isset($_POST['to_who'])) {
die('nobody');
}
$uid = $_COOKIE['uid'];
$money = $_POST['money'];
$to_who = $_POST['to_who'];
if ($uid > 0) {
echo "transfer {$money} yuan to {$to_who}!";
}
访问 http://192.168.150.131/csrf/pay.php?money=1000&to_who=father
可见代码语句在第二个if结束,POST生效。
但是攻击者既然可以伪造GET请求,伪造POST请求也不是什么难事,只要加一个form表单即可。
我们将这个文件发给受害者,受害者点击该文件的按钮,攻击生效。用户金币-2000
如此一来,不管哪种访问方式都可能受到攻击。所以,这并不是GET和POST谁更安全的问题,POST只是提高了攻击门槛和成本(其实也就多几行html和js)。
所以CSRF能够攻击的根本原因是:服务器无法识别你的来源是否可靠。
0x04 修复方法
那我们如何防御CSRF攻击呢,有人说每次都把网站注销清除痕迹不就行了吗,没错确实可以这样,但是cookie不就是为了解决每次都输账号密码的烦恼吗。这样做无异于抛弃计算机去打算盘。所以解决的根源在于开发层而不在用户层。
防御的方法其实有很多
1、比如加上验证码。但这么做很繁琐,并且影响用户体验。
2、比如转账这种危险操作需要二次密码验证,现在很多银行和支付软件就是这么搞的。
3、确认来源是否可靠(最佳解)
可以看出来:方法一和方法二都是让用户自身再次确认授权。但是这种安全防范的事,其实应该由双方一起承担,用户保护好自己的信息,程序保证信息交换的安全性。根据验证是否可靠性思路,可以有以下几种方法。
验证HTTP Referer 字段
HTTP协议里面定义了一个访问来源的字段,这个字段叫Referer。黑客伪造的链接或表单是在其他网站上,所以我们可以判断Referer是否为自身网站,如果是,则允许访问,如果不是,则拒绝访问。
我们在本地访问pay.php抓包查看referer字段
发现并没有referer字段
假如从黑客的网站跳转到pay.php,便会存在referer字段
所以我们就可以在代码中加入判断
所以我们只需拦截referer字段就可以判断是否为攻击。
但是这种方法是有缺陷的,存在referer的时候是在黑客的网站进行跳转,但是如果受害人通过qq点击链接呢,这样也相当于主动点击,此时也没有referer。也就绕过了过滤。而且部分低版本ie浏览器存在漏洞,referer可能被篡改,所以这个方案并不可靠。
服务端验证请求的token一致性
CSRF之所以可以成功,是因为用户的cookie被保存在了浏览器中间,而cookie包含用户验证信息。这样服务器便无法判断请求的真假。而token机制之所以可以防止CSRF,就是因为其是CSRF中最不可能被伪造的东西。因为它是随机的。
实现的原理:在服务端生成一个随机的token,加入HTTP请求中作为凭证,服务器首先拦截请求验证token是否和服务端一致,若一致,通过请求,不一致则拒绝。
新增 form.php表单页面,将token存入session,这里不能存到cookie,因为存入cookie如果被攻击者拿到利用就白搭了。
form.php
session_start();
$csrf_token = md5(openssl_random_pseudo_bytes(32));//生成随机token
$_SESSION['token']= $csrf_token;
?>
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>csrf</title>
</head>
<body>
<h1>转账</h1>
<form action="pay.php" method="POST">
money:<input type="text" name="money">
to_who:<input type="text" name="to_who">
<input type="hidden" name="token" value="<?php echo $csrf_token ?>">
<input type="submit" value="ok">
</form>
</body>
</html>
openssl_random_pseudo_bytes()函数
openssl_random_pseudo_bytes()函数本身是用来生成指定个数的随机字节
在pay.php获取post传过来的token,与session中存储的token判断是否一致:
pay.php
session_start();
if (! isset($_COOKIE['uid'])) {
die('login error!');
}
if (! isset($_POST['money'])) {
die('no money');
}
if (! isset($_POST['to_who'])) {
die('nobody');
}
if (! isset($_POST['token']) || $_POST['token'] != $_SESSION['token']) {
die('forbidden');
}
$uid = $_COOKIE['uid'];
$money = $_POST['money'];
$to_who = $_POST['to_who'];
if ($uid > 0) {
echo "transfer {$money} yuan to {$to_who}!";
}
上述代码中,我们在form.php中加入了随机token代码,每次访问都生成一个token,并存入session中,然后当触发pay.php时,pay.php会判断通过post传到服务器的token与session中的值是否相同。以达到防御CSRF。
我们以登陆的姿态访问form.php
转账成功
然后我们再以黑客给的链接访问。
发现被阻断了
我们抓一下包看一下区别
首先是用户正常操作
可见,是存在token参数的。
然后我们抓取黑客构造的跳转链接
差别出来了:伪造的数据包没有token!,这样便完美的防御了CSRF
需要强调的是,token必须是随机机制,否则如果被攻击者盗取了token,且token是永久机制,那么依然存在被攻击的危险。