安全性测试入门 (三):CSRF 跨站请求伪造攻击和防御
本篇继续对于安全性测试话题,结合DVWA进行研习。
CSRF(Cross-site request forgery):跨站请求伪造
1. 跨站请求伪造攻击
CSRF则通过伪装成受信任用户的请求来利用受信任的网站,诱使用户使用攻击性网站,从而达到直接劫持用户会话的目的。
由于现在的主流浏览器比如火狐和谷歌,都倾向于使用单个进程来管理用户会话(比如我们在FF和Chrome中,当要访问一个新页面时,通常是通过新增浏览器页面来达到的,而不是新开一个浏览器客户端进程),所以攻击者就可以通过用户新开的页面来劫持用户的已有cookie等关键信息。
常见的攻击形式,是通过邮件,QQ等即时聊天工具,给被攻击对象发送伪造链接。被攻击者一旦访问了该恶意链接,攻击即生效。
DVWA的相应模块中,使用一个密码修改功能来展示了CSRF攻击的可能性。
构造攻击
我们观察上述密码修改功能所触发的请求:
可以看到这个请求非常之简单,所需传递的只有三个参数
- password_new
- password_conf
- change
那么如果攻击者直接构造这样一条请求交给被攻击者去执行会怎么样?
比如我构造这样一条URL:
如果被攻击者傻呵呵的点击了,那么恭喜他,他的密码就已经被我改成"attackerpw"了。
当然了,这个链接的攻击意图过于明显了,攻击者还可以通过伪装一下这个链接,从而达到神不知鬼不觉的目的。
- 比如通过缩短链接服务:
- 比如通过构建一个带有攻击代码的页面,诱使被攻击者访问:
上图看起来就是个不明所以的网页?但实际上访问到网页的时候红框部分的攻击代码就已经生效了!
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);
}
?>
可以看到中级防御机制,关键在于以下部分:
即判断请求来源,如果类似修改密码的这种请求来自于未知第三方地址,那么则不执行修改逻辑。
这是不难绕过的,只需在攻击页面中加入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他来了:
用户每次访问页面,服务器会随机生成一个Token,相当于用户的身份牌。只有身份牌验证通过,功能逻辑才执行。
Token是现在很流行的令牌机制,但是他也是一种比较简单的机制,绝非无懈可击。接口测试做的多的话应该能感受到,我们接口测试中经常会对Token做传递处理。
实际token就在网页元素里面,如下图所示
我们通过抓包、爬虫、元素定位等方式完全可以获取到,传递到攻击页面中 - 当然这会需要一定的代码编写量了!
Impossible级别
DVWA提供的最高级别防御机制,说穿了非常简单,即修改密码前,强制要求用户输入旧密码。
如果不知道旧密码,则无论怎样也无法修改用户密码。
3. CSRF防御能力测试
结合着上述讨论,同样我们可以总结一下这一安全测试点的测试思路。
对于CSRF跨站脚本伪造攻击我们可以做:
-
渗透性测试: 扮演攻击者的角色,利用已知的攻击手段尝试跨站脚本伪造,比如自己构造请求和简单的页面进行攻击。
-
运行时测试: 实际就是功能测试,我们可以通过验证系统是否存在相应的防御功能:比如token机制,请求来源验证等。
-
代码审计: 了解了CSRF的几个级别的防御机制,那么就可以通过代码审计的方式来确定被测应用的后台逻辑有无相应防御机制。
还有一个问题在于,什么样的系统功能点可能是CSRF攻击的敏感区域呢?
从本文的例子中我们可以看到,当系统以一种简单的请求方式实现某种功能时,CSRF就存在劫持用户会话的可能性。那么这就要求测试人员能够敏锐的发现类似的系统特性区域,并且予以判断,是否有可能被会话劫持。
比如在一个购物平台中,订单的提交流程如果过于简单,一个简单的get请求就能实现下单功能(从这一点来说,get请求就不应用于处理敏感操作,发现这种情况就是你测试的点!),那么你就可以摩拳擦掌,考虑来一次渗透测试了。
从这个角度来说,对于安全性测试,知识和技术的累积是一方面,而敏锐的思维能力特别是逆向思维能力更加重要!