ua.js中JSocket.getlso和JSocket.setlso代码分析
actionlog_js_ua.js混淆编程形式与执行主流程
ua.js中mousedown和mousemove事件自定义函数功能解析
ua.js中JSocket.getlso和JSocket.setlso代码分析
ua.js中arguments.callee.caller的应用
搜索关键词JSocket.getlso或JSocket.setlso,会发现乌云上几个与”淘宝网存储xss”相关的漏洞,有一部分漏洞与ua.js中的JSocket相关。虽然以我目前的知识暂时还不能理解漏洞形成的原理,但是我可以先将漏洞相关内容与JSocket相关的代码字面意思记录下来。
淘宝网存储xss
乌云上这些标题看起来都很可怕,不明觉厉,但内容还感觉比较亲切:我从这些作者的文章里找到了我正在学习的ua.js文件的影子。在这里记录下相关的链接,万一我哪天可以看懂了呢,呵呵。
2013年10月 一个flash的0day导致的淘宝网存储xss(可形成永久后门)
2014年6月 淘宝埋雷式XSS第四集可在第三方网站窃取用户帐号密码(大陆地区)
2015年8月 淘宝网宝贝页面存储型XSS(需要卖家方发起攻击)
除上述内容外,知乎上还有一篇标题更可怕,描述更详细的文章:一个可大规模悄无声息窃取淘宝/支付宝账号与密码的漏洞 -(埋雷式攻击附带视频演示)
ua.js中JSocket相关代码
首先我们来回顾一下前面提到过JScoket的地方:JScoket相关的代码在load事件处理函数中被调用,会参与UA的生成:
1 2 3 4 5 6 7 8 9 10 11 12 |
if(ivsz["FlashInfo"]) { jl(); ///// vq4(13, [document.JSocket.getos(), document.JSocket.getversion()]); } wql("13"); if(ivsz["PCIDInfo"]) {//此部分涉及到getlso和setlso q(); setTimeout(aqi, 100); } wq1("10"); |
函数jl的功能是获取JSocket.getos和JSocket.getversion并参与生成UA,这两个函数我找不到更详细的说明。我只好大胆的猜测一下:或者flash所运行的操作系统和版本信息?这个函数不涉及到乌云相关漏洞中提到的setlso和getlso,所以我们把重点放在函数q和aqi中。
由函数q反混淆和简化后的代码可知:先判断当前页面中是否存在ID为JSocket的元素,若存在,q()返回JSocket元素;若不存在,那么该函数按照代码所示的属性创建该元素,然后再返回JSocket元素。
若ivsz[“PCIDInfo”]不为0,那么在DOM加载完成后100ms后将调用函数aqi,这里先对aqi中的函数做一个初步的说明:
dpm 执行document.JSocket.getlso(),将相应字符串加密后返回
rwh 对传入参数加密后,执行document.JSocket.setlso
tpy 返回一个与DOM加载完成的时间相关的数组。
j28/swzx/b0z 暂且理解为加密函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
q = function () { if(!window.document.getElementById('JSocket')) { var si = h3d6(); var hpc = si[0]; var tu = si[1]; var ir = (hpc == 'Maxthon' && tu.indexOf('unknown')==-1&& tu < 3)||(hpc == 'Maxthon' && tu.indexOf('unknown')!=-1)|| (hpc == 'Theworld') || (hpc == 'TencentTraveler'); var txj = window.document.createElement("div"); var sc3 = '//' + (ir ? 'acjs.aliyun.com' : ivsz["ResHost"]) + '/flash/JSocket.swf' + (ir ? '?timestamp=' + window.parseInt((new window.Date())[getTime]() / 1000) : ''); var cw8 = 'height:0;width:0;overflow:hidden;'; var a5b = 'http:'; if("https:"==window.document.location.protocol) { a5b = 'https:'; } txj.setAttribute(style, cw8); txj.style.cssText = cw8; window.document.body.appendChild(txj); txj.innerHTML='<object width="0" height="0" style="height:0;width:0;overflow:hidden;" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="' + a5b + '//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" id="JSocket"><param name="allowScriptAccess" value="always"/><param name="movie" value="' + a5b + sc3 + '"/> <embed src="' + a5b + sc3 + '" name="JSocket" allowScriptAccess="always" type="application/x-shockwave-flash" pluginspage="' + a5b + '//www.adobe.com/go/getflashplayer_cn" width="0" height="0" /></object>'; } return window.document.JSocket; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
aqi = function () { tda++; try { document.JSocket.getlso(); } catch (e) { //重试10次,若仍失败,将放弃此操作 if (tda == 10) { return; } setTimeout(aqi, 200); return; } //获取document.JSocket.getlso()返回的信息,getlso信息加密后返回,存储在变量p8中 var p8 = dpm(); try { //对传入字符串进行加密后返回 p8 = swzx(p8); } catch (e) {} var ooy = 0; var a5; var d8c = false; if (p8 == "") { ooy = 0; a5 = tpy(); d8c = true; } else if (p1(p8)) {//根据传入参数的加密状况,返回true或false a5 = p8; if (a5[2] != 1) { ooy = 1; d8c = true; } } else { ooy = 2; a5 = tpy();//返回一个数组,该数组第一个元素为当前文件调用时间的加密值,第二个元素为当前文件调用时间+随机数, d8c = true; } if (d8c) { a5[2] = ooy; a5[0] = j28(a5);//对传入参数进行加密运算后,返回一个数值 ////对传入参数加密后,运行document.JSocket.setlso rwh(b0z(a5));//在lso中写入一个与当前文件调用时间相关的字符串 } su(wjt(ovh("" + arguments["callee"]), "" + arguments["callee"]); vq4([10,b0z(a5)]); }; ////对传入参数加密后,运行document.JSocket.setlso rwh = function (vq4) { var wz1 = irt; for(var i = 0; i < vq4.length; i ++){ var jmgs = vq4.charCodeAt(i); wz1 += window.String.fromCharCode(((jmgs & 15)<<4) + ((jmgs & 240)>>4)); } try { document.JSocket.setlso(wz1); } catch (e) {} }; //获取document.JSocket.getlso(),将相应字符串加密后返回 dpm = function () { var p8 = ""; try { var tu = document.JSocket.getlso(); for(var i = 0; i < tu.length; i ++){ var jmgs = tu.charCodeAt(i); p8 += window.string.fromCharCode(((jmgs & 15)<<4) + ((jmgs & 0xf0) >> 4)); } } catch (e) { p8 = ""; } return p8; }; p1 = function (ulqc) { var sk2 = j28(ulqc); return (sk2 === ulqc[0]); }; |
1 2 3 4 5 6 7 8 9 10 11 |
tpy = function () { //ghj:DOM加载完成的时间 var a5 = [0, ghj + '|' + window.Math.random(), 0]; var sk2 = j28(a5); a5[0] = sk2; return a5; }; |
aqi函数的执行逻辑如下:
1. 执行document.JSocket.getlso();若该语句执行出现异常,那么200ms后再执行函数aqi,若持续10次异常,那么将不再调用aqi;若无异常则进入第2步。
2. 获取document.JSocket.getlso()返回的信息,将该信息加密后存储在变量p8中。
3. 在函数tpy中可以看到:var a5 = [0, ghj +”|” + window.Math.random(), 0],其中ghj是DOM加载完成的时间;代码中tpy返回的数组为a5,则a5 = [j28(a5), ghj +”|” + window.Math.random(), 0]。
在aqi函数中,会根据p8以及p1(p8)的值来重新为数组a5赋值,
若p8=””,则 a5 = [j28(a5),ghj +”|” + window.Math.random(),0];
若p1(p8)=true, a5=p8, a5=[j28(a5), p8[1], 1];
若不满足上述条件,则a5 = [j28(a5)ghj +”|” + window.Math.random(),2];
4.将第3步中得到的数组进行加密后作为函数document.JSocket.setlso的参数执行。
5.记录当前正在执行的函数名,用第3步中得到数组更新UA。
从代码来看,getlso从服务器获取相关参数用于更新UA;而setlso则主要是获取了DOM加载完成的时间来更新lso?或者其实有更高深的功能隐藏在其中,而我没有看懂?