单独写target="_blank"是安全漏洞,因新页面可通过window.opener劫持原页面地址栏、DOM或触发卡顿;必须配rel="noopener noreferrer",其中noopener切断opener引用防反向Tabnabbing,noreferrer阻止Referer泄露。

单独写 target="_blank" 就是安全漏洞,不是“可能有风险”,而是浏览器明确允许新页面篡改原页面的地址栏、DOM 甚至触发卡顿——rel="noopener noreferrer" 不是可选项,是必须项。
为什么 target="_blank" 会暴露原页面?
新标签页一打开,它的 window.opener 就指向你当前页面的 window 对象。跨域也不拦,哪怕目标站和你毫无关系:
-
window.opener.location.replace("https://fake-login.example")—— 原页面瞬间跳转 -
window.opener.document.write("")—— 原页面内容被清空重写 - 两个页面共享渲染进程,对方跑死循环,你这页就卡住
这种攻击叫 reverse tabnabbing(反向标签劫持),TikTok 2023 年就被真实利用过。
rel="noopener noreferrer" 怎么起作用?
noopener 是核心:它强制把新页面的 window.opener 设为 null,从源头切断控制链;noreferrer 是补充:它不发送 Referer 头,防来源泄露。
立即学习“前端免费学习笔记(深入)”;
- 只写
rel="noreferrer"不够——旧版 Safari(≤10.0)不切断window.opener - 只写
rel="noopener"足够防劫持,但noreferrer顺带保护隐私,生产环境建议一起写 -
rel必须小写、无连字符,且只在配合target="_blank"(或其它非_self的合法值)时生效
哪些地方最容易漏掉 rel 属性?
手动写 HTML 容易补上,但这些场景常被忽略:
- 富文本编辑器导出的 HTML(如 CKEditor、TinyMCE 输出)
- CMS 或博客系统自动生成的外链(比如评论区、文章内嵌链接)
- Vue / React 模板中动态拼接的
<a>标签,尤其是href来自 API 或用户输入时 - 第三方组件库(如旧版 Ant Design 的
Link)默认不加rel - 用
window.open(url, "_blank")替代原生<a>,但没传'noopener'特性字符串
这类问题靠肉眼检查几乎无效,得用 Lighthouse 扫描,或正则匹配 ]*target=["']_blank["'][^>]*> 并验证是否含 rel=["'].*noopener。
target 的合法值只有五个,别乱写
W3C 明确只认以下五类 target 值:
-
_self:当前标签页(可省略) -
_blank:唯一推荐用于“新开页”的值 -
_parent:仅在嵌套<iframe>场景中有效 -
_top:跳出所有 iframe 层级,在顶层窗口加载 -
framename:匹配页面中某个<iframe name="xxx">
像 target="blank"(漏下划线)、target="_new"、target="newtab" 这些写法,浏览器不会报错,但会退化为 _self —— 表面看是“新页”,实际是当前页跳转,更隐蔽也更难排查。
最麻烦的是:DevTools 不再报错提醒,Lighthouse 也只当建议项。等你发现原页面被悄悄跳转,往往已经晚了。


















