Cross Site Request Forgery (CSRF)
跨站请求伪造
0x00 LOW Level
最初想直接尝试SQL注入,然后发现大部分特殊字符被转义了,原因在于函数mysqli_real_escape_string
源码:
<?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);
}
?>
问题:什么是跨站
浏览器打开的一个页面其中信息被其他的网站修改(不同域之间相互请求资源,就算跨域)
比如说你现在打开了支付宝,登陆了自己的用户账号,看了一圈花呗,然后果断下线,如果此时你的Cookie等啥身份验证还没有过期,那么攻击者就可以伪造一个页面来诱惑你点击,在你神不知鬼不觉的情况下修改你的密码,让你无法再登陆自己的支付宝账号。
关于同源策略:
一个网页设置的cookie,另一个网页不能打开,除非这两个网页同源。
同源又是指:
- 协议相同
- 域名相同
- 端口相同
但是CSRF就是可以做到违反同源策略,仍可以依靠尚未过期的身份打开页面进行操作(就比如在接下来的操作中,我们Burpsuite生成的html文件是存放在攻击机的,因此通过file协议读取:file:///C:/Users/xxxx/Desktop/CRSF.html首先协议就不相同了)
重点
我们可以通过Burpsuite进行抓包
将HTML文件保存在本地,构造CSRF url(这个需要相同的浏览器打开,因为不同的浏览器cookie不同,没办法进行身份的盗用)
这里最开始测试的时候我们,可以在首部url中看到相关的GET方式得到的数据,
http://127.0.0.1/DVWA-master/vulnerabilities/csrf/?password_new=1&password_conf=1&Change=Change#
然后我们可以修改password的值然后再打开一个新的页面,就会发现之前的密码不管用了,这个可以作为一个初步的尝试,但是我觉得无法很好的展示其违反的同源的特性,尤其是都在同一个设备上测试的时候。
0x01 Medium_Level
源码:
<?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);
}
?>
我们注意到多了一个判断
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false )
什么是HTTP_REFERER
关于Referer:表明了请求的来源。我们学习计网的过程中了解过了,关于网络上访问浏览器的请求会传输数据包,然后在应用层采用http协议中Referer是其Header的一部分,你访问的网站也是通过它来统计网站的流量的。
目的:它存在的作用一般是为了防盗链接,就是我要访问的网站只允许自己的域名访问,不被允许就会被拦截
就比如此时我们在打开一个新的页面更改url是不被允许的,因为我们通过F12可以看到新的网页不含referer。
解决
因此,我们可以抓包后,手动加入Referer。
0x02 High_Level
源码:
<?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();
?>
发现此时多出来了许多判断,
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;
}
}
多出来了一个token,
http://127.0.0.1/DVWA-master/vulnerabilities/csrf/?password_new=1&password_conf=1&Change=Change&user_token=2361956e672e361c3a9a3436a47105d1#
什么是token
:Anti-CSRF token机制,用户每次访问修改密码的页面时,服务器都会返回一个随机的token,当浏览器向服务器发起请求,需要提交token参数,而服务器在收到请求时,会优先检查token,只有token正确,才会处理客户端的请求。
因此它比referer更安全
这里Burpsuite可以帮我们直接完成token的获取
安装好插件后修改,就可以进行修改密码。
重点
- 什么是跨站(不同域的资源请求)| 什么是同源(三个特性相同即为同源)
- Referer的作用:用于指出域名来源
- token作用:安全性更近一步