必须配rel="noopener",否则target="_blank"会使新页面通过window.opener跨域劫持原页面,引发tabnabbing攻击;它在解析阶段就将新页面window.opener设为null,彻底切断控制链。

所有带 target="_blank" 的外链,不加 rel="noopener" 就等于把原页面的 window 对象直接交到对方手上——这不是风险提示,是浏览器明确允许的行为。
为什么 target="_blank 必须配 rel="noopener"
新标签页打开后,它的 window.opener 默认指向原始页面的 window 对象,跨域也生效。恶意页面能立刻执行:
-
window.opener.location.replace("https://fake-login.example")—— 原页面无声跳转 -
window.opener.document.write(...)—— 替换你刚离开的页面 DOM -
window.opener.postMessage(...)—— 如果你监听了message,可能被诱导泄露 token
这不是理论推演,是 Tabnabbing 攻击的标准起点。2023 年 TikTok 就因此被利用过。
rel="noopener" 到底切断了什么
它不是运行时拦截,而是在浏览器解析阶段就跳过设置 opener 字段这一步,让新页面的 window.opener 永远为 null:
- 只加
target="_blank"→window.opener是一个有效的Window对象 - 加
rel="noopener"→window.opener === null,任何读写都无效 -
rel="noreferrer"不影响window.opener,它只控制Referer请求头是否发送
旧版 Safari(≤12.0)和某些 WebView 中,rel="noreferrer" 不保证 window.opener 为 null,不能替代 noopener。
哪些地方最容易漏掉 rel="noopener"
风险不在手写 HTML,而在动态生成环节:
- Markdown 渲染器(如
marked、remark)自动加target="_blank",但不补rel - 富文本编辑器导出的 HTML 未过滤或重写
rel属性 - 服务端模板(如 Jinja、Twig)拼接链接时硬编码
target="_blank",却忘了rel - 已有
rel="nofollow"的外链,直接覆盖成rel="noopener",丢失原有语义
这些“半截子”链接不会在 DevTools 报错,Lighthouse 也未必扫得全,但攻击者一点击就生效。
window.open() 不能靠 rel 属性防护
rel 是 HTML 属性,对 JavaScript 主动打开的窗口完全无效。以下写法是错的:
window.open('https://evil.com', '_blank', 'noopener')
正确做法是:
- 先打开窗口:
const newTab = window.open(url, '_blank') - 再立即切断引用:
if (newTab) newTab.opener = null - 如果需要通信,改用
postMessage(),而不是依赖window.opener
真正起作用的只有显式、完整、小写、无空格的 rel="noopener" 或 rel="noopener noreferrer";拼写错误(如 noopenner)、引号缺失(如 rel= noopener)、值为空字符串(如 rel=""),都会导致失效。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











