浅谈CSRF漏洞
2020-03-30 10:54:52 Author: xz.aliyun.com(查看原文) 阅读量:211 收藏

0x01、什么是CSRF

CSRF(Cross Site Request Forgery),中文名称跨站点请求伪造 , 跟XSS攻击一样,存在巨大的危害性 。利用csrf,攻击者可以盗用你的身份,以你的名义发送恶意请求。 你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等。

0x02、CSRF攻击原理

1、Tom登录某银行网站,于是浏览器生成了Tom在该银行的的身份验证信息。

2、Jerry利用Sns.com服务器将伪造的转账请求包含在帖子中,并把帖子链接发给了Tom。

3、Tom在银行网站保持登录的情况下浏览帖子

4、这时Tom就在不知情的情况下将伪造的转账请求连同身份认证信息发送到银行网站。

5、银行网站看到身份认证信息,认为请求是Tom的合法操作

之后,银行网站会报据Tom的权限来处理Jerry所发起的恶意请求,这样Jerry就达到了伪造Tom的身份请求银行网站给自己转账的目的。

在此过程中受害者Tom只需要做下面两件事情,攻击者Jerry就能够完成CSRF攻击:

  • 登录受信任银行网站,并生成身份验证信息;

  • 在不登出银行网站(清除身份验证信息)的情况下,访问恶意站点Sns.com。

很多情况下所谓的恶意站点,很有可能是一个存在其他漏洞(如XSS) 的受信任且被很多人访问的站点,这样,普通用户可能在不知不觉中便成为了受害者。

0x03、CSRF攻击分类

CSRF漏洞一般分为站外和站内两种类型。

CSRF站内类型的漏洞在一定程度上是由于程序员滥用$_REQUEST类变量造成的,一些敏感的操作本来是要求用户从表单提交发起POST请求传参给程序,但是由于使用了$_REQUEST等变量,程序也接收GET请求传参,这样就给攻击者使用CSRF攻击创造了条件,一般攻击者只要把预测好的请求参数放在站内一个贴子或者留言的图片链接里,受害者浏览了这样的页面就会被强迫发起请求。

CSRF站外类型的漏洞其实就是传统意义上的外部提交数据问题,一般程序员会考虑给一些留言评论等的表单加上水印以防止SPAM问题,但是为了用户的体验性,一些操作可能没有做任何限制,所以攻击者可以先预测好请求的参数,在站外的Web页面里编写javascript脚本伪造文件请求或和自动提交的表单来实现GET、POST请求,用户在会话状态下点击链接访问站外的Web页面,客户端就被强迫发起请求。

0x04、CSRF漏洞检测

检测CSRF漏洞是一项比较繁琐的工作,最简单的方法就是抓取一个正常请求的数据包,去掉Refexex字段后再重新提交,如果该提交还有效,那么基本上可以确定存在CSRF漏洞。

