window.opener.location 安全风险讨论
在浏览器中,通过 <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>
子页面中有这样一段代码:
HTML<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 置为空。
我们测试一下这两种方案是否能达到预期效果,以及是否会带来负面影响。加上默认情况,一共要测试三种情况,代码如下:
HTML<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 的形式编码,事先准备好。
下面是一个简单的案例:
HTML<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>
完整的测试页面见这里。点击链接后打开的确实是正常的登录页,但几秒后会被替换为提前准备好的钓鱼页,如果这时没注意地址栏的变化,就很容易被钓鱼者利用。
- 开发你不能忽略的问题?JavaScript(JS)
- 厚土Go学习笔记 | 36. web服务指定路径下的get参数接收与处理
- 用R进行文本分析初探——包含导入词库和和导入李白语句
- Golang事务模型
- 厚土Go学习笔记 | 35. web服务器实现动态路径
- 过滤器Filter精华知识点,怎能不看
- JavaMail开发示例,学习要看对资料
- 厚土Go学习笔记 | 34. 一个简单的 web 服务器实现
- sqlplus / as sysdba无法登录的奇怪报错 (r8笔记第36天)
- JSP与EL表达式重点学习笔记(1)
- R语言读CSV、txt文件方式以及read.table read.csv 和readr(大数据读取包)
- JSP与EL表达式重点学习笔记(2)
- Node.js真的无所不能?那些不适用的应用领域分析
- #!/bin/bash 与#!/bin/sh
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 大数据开发平台(Data Platform)在有赞的最佳实践
- 【LeetCode两题选手】算法类题目(8.7)
- 树莓派2配置LAMP环境
- 实习第八周
- Office OpenXml SDK 使用 Fallback 图片显示 Ole 元素
- 直播平台在线人数功能
- ZanProxy —— 本地代码调试线上页面,环境再也不是问题
- 实习第九周
- GET 和 POST 区别
- 实习第十周
- Linux下文件系统技巧 | 统计个数 | 只见文件或目录
- 有赞线上拨测系统实践(一)
- Node Schedule文档翻译
- 基于 Generator 和 Iterator 的惰性列表
- 【Centos8】安装docker的坎坷历程