CSRF的中文叫:”跨站请求攻击“,它是通过仿照某一个特殊的网页(重置密码)来进行诱惑性攻击。
难度(low级)
审计代码:
<?phpif( 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 userecho "<pre>Password Changed.</pre>";}else {// Issue with passwords matchingecho "<pre>Passwords did not match.</pre>";}((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}?>
G L O B A L S :引用全局作用域中可用的全部变量。 GLOBALS :引用全局作用域中可用的全部变量。 GLOBALS:引用全局作用域中可用的全部变量。GLOBALS 这种全局变量用于在 PHP 脚本中的任意位置访问全局变量(从函数或方法中均可)。PHP 在名为 $GLOBALS[index] 的数组中存储了所有全局变量。变量的名字就是数组的键。
从源代码可以看出这里只是对用户输入的两个密码进行判断,看是否相等。不相等就提示密码不匹配。
相等的话,查看有没有设置数据库连接的全局变量和其是否为一个对象。如果是的话,用mysqli_real_escape_string()函数去转义一些字符,如果不是的话输出错误。
是同一个对象的话,再用md5进行加密,再更新数据库。
知道了这些之后,我们第一次尝试两次密码不一致看看。
两次密码不一致则提示,密码不一致不允许修改,并且我们在链接上可以看到修改的明文密码,那我们思考一个问题能不能用这个链接直接改密码呢?
密码链接:
http://172.31.63.146/DVWA/vulnerabilities/csrf/?
漏洞利用:
我们打开另一个页面,在顶部URL中自己输入如下的payload。
password_new=123456&password_conf=789456&Change=Change#
在浏览器中点开链接,提示密码修改成功。
低难度则证明咱们可以利用这个链接任意的修改密码了,实质上这里就存在csrf漏洞我们伪造了修改密码的链接,从而实现了任意密码修改。
难度(Medium)
审计代码
<?phpif( isset( $_GET[ 'Change' ] ) ) {// Checks to see where the request came fromif( 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 userecho "<pre>Password Changed.</pre>";}else {// Issue with passwords matchingecho "<pre>Passwords did not match.</pre>";}}else {// Didn't come from a trusted sourceecho "<pre>That request didn't look correct.</pre>";}((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}?>
Medium类型的代码在Low级别的基础上,加上了对用户请求头的中的Referer字段进行验证。
if( stripos( S E R V E R [ ′ H T T P R E F E R E R ′ ] , _SERVER[ 'HTTP_REFERER' ] , SERVER[′HTTPREFERER′],_SERVER[ ‘SERVER_NAME’ ]) !== false )
即用户的请求头中的Referer字段必须包含了服务器的名字。关于Http Referer字段。
当我们再打开另一个页面,在顶部URL中自己输入如下的时,http://172.31.63.146/DVWA/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change
它会报错,提示你:That request didn’t look correct。
我们使用burpsuite抓包分析一下:
在界面上可以修改成功,Referer字段限制从从哪里访问的来源。
Referer: http://172.31.63.146/DVWA/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change
提示修改成功:
password changed
我们任意浏览器打开链接没有Referer字段则显示修改失败:
That request didn’t look correct。
漏洞利用:
那么我思考一下在这个数据包上面伪造一个Referer头能不能利用成功呢?
把上述数据包发送到Repeater上加入。
Referer: http://172.20.10.2/dvwa/vulnerabilities/csrf/
提示密码修改成功,这表示我们能够通过伪造Referer头来实现跨站请求伪造,达到我们修改密码的目的。
难度(High)
源码解析
<?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 tokencheckToken( $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();
High级别的代码增加了Anti-CSRF token机制,用户每次访问改密页面时,服务器会返回一个随机的token,向服务器发起请求时,需要提交token参数,而服务器在收到请求时,会优先检查token,只有token正确,才会处理客户端请求
因为该请求是get请求,所以token验证会被放在请求URL中,我们随便输入密码验证一下,可以看到,在请求的URL中最末尾加入了token。
http://172.31.63.146/DVWA/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change&user_token=86c02ef72eb549a0f63d81d9e19784b2#
漏洞利用:
要绕过High级别的反CSRF机制,关键是要获取token,要利用受害者的cookie,去修改密码的页面,获取关键的token。
可以利用High级别的XSS漏洞协助获取Anti-CSRF token。
XSS注入有长度限制,不能够注入完整的攻击脚本,所以只获取Anti-CSRF token即可。
在XSS中使用以下payload获取Token。
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/958dce270014441f93a4731eb7fd0ddb.png) 将Token复制到修改密码的数据包中发现,完成修改。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/7c439b579abc4932a5120b2c9e7d419d.png)最后尝试登录一次成功
难度(Impossible)
源码分析
<?phpif( isset( $_GET[ 'Change' ] ) ) {// Check Anti-CSRF tokencheckToken( $_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;' );$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 ) ) {// 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 );$current_user = dvwaCurrentUser();$data->bindParam( ':user', $current_user, PDO::PARAM_STR );$data->execute();// Feedback for the userecho "<pre>Password Changed.</pre>";}else {// Issue with passwords matchingecho "<pre>Passwords did not match or current password incorrect.</pre>";}
}// Generate Anti-CSRF token
generateSessionToken();?>
漏洞复现
要求我们先输入密码再修改,攻击者不知道原始密码的情况下是无法发起 CSRF 攻击的。