Tabnabbing 攻击 (利用 window.opener.location = …):如何通过 rel=’noopener’ 或 rel=’noreferrer’ 防御?

各位同学,晚上好!我是你们的老朋友,今天咱们聊聊一个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.comlocation 了。

  • 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 属性,则会在原有属性值的基础上添加 noopenernoreferrer

表格总结:

属性 作用 兼容性 是否禁用 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 应用时,时刻保持警惕,关注各种安全风险,并采取相应的防御措施。

今天的分享就到这里,谢谢大家!如果有什么问题,欢迎随时提问。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注