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:

<!--?php
    setcookie('uid', 1, time()+86400);
    echo "your uid is {$_COOKIE['uid']}";
-->

pay.php

<!--?php
   //身份验证
    if (!isset($_COOKIE['uid']) || $_COOKIE['uid']&lt; 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

<!--?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字段

所以我们就可以在代码中加入判断

if  (HTTP_REFERER!="")  {
    die('可能是CSRF攻击,拒绝访问');
} else {
    die('允许访问');
}

所以我们只需拦截referer字段就可以判断是否为攻击。

但是这种方法是有缺陷的,存在referer的时候是在黑客的网站进行跳转,但是如果受害人通过qq点击链接呢,这样也相当于主动点击,此时也没有referer。也就绕过了过滤。而且部分低版本ie浏览器存在漏洞,referer可能被篡改,所以这个方案并不可靠。

服务端验证请求的token一致性

CSRF之所以可以成功,是因为用户的cookie被保存在了浏览器中间,而cookie包含用户验证信息。这样服务器便无法判断请求的真假。而token机制之所以可以防止CSRF,就是因为其是CSRF中最不可能被伪造的东西。因为它是随机的。

实现的原理:在服务端生成一个随机的token,加入HTTP请求中作为凭证,服务器首先拦截请求验证token是否和服务端一致,若一致,通过请求,不一致则拒绝。

新增 form.php表单页面,将token存入session,这里不能存到cookie,因为存入cookie如果被攻击者拿到利用就白搭了。

form.php

<?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

<!--?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是永久机制,那么依然存在被攻击的危险。

0x05 结语

参考: https://cloud.tencent.com/developer/article/1004943

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注