2019-3-13 dvwa学习(12)--Cross Site Request Forgery (CSRF),跨站点请求伪造

Cross Site Request Forgery (CSRF),跨站点请求伪造,它会让用户在当前经过身份验证的Web应用程序上执行攻击者预订的操作。CSRF攻击专门针对状态更改请求,而不是数据盗窃,因为攻击者无法看到对伪造请求的响应。恶意代码会通过电子邮件或伪装网站,甚至直接链接形式发送给用户,诱骗Web应用程序的用户执行攻击者设定的操作。

如果受害者是普通用户,成功的CSRF攻击会迫使用户执行状态更改请求,如转移资金、更改其电子邮件地址等。如果受害者是管理帐户,CSRF可以危害整个Web应用程序。

当用户的身份认证信息(cookie、会话等)尚未失效,此时他向服务器发送请求是受到信任的,所以恶意操作(如转账、改密等)就会被服务器执行。

下面看实例
low
先操作一下,看结果
在这里插入图片描述
其实就是执行了浏览器中的URL就完成了密码修改。
看一下源码

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

?> 

这里唯一干的事情就是比较新旧密码是否正确。修改的用户来自dvwaCurrentUser()函数。在dvwaPage.inc.php中可以查到这个函数和关联函数。

function dvwaCurrentUser() {
        $dvwaSession =& dvwaSessionGrab();
        return ( isset( $dvwaSession[ 'username' ]) ? $dvwaSession[ 'username' ] : '') ;
}
function &dvwaSessionGrab() {
        if( !isset( $_SESSION[ 'dvwa' ] ) ) {
                $_SESSION[ 'dvwa' ] = array();
        }
        return $_SESSION[ 'dvwa' ];
}
function dvwaLogin( $pUsername ) {
        $dvwaSession =& dvwaSessionGrab();
        $dvwaSession[ 'username' ] = $pUsername;
}

其实用户信息在登录时候会写入session的。攻击者要利用的就是这个保存在浏览器中的信息。
我们可以新建一个test.html文件,内容如下

<img src="http://172.25.137.226/vulnerabilities/csrf/?password_new=456&password_conf=456&Change=Change#" border="0" style="display:none;"/>

<h1>404</h1>

<h2>file not found.</h2>

此时,如果用户在同一浏览器中打开了以上html文件,显示内容如下

在这里插入图片描述
用户只会认为网页问题,不会意识到实际他的登录密码已经被修改为456了。
此时可以尝试登录dvwa,密码123已经无效了,只能用456登录。
不过,如果换一个浏览器来打开,那就没有用了。

medium
查看源码

<?php

if( isset( $_GET[ 'Change' ] ) ) {
    // Checks to see where the request came from
    if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
        // 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);
}

?> 

增加了以下代码

stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false

说明如下:

  • $_SERVER[‘HTTP_REFERER’],获取当前链接的上一个连接的来源地址,即链接到当前页面的前一页面的 URL 地址,可以做到防盗链作用。只有点击超链接(即< A href=…>) 打开的页面才有HTTP_REFERER环境变量。

通常下面的一些方式,$_SERVER[‘HTTP_REFERER’] 会无效(为空):

1.直接输入网址访问该网页。
2.Javascript 打开的网址。
3.Javascript 重定向(window.location)网址。
4.使用 meta refresh 重定向的网址。
5.使用 PHP header 重定向的网址。
6.flash 中的链接。
7.浏览器未加设置或被用户修改。

所以一般来说,只有通过 < a></ a> 超链接,以及POST或GET表单访问的页面,$_SERVER[‘HTTP_REFERER’] 才有效。

  • $_SERVER[ ‘SERVER_NAME’ ],当前服务器

我们操作一下,看看结果

在这里插入图片描述
应该知道意思了吧。假设服务器地址是172.25.100.100,那么在提交http请求的referer中也必须出现这个地址字符串。
既然是字符串匹配,那就简单了,只需要把上面test.html改为172.25.100.100.html,然后放入自己伪装的网站就行了。反正只要referer字符串中有172.25.100.100就成功了。

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();

?> 

这次多了 checkToken,那么如何获得token就是关键了。
我们可以尝试在dvwa中提交一下,可以看到URL如下

http://172.25.100.100/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change&user_token=2acfea0dbe4dcb19e2a1843319f66e88#

看到嘛,多了一个user_token参数。
那么如何获得token呢?
查了网上攻略,此时必须结合XSS来实现。
还记得在反射型XSS攻击 介绍过,dvwa的high级别XSS攻击防御中,只是屏蔽了< script>标签,其他标签依然有效。
所以,此时在浏览器中输入以下URL

http://172.25.100.100/vulnerabilities/xss_r/?name=%3Ciframe+src%3D%22..%2Fcsrf%2F%22+onload%3Dalert%28frames[0].document.getElementsByName%28%27user_token%27%29[0].value%29%3E#

显示以下界面
在这里插入图片描述
获得了token。后面该怎么做就不用说了吧。
也就是说,其实如果网页代码中加了token验证,那就需要结合XSS攻击才能CSRF攻击。XSS漏洞如果不存在,那也就这里的CSRF也安全了。

impossible
看源码

<?php

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

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

    // Sanitise current password input
    $pass_curr = stripslashes( $pass_curr );
    $pass_curr = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_curr ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
    $pass_curr = md5( $pass_curr );

    // Check that the current password is correct
    $data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
    $data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
    $data->bindParam( ':password', $pass_curr, PDO::PARAM_STR );
    $data->execute();

    // Do both new passwords match and does the current password match the user?
    if( ( $pass_new == $pass_conf ) && ( $data->rowCount() == 1 ) ) {
        // It does!
        $pass_new = stripslashes( $pass_new );
        $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 database with new password
        $data = $db->prepare( 'UPDATE users SET password = (:password) WHERE user = (:user);' );
        $data->bindParam( ':password', $pass_new, PDO::PARAM_STR );
        $data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
        $data->execute();

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

// Generate Anti-CSRF token
generateSessionToken();

?> 

其实就是多了一个现在密码。如下图。
在这里插入图片描述

意思是,如果攻击者不知道现在的密码,是不能修改密码的。这个防御逻辑很不错,如果攻击者已经知道现在密码,也不需要CSRF了。

猜你喜欢

转载自blog.csdn.net/weixin_42555985/article/details/88533752