安全性测试入门 (三):CSRF 跨站请求伪造攻击和防御

本篇继续对于安全性测试话题,结合 DVWA 进行研习。

CSRF(Cross-site request forgery):跨站请求伪造

image.png

1. 跨站请求伪造攻击

CSRF 则通过伪装成受信任用户的请求来利用受信任的网站,诱使用户使用攻击性网站,从而达到直接劫持用户会话的目的。

image.png

由于现在的主流浏览器比如火狐和谷歌,都倾向于使用单个进程来管理用户会话(比如我们在 FF 和 Chrome 中,当要访问一个新页面时,通常是通过新增浏览器页面来达到的,而不是新开一个浏览器客户端进程),所以攻击者就可以通过用户新开的页面来劫持用户的已有 cookie 等关键信息。
常见的攻击形式,是通过邮件,QQ 等即时聊天工具,给被攻击对象发送伪造链接。被攻击者一旦访问了该恶意链接,攻击即生效。

DVWA 的相应模块中,使用一个密码修改功能来展示了 CSRF 攻击的可能性。
image.png

构造攻击

我们观察上述密码修改功能所触发的请求:
image.png

可以看到这个请求非常之简单,所需传递的只有三个参数

那么如果攻击者直接构造这样一条请求交给被攻击者去执行会怎么样?
比如我构造这样一条 URL:
image.png

如果被攻击者傻呵呵的点击了,那么恭喜他,他的密码就已经被我改成"attackerpw"了。
当然了,这个链接的攻击意图过于明显了,攻击者还可以通过伪装一下这个链接,从而达到神不知鬼不觉的目的。

image.png

上图看起来就是个不明所以的网页?但实际上访问到网页的时候红框部分的攻击代码就已经生效了!

2.CSRF 的防御

下面我们看一看 DVWA 是如何防御跨站脚本伪造的:
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);
}

?>

可以看到中级防御机制,关键在于以下部分:

image.png

即判断请求来源,如果类似修改密码的这种请求来自于未知第三方地址,那么则不执行修改逻辑。
这是不难绕过的,只需在攻击页面中加入 HTTP_REFERER 并使其与被攻击 server 一致即可。

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

?>

嘞了嘞了,耳熟能详的 Token 他来了:

image.png

用户每次访问页面,服务器会随机生成一个 Token,相当于用户的身份牌。只有身份牌验证通过,功能逻辑才执行。
Token 是现在很流行的令牌机制,但是他也是一种比较简单的机制,绝非无懈可击。接口测试做的多的话应该能感受到,我们接口测试中经常会对 Token 做传递处理。
实际 token 就在网页元素里面,如下图所示

image.png

我们通过抓包、爬虫、元素定位等方式完全可以获取到,传递到攻击页面中 - 当然这会需要一定的代码编写量了!

Impossible 级别

DVWA 提供的最高级别防御机制,说穿了非常简单,即修改密码前,强制要求用户输入旧密码。
如果不知道旧密码,则无论怎样也无法修改用户密码。

3. CSRF 防御能力测试

结合着上述讨论,同样我们可以总结一下这一安全测试点的测试思路。

对于 CSRF 跨站脚本伪造攻击我们可以做:

还有一个问题在于,什么样的系统功能点可能是 CSRF 攻击的敏感区域呢?

从本文的例子中我们可以看到,当系统以一种简单的请求方式实现某种功能时,CSRF 就存在劫持用户会话的可能性。那么这就要求测试人员能够敏锐的发现类似的系统特性区域,并且予以判断,是否有可能被会话劫持。

比如在一个购物平台中,订单的提交流程如果过于简单,一个简单的 get 请求就能实现下单功能(从这一点来说,get 请求就不应用于处理敏感操作,发现这种情况就是你测试的点!),那么你就可以摩拳擦掌,考虑来一次渗透测试了。

从这个角度来说,对于安全性测试,知识和技术的累积是一方面,而敏锐的思维能力特别是逆向思维能力更加重要!

回帖
请输入回帖内容 ...