随着对CSRF漏洞研究的不断深入,不断涌现出一些专[门针对CSRF漏洞进行检测的工具,如CSRFTester, CSRF Request Builder等。

以CSRFTester工具为例,CSRF漏洞检测工具的测试原理如下:使用CSRFTester进行测试时,首先需要抓取我们在浏览器中访问过的所有链接以及所有的表单等信息,然后通过在CSRFTester中修改相应的表单等信息,重新提交,这相当于一次伪造客户端请求。如果修改后的测试请求成功被网站服务器接受,则说明存在CSRF漏洞,当然此款工具也可以被用来进行CSRF攻击。

0x05、CSRF攻击实例

DVWA-CSRF

low级别

查看源代码:

<?php

if( isset( $_GET[ 'Change' ] ) ) {
    // Get input
    $pass_new  = $_GET[ 'password_new' ];
    $pass_conf = $_GET[ 'password_conf' ];

    // Do the passwords match?
    if( $pass_new == $pass_conf ) {
        // They do!
        $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
        $pass_new = md5( $pass_new );

        // Update the database
        $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
        $result = mysqli_query($GLOBALS["___mysqli_ston"],  $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

        // Feedback for the user
        echo "<pre>Password Changed.</pre>";
    }
    else {
        // Issue with passwords matching
        echo "<pre>Passwords did not match.</pre>";
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

经过简单的审计,发现该网站通过mysqli_real_escape_string()函数的过滤作用,将用户传入的数据中的特殊字符进行转义,对SQL注入做了防御。但没有对CSRF做任何防范措施。

正常输入密码,然后抓包

用burpsuite自带的CSRF PoC进行攻击

把CSRF HTML复制到本地,然后用该浏览器访问

点击提交请求后自动跳转到我们的页面,并且此时密码已被成功修改

以上过程中要注意的是一定不要中途更换浏览器,访问csrf.php的时要同一个浏览器访问,并且还要保证你登录DVWA的cookie没有过期,不然会因为缺少身份验证信息而执行失败。

Medium级别

查看源代码:

<?php

if( isset( $_GET[ 'Change' ] ) ) {
    // Checks to see where the request came from
    if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ])!=-1 ) {
        // Get input
        $pass_new  = $_GET[ 'password_new' ];
        $pass_conf = $_GET[ 'password_conf' ];

        // Do the passwords match?
        if( $pass_new == $pass_conf ) {
            // They do!
            $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
            $pass_new = md5( $pass_new );

            // Update the database
            $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
            $result = mysqli_query($GLOBALS["___mysqli_ston"],  $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

            // Feedback for the user
            echo "<pre>Password Changed.</pre>";
        }
        else {
            // Issue with passwords matching
            echo "<pre>Passwords did not match.</pre>";
        }
    }
    else {
        // Didn't come from a trusted source
        echo "<pre>That request didn't look correct.</pre>";
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

与low级别的相比,medium级别多了referer验证, referer验证限制了不是同一个域的不能跨域访问。

经过测试,发现只要referer存在127.0.0.1就可以绕过验证。

构造poc

这次我们把密码改成admin007并保存为127.0.0.1.html文件

然后用浏览器访问

点击提交请求后自动跳转到我们的页面,并且此时密码已被成功修改

High级别

查看源代码

<?php

if( isset( $_GET[ 'Change' ] ) ) {
    // Check Anti-CSRF token
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

    // Get input
    $pass_new  = $_GET[ 'password_new' ];
    $pass_conf = $_GET[ 'password_conf' ];

    // Do the passwords match?
    if( $pass_new == $pass_conf ) {
        // They do!
        $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
        $pass_new = md5( $pass_new );

        // Update the database
        $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
        $result = mysqli_query($GLOBALS["___mysqli_ston"],  $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

        // Feedback for the user
        echo "<pre>Password Changed.</pre>";
    }
    else {
        // Issue with passwords matching
        echo "<pre>Passwords did not match.</pre>";
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

// Generate Anti-CSRF token
generateSessionToken();

?>

经过分析发现High级别的代码加入了Anti-CSRF token机制,用户每次访问改密页面时,服务器会返回一个随机的token,向服务器发起请求时,需要提交token参数,而服务器在收到请求时,会优先检查token,只有token正确,才会处理客户端的请求。

这里单纯的利用CSRF是不行的,还要配合XSS才行,这里先不细说,等后面总结完XSS再回来看看。

0x06、CSRF防御之道

1、尽量POST

GET太容易被CSRF攻击了,用POST可以降低风险,但也不能保证万无一失, 攻击者只要构造一个form就可以,但需要在第三方页面做,这样就增加暴露的可能性。

2、加入验证码

在POST的基础上可以再加一个验证码,用户每次提交数据时都需要在表单中填写验证码,这个方案大幅度的降低CSRF攻击,一些简单的验证码可能会被hacker破解,但一般情况下,验证码是很难被破解的。

3、验证Referer

就像上面的Medium级别那样,在验证时添加一个Referer,判断请求的来源地址是否是当前网页,如果是,则可以认为该请求是合法的,否则就拒绝用户请求。

4、Anti CSRF Token

CSRF攻击之所以能够成功,是因为攻击者可以伪造用户的请求,该请求中所有的用户验证信息都存在于cookie中,因此攻击者可以在不知道用户验证信息的情况下直接利用用户的cookie来通过安全验证。由此可知,抵御CSRF攻击的关键在于:在请求中放入攻击者所不能伪造的信息,并且该信总不存在于cookie之中。

在开发过程中我们可以在HTTP请求中以参数的形式加入一个随机产生的token,并在服务端进行token校验,如果请求中没有token或者token内容不正确,则认为是CSRF攻击而拒绝该请求。

0x07、总结

通过总结才发现,原来CSRF里有这么多东西,之前总觉得CSRF没什么好写的,看来还是小白太眼高手低了,以后还是得多总结多写博客才行。


文章来源: http://xz.aliyun.com/t/7450
如有侵权请联系:admin#unsafe.sh