各位同学,晚上好!我是你们的老朋友,今天咱们聊聊一个Web安全的“小玩意儿”——Tabnabbing,以及如何用 rel='noopener'
和 rel='noreferrer'
这两个好兄弟来保护我们的网站。
Tabnabbing:潜伏的“钓鱼”攻击
Tabnabbing,也叫 Reverse Tabnabbing,简单来说,就是利用 window.opener
这个属性,把用户已经打开的、信任的页面偷偷替换成一个钓鱼页面。想象一下,用户在一个看起来安全的网站上点击了一个链接,打开了一个新标签页。当用户回到之前的标签页时,却发现页面被替换成了假冒的登录页面,要求重新输入密码。用户一看,“哎?难道我刚才的操作session过期了?”然后乖乖地输入了密码… 悲剧就发生了。
为什么会发生这种事?window.opener
在搞鬼!
当你使用 <a href="..." target="_blank">
打开一个新标签页时,新标签页的 window
对象会有一个 opener
属性,指向打开它的那个标签页的 window
对象。这意味着,新标签页可以通过 window.opener
来访问和修改打开它的页面的 location
属性,也就是网址。
举个栗子:
假设我们有一个网站 safe-website.com
,上面有一个指向恶意网站 evil-website.com
的链接:
<a href="https://evil-website.com" target="_blank">点击这里!</a>
当用户点击这个链接时,evil-website.com
可以在自己的 JavaScript 代码中这样搞事情:
// evil-website.com 的 JavaScript 代码
if (window.opener) {
window.opener.location = "https://evil-website.com/fake-login.html"; // 替换 safe-website.com 的页面
}
这样,当用户回到 safe-website.com
标签页时,看到的就不再是原来的页面,而是一个假的登录页面。
防御武器:rel='noopener'
和 rel='noreferrer'
好了,了解了 Tabnabbing 的原理,我们就要祭出我们的防御武器了:rel='noopener'
和 rel='noreferrer'
。
-
rel='noopener'
:断开连接!rel='noopener'
的作用是告诉浏览器,不要在新标签页中设置window.opener
属性。这样,新标签页就无法通过window.opener
来访问和修改打开它的页面了。使用方法很简单,只需要在
<a>
标签中添加rel='noopener'
属性即可:<a href="https://evil-website.com" target="_blank" rel="noopener">点击这里!</a>
加上
rel='noopener'
后,evil-website.com
的 JavaScript 代码中的window.opener
将会是null
,也就无法修改safe-website.com
的location
了。 -
rel='noreferrer'
:隐藏身份!rel='noreferrer'
的作用是告诉浏览器,在跳转到新标签页时,不要发送Referer
请求头。Referer
请求头包含了用户是从哪个页面跳转过来的信息。为什么要隐藏
Referer
信息呢?因为有些恶意网站可能会利用Referer
信息来判断用户是从哪个网站跳转过来的,然后根据不同的来源展示不同的内容,或者进行一些其他的恶意操作。使用方法同样很简单:
<a href="https://evil-website.com" target="_blank" rel="noreferrer">点击这里!</a>
需要注意的是,
rel='noreferrer'
也会同时禁用window.opener
,相当于同时使用了rel='noopener'
。但是,为了兼容一些老旧的浏览器,建议同时使用rel='noopener'
和rel='noreferrer'
。
兼容性问题:
rel='noopener'
在大多数现代浏览器中都得到了支持,包括 Chrome, Firefox, Safari, Edge 等。rel='noreferrer'
的兼容性也很好,几乎所有的浏览器都支持。
最佳实践:
-
总是为所有
target='_blank'
的链接添加rel='noopener noreferrer'
: 这是一个良好的安全习惯,可以有效地防止 Tabnabbing 攻击。<a href="https://example.com" target="_blank" rel="noopener noreferrer">点击这里!</a>
-
如果需要传递
Referer
信息,可以考虑使用rel='noopener'
和window.postMessage'
: 如果你的业务场景确实需要传递Referer
信息,可以使用rel='noopener'
来防止 Tabnabbing 攻击,然后使用window.postMessage'
来安全地传递数据。例如:
<!-- safe-website.com --> <a href="https://example.com" target="_blank" rel="noopener" onclick="sendMessage()">点击这里!</a> <script> function sendMessage() { const newWindow = window.open("https://example.com"); newWindow.onload = () => { newWindow.postMessage({ referrer: window.location.href }, "https://example.com"); }; } </script> <!-- example.com --> <script> window.addEventListener("message", (event) => { if (event.origin === "https://safe-website.com") { const referrer = event.data.referrer; console.log("Referrer:", referrer); // 安全地获取 referrer 信息 } }); </script>
-
审查第三方库和组件: 确保你使用的第三方库和组件没有安全漏洞,例如,可能会创建不安全的
target='_blank'
链接。
代码示例:使用 JavaScript 添加 rel='noopener noreferrer'
如果你想使用 JavaScript 来自动为所有 target='_blank'
的链接添加 rel='noopener noreferrer'
,可以使用以下代码:
// 在 DOM 加载完成后执行
document.addEventListener("DOMContentLoaded", function() {
const links = document.querySelectorAll('a[target="_blank"]');
links.forEach(link => {
// 检查是否已经包含 rel 属性
if (link.hasAttribute('rel')) {
const relValue = link.getAttribute('rel');
if (!relValue.includes('noopener')) {
link.setAttribute('rel', relValue + ' noopener');
}
if (!relValue.includes('noreferrer')) {
link.setAttribute('rel', relValue + ' noreferrer');
}
} else {
link.setAttribute('rel', 'noopener noreferrer');
}
});
});
这段代码会在页面加载完成后,找到所有 target='_blank'
的链接,然后为它们添加 rel='noopener noreferrer'
属性。如果链接已经有 rel
属性,则会在原有属性值的基础上添加 noopener
和 noreferrer
。
表格总结:
属性 | 作用 | 兼容性 | 是否禁用 window.opener |
是否发送 Referer |
---|---|---|---|---|
rel='noopener' |
告诉浏览器不要在新标签页中设置 window.opener 属性,防止新标签页访问和修改打开它的页面。 |
良好 | 是 | 是 (大部分浏览器) |
rel='noreferrer' |
告诉浏览器在跳转到新标签页时,不要发送 Referer 请求头,防止恶意网站利用 Referer 信息。同时也会禁用 window.opener 。 |
良好 | 是 | 否 |
target='_blank' |
告诉浏览器在新标签页中打开链接。如果不配合 rel='noopener' 或 rel='noreferrer' 使用,可能会导致 Tabnabbing 攻击。 |
良好 | 否 | 是 |
一个稍微复杂点的例子:动态生成链接
很多时候,我们的链接不是写死的,而是动态生成的。比如,从数据库里读取数据,然后生成链接。这时候,我们就需要在生成链接的时候,动态地添加 rel='noopener noreferrer'
属性。
假设我们使用 Node.js 和 Express.js 来生成一个包含链接的 HTML 页面:
// Node.js + Express.js
const express = require('express');
const app = express();
app.get('/', (req, res) => {
const links = [
{ url: 'https://example.com', text: 'Example Website' },
{ url: 'https://evil-website.com', text: 'Evil Website' }
];
let html = '<h1>Welcome!</h1><ul>';
links.forEach(link => {
html += `<li><a href="${link.url}" target="_blank" rel="noopener noreferrer">${link.text}</a></li>`;
});
html += '</ul>';
res.send(html);
});
app.listen(3000, () => {
console.log('Server listening on port 3000');
});
在这个例子中,我们在生成链接的 HTML 代码时,直接将 rel="noopener noreferrer"
属性添加到 <a>
标签中。这样,无论链接是从哪里来的,都可以保证安全性。
总结:
Tabnabbing 是一种隐蔽而危险的 Web 安全攻击,但我们可以通过使用 rel='noopener'
和 rel='noreferrer'
这两个简单而有效的属性来防御它。记住,养成良好的安全习惯,总是为所有 target='_blank'
的链接添加 rel='noopener noreferrer'
,可以有效地保护你的用户免受 Tabnabbing 攻击。
最后,安全无小事,希望大家在开发 Web 应用时,时刻保持警惕,关注各种安全风险,并采取相应的防御措施。
今天的分享就到这里,谢谢大家!如果有什么问题,欢迎随时提问。