CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装成受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。

CSRF的简单例子

用DVWA CSRF模块Low等级的代码为例:

这是一个用户修改密码的界面:

Alt text

后端php源码如下:

 <?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);
}

?>

可以看到后端代码没有做任何用户身份的校验,而且新密码也是通过GET参数传递,于是攻击者可以构造这样一个钓鱼链接:

http://192.168.177.131/vulnerabilities/csrf/?password_new=admin&password_conf=admin&Change=Change#

当某用户自己登录后,在cookie失效前点击该链接,该用户的密码就会被修改为admin

那如果是新旧密码都是POST传参呢?那我们当然可以构造一个提交表单页面了:

<html>
    <body>
        <form name="DVWA" method="POST" action="http://www.target.com/csrf.php">
            <input type="text" name="password_new" value="admin">
            <input type="text" name="password_conf" value="admin">
        </form>
        <script>
         document.DVWA.submit();
         </script>
     </body>
 </html>

当该用户点击该页面后,该表单会自动提交,用户的密码自然也会被修改了

防御CSRF

验证HTTP请求头中的Referer

先来看HTTP referer的定义:

HTTP Referer是header的一部分,当浏览器向web服务器发送请求的时候,一般会带上Referer,告诉服务器该网页是从哪个页面链接过来的,服务器因此可以获得一些信息用于处理。

还是以DVWA CSRF medium模块的源码为例,medium相对于low添加了这样一句判断:

    if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ])!=-1 )

也就是说判断referer头中是否包含$_SERVER['SERVER_NAME'],举个例子:

比如该网站的域名为:*.bank.cn,如果请求中的referer头包含该字段,则认为用户是从本站点发起的访问,而不是因为在其他地方点击恶意链接才发出的请求

然而仅仅验证referer头是不安全的,比如medium等级的绕过方法是:

比如攻击者的服务器IP是1.1.1.1,而目标网站域名为www.bank.cn,那么攻击者在伪造页面时可以把钓鱼页面命名为:www.bank.cn.html,当用户从这个页面发送请求时,referer头的值为:http://1.1.1.1/www.bank.cn.html,这样就绕过了后端的判断

使用token防御CSRF

token即令牌,开发者可以在用户的session中加入token字段,在每次请求时都会先验证客户端传输的token是否与服务端一致,以此来进行身份验证

通常token都是随机数再进行MD5加密的,因此难以伪造

使用验证码或其他二次判断

使用验证码就可以很好的防御csrf,在用户操作时需要输入验证码,这样就会产生与用户的交互,用户就可以知道正在执行什么操作,但兼顾用户体验的话肯定不会每一个步骤都要求输入验证码

再或者如修改密码这种功能,在修改时一定要验证用户的原密码,这样攻击者就难以进行CSRF

CSRF绕过的各种技巧

绕过referer头验证可以参考这个:

https://www.ohlinge.cn/web/csrf_referer.html