Heim  >  Artikel  >  WeChat-Applet  >  WeChat QQ QR-Code-Anmeldeprinzip JS-Code-Analyse

WeChat QQ QR-Code-Anmeldeprinzip JS-Code-Analyse

高洛峰
高洛峰Original
2017-01-20 14:55:551873Durchsuche

An vielen Orten gibt es Anwendungen wie QR-Code-Anmeldung, QR-Code-Zahlung, QR-Code-Konto (hier QR-Code-Stud, ganz zu schweigen von Betrug), QR-Code-Verifizierung, Multi-Terminal. Es gibt immer mehr Hilfsautorisierungsanwendungen Lassen Sie uns zunächst darüber sprechen, was ein QR-Code ist. Tatsächlich ist ein QR-Code ein Schwarz-Weiß-Bild, das für die Anmeldung einen QR-Code benötigt Code-Informationen werden an den Kunden gesendet und in Form eines QR-Codes (Bild) auf die Webseite geschrieben. Anschließend wird ein einheitlicher viereckiger QR-Code angezeigt. Wenn dies gut gemacht ist, sollten die QR-Code-Informationen zeitkritisch sein . Ja, betrachten wir diese zunächst nicht. Nehmen wir als Beispiel eine einfache WeChat-Anmeldung:

WeChat QQ QR-Code-Anmeldeprinzip JS-Code-Analyse

Lassen Sie uns zunächst über den gesamten Autorisierungsprozess sprechen

WeChat QQ QR-Code-Anmeldeprinzip JS-Code-Analyse

Auf der Client-Webseite werden kontinuierlich https-Verbindungen an den Server gesendet, und die Verbindung wird getrennt, nachdem nur sehr wenige Daten übertragen wurden. Schauen wir uns die Datei login1c709c.js auf der WeChat-Webseite an:

(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;
 $(&#39;.errorMsg&#39;).hide();
 $(&#39;.normlDesc&#39;).hide();
 $(&#39;.successMsg&#39;).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 == &#39;timeout&#39;) {
  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&#39;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);

Nachdem Sie die js sorgfältig gelesen haben, sehen Sie die Anmeldeanforderungsseite des Webclients. Der Webclient initiiert alle 500 Millisekunden eine SSL-Anfrage an den Server, um anzufragen, ob der aktuelle QR-Code verwendet wurde Andere Clients (Mobiltelefon) Autorisierung: Wenn das Rückgabeergebnis 201 ist, bedeutet dies, dass die gleiche Kontoanmeldeautorisierung durch Scannen des QR-Code-Terminals erhalten wurde. Unter anderen Umständen wird die Anfrage nach 500 erneut in einer Schleife gesendet Millisekunden. Dieser Vorgang wird fortgesetzt, bis der QR-Code gescannt wird oder der QR-Code abläuft (ungültig).
Die verwendeten Tools sind: Paketerfassungstool Fidller, Chrome F12-Entwicklertools, achten Sie auf die zufällige Entdeckung, dass der WeChat-Client eine min-webmm1cba21.js hat, in der die XSS-Filterspezifikation deutlich sichtbar ist, dies ist für diese Wer weiße Tauben mag, die den XSS-Boxtest machen, hat die Hoffnung, erneut den Q-Preis zu gewinnen! ! !

Das Obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, dass er zum Lernen aller beiträgt. Ich hoffe auch, dass jeder die PHP-Chinesisch-Website unterstützt.

Weitere Artikel zum QR-Code-Anmeldeprinzip von WeChat QQ und zur JS-Code-Analyse finden Sie auf der chinesischen PHP-Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn