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

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

<!– JiaThis Button BEGIN –> <p 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=”https://www.jiathis.com/share” class=”jiathis jiathis_txt jtico jtico_jiathis” target=”_blank”></a> <a class=”jiathis_counter_style”></a> </p> <script type=”text/javascript” src=”https://v3.jiathis.com/code/jia.js?uid=1427098094896152″ charset=”utf-8″></script> <!– JiaThis Button END –>

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

1

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

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

2

画框的两个,之前没见过,可以研究一下。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(“<p style=’position:absolute;width:0px;height:0px;’><object classid=’clsid:D27CDB6E-AE6D-11cf-96B8-444553540000′ width=0 height=0 id=’” + SWFID + “‘ codebase=’https://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=’https://www.macromedia.com/go/getflashplayer’ /></object></p>”) } 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对象中读取值的方法。

将https://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代码。