rel="noopener"的核心作用是让新标签页的window.opener为null,从解析阶段源头切断引用链,防止tabnabbing攻击;不加它,target="_blank"会使新页面跨域获得对原页面的完全控制权。

rel="noopener" 的核心作用是让新标签页的 window.opener 为 null,彻底断开它对原页面的 JavaScript 控制权——不加它,target="_blank" 就等于把原页面的 window 对象白送给新开页面。
为什么 target="_blank" 默认会暴露 window.opener?
浏览器规范明确要求:只要用 target="_blank" 打开新标签页,新页面的 window.opener 就自动指向原始页面的 window 对象。这个引用不受同源策略限制,跨域也生效。
攻击者拿到这个引用后,能立刻执行:
-
window.opener.location.href = "https://fake-login.example"—— 原页面无声跳转 -
window.opener.document.write("...")—— 替换你刚离开的页面 DOM -
window.opener.postMessage(...)—— 如果你监听了message,可能被诱导泄露 token
这不是理论推演,而是 Tabnabbing 攻击链的标准起点。2023 年 TikTok 就因此被利用过。
rel="noopener" 到底切断了什么?
它不是运行时拦截,而是在浏览器创建新浏览上下文(即新标签页)的解析阶段,就跳过设置 opener 字段这一步——相当于从源头上不建立引用链。
关键事实:
- 加了
rel="noopener"后,window.opener === null,任何读写都无效 -
rel="noreferrer"不影响window.opener,它只控制 Referer 请求头是否发送 - Chrome ≥64、Firefox ≥79 虽有隐式
noopener行为,但仅限于rel属性为空的干净场景;一旦已有rel="nofollow"或rel="external",隐式机制立即失效 - Safari ≤12.0 和部分 Android WebView 完全不支持隐式行为,必须显式声明
哪些地方最容易漏掉 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 是 HTML 属性,对 JS 主动打开的窗口完全无效。写 window.open(url, "_blank", "noopener") 是错的——noopener 不是合法的 features 字符串参数。
正确做法分情况:
- 同源且兼容性要求不高:用
window.open(url, "_blank", "noopener=yes")(Chrome/Edge 支持),再配合 HTML 里rel="noopener noreferrer"双重保险 - 跨域或需强兼容:改用
location.replace()跳转 + 新窗口监听,或直接放弃 JS 开窗,回归<a target="_blank" rel="noopener noreferrer"></a> - 绝对不要只写
window.open(url)或window.open(url, "_blank"),默认保留window.opener引用
最常被忽略的不是语法,而是逻辑覆盖:单引号分隔的 target、已存在其他 rel 值的合并处理、内链误加 noreferrer 导致分析归因失效……安全不是加一次就完事,是每个动态出口都得守住这一关。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











