首先说下整个授权流程:
在客户端网页中会不断向服务器发送https连接,并且这里传输很少的数据之后就断开连接了,下面看下微信网页中这个login1c709c.js文件:
(function($, _aoWin) { _aoWin.QRLogin = {}; _aoWin.LoginLog = ; var _sBaseHost = , _oLoginQrCodeImg = document.getElementById(loginQrCode); if (document.domain == qq.com) { _sBaseHost = weixin.qq.com; } else if(location.hostname.match(/(wechat\.com)$/)){ _sBaseHost = wechat.com; }else{ _sBaseHost = wechatapp.com; } var show_tip = 1, _sCurUUId, _oResetTimeout, _aWebMMCallbacks = [], _oDetactWebMMInterval = setInterval(function(){ if(_aoWin.WebMM){ clearInterval(_oDetactWebMMInterval); var callback; while(callback = _aWebMMCallbacks.shift()){ if(typeof(callback) != function) continue; callback(); } } }, 1000); function _logInPage(_asLog){ _aoWin.LoginLog = LoginLog + _asLog + \n; } function _afterLoadWebMMDo(callback){ if(!_aoWin.WebMM){ _aWebMMCallbacks.push(callback); }else{ callback(); } } function _reportNow(text){ _logInPage(text); _afterLoadWebMMDo(function(){ WebMM.ossLog({Text: text}); WebMM.flushOssLog(); }); } var reLoadQRImgCount = 0, loadQRCodeTime = 0, loadQRImgSucc = function(){ clearInterval(loadQRImgWatchDog); _logInPage(Load QRCode Success, time= + (new Date().getTime() - loadQRCodeTime) + ms, reload count: + reLoadQRImgCount); }, loadQRImgFail = function(img){ _reportNow(Load QRcode fail! + status + , src: + img.src + , time: + (new Date().getTime() - loadQRCodeTime) + ms); }, loadQRImgWatchDog = null; function _loadQRImg(uuid) { _poll(uuid); _logInPage(Load QRCode Start); loadQRCodeTime = new Date().getTime(); _oLoginQrCodeImg.onload = function(){ loadQRImgSucc(); _oLoginQrCodeImg.onload = null; }; _oLoginQrCodeImg.onerror = function(){loadQRImgFail(this)}; _oLoginQrCodeImg.src = https://login.+_sBaseHost+/qrcode/+uuid+?t=webwx; loadQRImgWatchDog = setInterval(function(){ if (reLoadQRImgCount >= 5) { _reset(); return; } reLoadQRImgCount++; var _img = new Image(); _img.onload = function () { if(!_oLoginQrCodeImg.onload) return; _oLoginQrCodeImg.onload = null; _oLoginQrCodeImg.src = this.src;//replace loadQRImgSucc(); }; _img.onerror = function(){loadQRImgFail(this)}; _img.src = _oLoginQrCodeImg.src + &r= + new Date().getTime(); }, 5000); } var _sSecondRequestTime = 0, _nAjaxTimeout = 100 * 1000, _nNewLoginFuncErrCount = 0; function _poll(_asUUID) { var _self = arguments.callee, _nTime = 0; _sCurUUId = _asUUID; _logInPage(_poll Request Start, time: + new Date().getTime()); _nTime = new Date().getTime(); $.ajax({ type: GET, url: https://login. + _sBaseHost + /cgi-bin/mmwebwx-bin/login?uuid= + _asUUID + &tip= + show_tip, dataType: script, cache: false, timeout: _nAjaxTimeout, success: function(data, textStatus, jqXHR) { _logInPage(_poll Request Success, code: + window.code + , time: + (new Date().getTime() - _nTime) + ms); switch (_aoWin.code) { case 200: _sSecondRequestTime = new Date().getTime() - _sSecondRequestTime; _logInPage(Second Request Success, time: + _sSecondRequestTime + ms); clearTimeout(_oResetTimeout); var _fNewLoginFunc = function(){ $.ajax({ url: _aoWin.redirect_uri + &fun=new,//new login page type: GET, success:function(msg) { _logInPage(new func reponse, reponseMsg: + msg); var code = msg.match(/<script>(.*)<\/script>/); var skey=msg.match(/<skey>(.*)<\/skey>/); if(code){ eval(code[1]); }else{ $(#container).show(); $(#login_container).hide(); } if(skey && skey[1]){ WebMM.model(account).setSkey(skey[1]); } }, error:function(jqXHR, textStatus, errorThrown){ _nNewLoginFuncErrCount++; if(_nNewLoginFuncErrCount > 5){ if(confirm(Call new login page func error, refresh?)){location.reload()} return; } _reportNow(_aoWin.redirect_uri + New login page func error: + textStatus + retryCount: + _nNewLoginFuncErrCount); setTimeout(_fNewLoginFunc, 500); } }); }; _fNewLoginFunc(); _reportNow(/cgi-bin/mmwebwx-bin/login, Second Request Success, uuid: + _asUUID + , time: + _sSecondRequestTime + ms); break; case 201: clearTimeout(_oResetTimeout); show_tip = 0; $('.errorMsg').hide(); $('.normlDesc').hide(); $('.successMsg').show(); _reportNow(/cgi-bin/mmwebwx-bin/login, First Request Success, uuid: + _asUUID); _reportNow(/cgi-bin/mmwebwx-bin/login, Second Request Start, uuid: + _asUUID); _sSecondRequestTime = new Date().getTime(); //_nAjaxTimeout = 5 * 1000; _self(_asUUID); break; case 408: setTimeout(function(){ _self(_asUUID); }, 500); break; case 400: case 500: _reset(); _afterLoadWebMMDo(function(){ _aoWin.Log.d(500, Login Poll Svr Exception); }); break; } }, error: function(jqXHR, textStatus, errorThrown) { if (textStatus == 'timeout') { setTimeout(function(){ _self(_asUUID); }, 500); } else { setTimeout(function(){ _self(_asUUID); }, 5000); _logInPage(_poll Request Error: + textStatus); _afterLoadWebMMDo(function(){ _aoWin.Log.e(Login Poll Error: + textStatus); }); } } }); } var getUUIDCount = 0, _getUUIDWatchDog, _bGetUUIDSuccess = false;//ajax successִ function _getUUID() { getUUIDCount++; var _self = arguments.callee, _loadError = function(errorText){ _reportNow(Load UUID Error! ErrorText: + errorText + getUUIDCount= + getUUIDCount); if(getUUIDCount > 5){ if (confirm(Load uuid error. Refresh?)) { location.reload(); } } setTimeout(function(){ _self(); }, 500); }; clearTimeout(_getUUIDWatchDog); _getUUIDWatchDog = setTimeout(function(){ if(!_aoWin.QRLogin.code){ _logInPage(GetUUID Timeout, WatchDog Run); _self(); } }, 10000); $.ajax({ type: GET, url: https://login. + _sBaseHost + /jslogin?appid=wx782c26e4c19acffb&redirect_uri=+encodeURIComponent(location.protocol+//+location.host+/cgi-bin/mmwebwx-bin/webwxnewloginpage)+&fun=new&lang= + document.lang, dataType: script, cache: false, success : function(){ clearTimeout(_getUUIDWatchDog); if(_bGetUUIDSuccess) return; if (_aoWin.QRLogin && _aoWin.QRLogin.code == 200) { _logInPage(GetUUID Success, UUID= + QRLogin.uuid); _bGetUUIDSuccess = true; clearTimeout(_oResetTimeout); _oResetTimeout = setTimeout(function(){ location.reload();//Note: Don't run _reset(). If you run _reset(), there will may have many _poll request, as they get 408 return code }, 5 * 60 *1000);//5 mins _loadQRImg(QRLogin.uuid); } else { var QRLoginCode = (_aoWin.QRLogin && _aoWin.QRLogin.code) ? _aoWin.QRLogin.code : None; _logInPage(GetUUID Error, QRLogin.code= + QRLoginCode); _loadError(QRLogin.code= + QRLoginCode); } }, error : function(xhr, textStatus, errorThrown){ _logInPage(GetUUID Error, textStatus= + textStatus); _loadError(textStatus); } }); } function _reset(){ location.reload(); } if ($(#login_container).is(:visible) ) { _getUUID(); } var _bHadLog = false; function _ossLog() { if (_bHadLog) return; _bHadLog = true; var _sUvid = document.cookie.match(new RegExp( (^| )+webwxuvid+=([^;]*)(;|$))); if(!_sUvid || _sUvid.length < 3) return; _sUvid = _sUvid[2]; (new Image()).src = /cgi-bin/mmwebwx-bin/webwxstatreport?funkey=indexdemo&uvid=+_sUvid+&uuid=+_sCurUUId; } if($(img.guide).length > 0) { var _nTimer = 0, _oGuide$ = $(.guide), _oGuideTrigger$ = $(#guideTrigger, #tipTrigger), _oMask$ = $(.mask); function _back() { _nTimer = setTimeout(function() { _oMask$.stop().animate({opacity:0}, function(){$(.mask).hide()}); _oGuide$.stop().animate({marginLeft:-120px,opacity:0}, 400, swing,function(){ _oGuide$.hide(); }); }, 100); } /*guide*/ _oGuide$.css({left:50%, opacity:0}); _oGuideTrigger$.css({backgroundColor:white, opacity:0}); _oGuideTrigger$.mouSEOver(function(){ clearTimeout(_nTimer); _oMask$.show().stop().animate({opacity:0.2}); _oGuide$.css(display, block).stop().animate({marginLeft:+168px, opacity:1}, 900, swing, function() { _oGuide$.animate({marginLeft:+153px}, 300); }); _ossLog(); }).mouSEOut(_back); _oGuide$.mouSEOver(function(){ clearTimeout(_nTimer); }).mouSEOut(_back); } })(jQuery, window);
细读js之后,你就会从网页客户端这边看到请求登录的一面,网页客户端每隔500毫秒就向服务器发起ssl请求,请求当前的二维码是否被其他客户端(手机)授权,如果返回结果是201,就是说明已经获取扫描二维码终端相同的账号登录授权,如果是其他情况就再隔500毫秒再循环发请求。这个过程会一直持续到二维码被扫描通过或者二维码超时(失效)为止。
其中使用的工具有: 抓包工具 Fidller ,Chrome F12开发人员工具,注意偶然的发现,微信的客户端有一个min-webmm1cba21.js ,其中清晰可见的XSS filter规范。