上篇文章中我介绍了 Content Security Policy Level 2 ,之前我还介绍过 Subresource Integrity 、Referrer Policy 等其它与 Web 安全或用户隐私有关的协议。由于精力有限,我所写的只是新增协议中很小的一部分,但已经可以由此感受到现代浏览器在安全上所作出的努力。然而,一个我认为比较严重的安全隐患,却始终存在于各大浏览器之中。甚至到目前为止,仍然没有一个正式标准出来指导浏览器和开发者完美解决这个问题。这让我十分困惑,我将在本文讨论这个问题。
在浏览器中,通过 <a>
标签或者 JavaScript 中的 window.open
函数,可以打开新页面。新页面的 window 对象中,存在一个 opener
属性,保存对父页面的引用。我们知道,Web 应用的安全性,很大程度上是由同源策略(Same Origin Policy,SOP)所保证的。但是,在子页面访问 opener.location
的一些属性和方法时却不受 SOP 保护,这就是本文要探讨问题的核心所在。
来看一个案例,假设父页面中有新窗口打开的子页面链接:
<a href="http://qgy18.imququ.com/file/opener.html" target="_blank">click me</a>
子页面中有这样一段代码:
<script> window.opener.location = 'https://imququ.com/post/about.html'; //window.opener.location.replace('https://imququ.com/post/about.html'); </script>
将以上两段代码,分别生成两个不同域的页面(本文探讨安全风险,故只考虑不同域的情况)。在大部分浏览器中,通过父页面中的链接打开子页面后,子页面都可以通过 opener.location
将父页面跳走(上面两行 JS 可以都可以跳转,不同之处是 replace
不产生历史纪录)。
现在很多社区允许用户填写个人网站链接。设想一下,你点开某人资料中的链接,浏览一番后关掉新窗口,如果原来的页面已经被重定向到高仿的钓鱼页,你会轻易察觉么?
这个现象,很早之前就被人发现并利用在黑帽 SEO 上,同样很早之前,就有人给各大浏览器提 bug( 详情 ),得到的建议无外乎两种:1)通过 window.open 打开链接,并将 opener 置为空;2)通过给链接加上 rel=noreferrer 属性,将 opener 置为空。
我们测试一下这两种方案是否能达到预期效果,以及是否会带来负面影响。加上默认情况,一共要测试三种情况,代码如下:
<a href="http://qgy18.imququ.com/file/opener.html" target="_blank">click me</a> <a href="http://qgy18.imququ.com/file/opener.html" target="_blank" onclick="var win=window.open(this.href,'_blank');win.opener=null;return false;">click me</a> <a href="http://qgy18.imququ.com/file/opener.html" target="_blank" rel="noreferrer">click me</a>
完整的测试页面可以在 这里 找到,以下是我在部分浏览器下的测试结果:
浏览器 | 1)默认情况 | 2)window.opener=null | 3)rel=noreferrer |
---|---|---|---|
IE 8.0.6001.18702 | 不跳转 *,有 Referrer | 不跳转,无 Referrer | 不跳转 *,有 Referrer |
IE 11.0.10240.16431 | 跳转,有 Referrer | 不跳转,无 Referrer | 跳转,有 Referrer |
Edge 20.10240.16384.0 | 跳转,有 Referrer | 不跳转,无 Referrer | 跳转,有 Referrer |
Chrome 45.0.2454.101 | 跳转,有 Referrer | 不跳转,有 Referrer | 不跳转,无 Referrer |
Firefox 41.0.1 | 跳转,有 Referrer | 不跳转,有 Referrer | 不跳转,无 Referrer |
Safari 9.0.1 | 跳转,有 Referrer | 跳转,有 Referrer | 不跳转,无 Referrer |
(注:IE 8.0 中,方案 1 和 3 默认不会跳走,但会有弹出窗口被拦截的提示。这个问题可以通过在页面增加 var location;
来解决,不属于本文重点,这里不展开讨论)
由表格可以看出,在所有现代浏览器中,默认情况下父页面都会被跳走。方案 1,在最新的 Safari 下不能阻止跳转,并且会导致 IE 系列丢失 Referrer;方案 2,在不支持 rel=noreferrer 的 IE 中等同于默认情况,在其它浏览器中可以阻止跳转,同时 Referrer 也被去掉了。
这两个方案都不完美,Referrer 在很多时候并不能轻易去掉,这样只剩下 window.open 这个「改动成本大、不优雅、会引入新的问题」的方案勉强可用了。
于是,一些人开始提出各种建议,试图让浏览器既能保留 Referrer,又能阻断 opener 引用。下面是一些提议,可惜到目前为止并没有任何浏览器采纳:
- rel="newcontext":建议给 rel 属性增加
newcontext
属性值, 详情1 、 详情2 ; - rel="unrelated":建议给 rel 属性增加
unrelated
属性值, 详情 ; - target="_unrelated":建议给 target 属性增加
_unrelated
属性值, 详情 ; - disown-window-opener:建议在 CSP3 中增加
disown-window-opener
指令, 详情 ;
到这里为止,我们讨论的都是「新窗口打开的子页面将父页面跳走」所带来的风险。实际上,父页面也可以将子页面跳走,这也是一个风险点。假设我的网站上有一个名为「XX 网站登录」的外链,用户点击后发现打开的确实是 XX 网站登录页,正准备输入密码时父页面将这个子页面跳转到钓鱼页面,也不容易被察觉。为了避免加载时的空白,还可以将钓鱼页以 data URIs 的形式编码,事先准备好。
下面是一个简单的案例:
<a id="link" href="#" target="_blank">click me</a> <script> document.getElementById('link').addEventListener('click', function(e) { e.preventDefault(); var win = window.open('http://qgy18.imququ.com/file/login.html', '_blank'); setTimeout(function() { win.location.replace('data:text/html;charset=utf-8,<!DOCTYPE%20html><html><head><meta%20charset%3D"utf-8"%20%2F><%2Fhead><body><div>这是虚假的登录页面:<br><br><input><%2Fdiv><%2Fbody><%2Fhtml>'); }, 4000); }); </script>
完整的测试页面见 这里 。点击链接后打开的确实是正常的登录页,但几秒后会被替换为提前准备好的钓鱼页,如果这时没注意地址栏的变化,就很容易被钓鱼者利用。
本文先写到这里,这个问题我会持续关注,如果有浏览器提供了更好的解决方案或者有新的标准规范出来,我会及时更新本文。也欢迎大家留言讨论。
本文链接: https://imququ.com/post/the-security-of-window-opener-location.html
-- EOF --
于 2015-10-09 01:09:00 发表于「前端技术」分类下。
本站部署于「 阿里云 ECS 」。如果你也要购买阿里云服务,可以使用我的九折推荐码 NY1Z0E ,感谢你对本站的支持!