DVWA靶机学习——CSRF(跨站请求伪造)

这个系列是学习DVWA靶机的。今天学习CSRF的Low、Medium、High、Impossible级别。

Posted by K4ys0n on July 14, 2020

0x00 CSRF跨站请求伪造

CSRF,全称Cross-site request forgery,翻译过来就是跨站请求伪造,是指利用受害者尚未失效的身份认证信息(cookie、会话等),诱骗其点击恶意链接或者访问包含攻击代码的页面,在受害人不知情的情况下以受害者的身份向(身份认证信息所对应的)服务器发送请求,从而完成非法操作(如转账、改密等)。CSRF与XSS最大的区别就在于,CSRF并没有盗取cookie而是直接利用。

DVWA中的CSRF是一个登录框,需要输入账号密码,利用CSRF攻击已登录用户来修改用户密码。

0x01 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);
}

?> 

只检查了password_new和password_conf是否相等,以及对输入字符进行了一些转义,但并未对CSRF做任何防御措施。

解题思路

  • 构造恶意链接http://127.0.0.1:9000/dvwa/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change

将这个链接在登录了DVWA账户的浏览器中访问,浏览器就会自动携带用户cookie访问链接并修改密码,实际CSRF攻击会模仿真实网站页面来混淆,不过链接还是会很明显,所以需要下面这种方法。

  • 使用短链接伪装

https://dwz.cn缩短网址,但是必须是带域名链接而不能是带IP地址的链接。

  • 构造恶意页面,使用img标签隐藏真实目的 ```html

404

file not found.

用户使用浏览器访问该页面时,就会被修改。如果用户是用A浏览器访问站点,又使用B浏览器访问恶意页面,是不会出发漏洞的。



## 0x02 Medium

#### 源码分析
```php
<?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’ ]),这个函数表示SERVER_NAME中要含有HTTP_REFERER字符串,HTTP_REFERER参数是Referer,SERVER_NAME是Host。

GET /dvwa/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change HTTP/1.1
Host: 127.0.0.1:9000		# $_SERVER[ 'HTTP_REFERER' ] 
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Referer: http://127.0.0.1:9000/dvwa/vulnerabilities/csrf/?password_new=password&password_conf=&Change=Change	# $_SERVER[ 'SERVER_NAME' ]
Cookie: security=medium; PHPSESSID=pmdmqodk88goddna16tm61td55
Upgrade-Insecure-Requests: 1

解题思路

  • 构造恶意HTML页面,并将HTML用目标主机IP命名。写入以下代码:
<img src="http://127.0.0.1:9000/dvwa/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change" border="0" style="display:none;" />
<h1>404</h1>
<h2>file not found.</h2>

最后访问这个文件:http://127.0.0.1:9000/dvwa/vulnerabilities/csrf/127.0.0.1.html即可。

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

?> 

多了Anti-CSRF token防御机制,每个请求都要带上唯一的token值才能成功提交。

解题思路

  • 构造恶意页面,代码如下 ```html
这段代码就是新建跳转子页面然后向服务器发送一个请求,从而得到最新的token值然后再进行提交。

保存上面的代码到high.html并放到dvwa/vulnerabilities/csrf/目录下(能访问到就行),然后我们访问http://127.0.0.1:9000/dvwa/vulnerabilities/csrf/high.html 这个链接,会发现一闪而过一个空白页面,然后我们退出登录DVWA,再重新登录就会发现默认密码被修改成123了。

- 构造存储XSS,如果存在存储XSS型漏洞的话也可以利用。



## 0x04 Impossible

#### 源码分析
```php
<?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();

?> 

可以看到除了Anti-CSRF token意外,还多了要输入旧密码。也就是在修改密码的时候要先输入原来的密码,这样攻击者就不能随意修改密码了,必须要知道旧密码才行。后面又多了些去除反斜杠、转义常见字符、sql预编译等等防止XSS和SQL注入的代码。基本上不可能攻击了。

解题思路

无。

0x05 小结

防御方法:

  • Anti-CSRF token 每次向客户端发送一个随机数,当客户端想服务端发送数据时,比对随机数以此来确定客户端身份。
  • 检查Referer,看是否包含主机或域名。
  • PDO预编译防止SQL注入。
  • 去除反斜杠,常见字符转义防御XSS或SQL注入。
  • 获取当前用户的密码,以此判断是否当前用户的操作,而非CSRF。

0x06 参考

https://www.freebuf.com/articles/web/118352.html