JS `Trusted Types` API (提案) `Policy` 设计与 `Sanitizer` 实现

各位观众老爷们,掌声在哪里!今天咱们来聊聊一个听起来高大上,实则能让你代码更安全的“Trusted Types”。这玩意儿就像给你的应用装了个安检门,专门拦那些来路不明的字符串,防止 XSS 攻击。

开场白:为啥我们需要 Trusted Types?

想象一下,你的网页像一个热闹的集市,各种数据进进出出。有些数据是从你信任的来源来的,比如你的后端服务器。但有些数据可能藏着坏心思,比如用户输入,第三方广告等等。这些坏家伙可能会在你的网页里偷偷塞一些恶意脚本,一旦执行,你的用户数据就可能被窃取,甚至整个网站都被控制。这就是 XSS 攻击。

传统的 XSS 防御方法,比如 HTML 转义,往往不够彻底,而且容易出错。Trusted Types 就是为了从根本上解决这个问题,它强制你必须使用“可信任”的方式来处理那些可能被用来执行脚本的字符串。

Trusted Types 的核心概念:Policy 和 Sanitizer

Trusted Types 的核心就是 PolicySanitizer。你可以把 Policy 想象成一个“信任工厂”,它负责生产各种“可信任类型”的对象,比如 TrustedHTMLTrustedScriptTrustedScriptURLSanitizer 则是一个“清洁工”,它可以把那些不信任的字符串清理干净,变成可信任的类型。

1. Policy:信任工厂

Policy 是 Trusted Types 的核心,它定义了如何创建可信任类型对象。你需要先创建一个 Policy,然后才能使用它来创建 TrustedHTML、TrustedScript、TrustedScriptURL 等对象。

创建 Policy

if (window.trustedTypes && window.trustedTypes.createPolicy) {
  // 检查浏览器是否支持 Trusted Types
  const policy = window.trustedTypes.createPolicy('my-app', {
    createHTML: (string) => {
      // 这里对字符串进行处理,确保它是安全的 HTML
      // 例如,你可以使用 DOMPurify 来清理字符串
      return string;
    },
    createScript: (string) => {
      // 这里对字符串进行处理,确保它是安全的 JavaScript 代码
      // 最好不要直接返回字符串,而是使用其他方法来创建安全的脚本
      throw new Error('不允许创建动态脚本');
    },
    createScriptURL: (string) => {
      // 这里对字符串进行处理,确保它是安全的 URL
      // 例如,你可以检查 URL 是否在白名单中
      if (string.startsWith('https://example.com/')) {
        return string;
      } else {
        throw new Error('不允许加载来自该域名的脚本');
      }
    }
  });

  // 现在你可以使用 policy 来创建 Trusted Types 对象了
  const trustedHTML = policy.createHTML('<p>Hello, world!</p>');
  const trustedScriptURL = policy.createScriptURL('https://example.com/script.js');

  // 将 Trusted Types 对象赋值给相应的 DOM 属性
  document.getElementById('my-div').innerHTML = trustedHTML;
  const script = document.createElement('script');
  script.src = trustedScriptURL;
  document.head.appendChild(script);
} else {
  // 浏览器不支持 Trusted Types,降级处理
  console.warn('Trusted Types is not supported.');
  // 可以使用传统的 XSS 防御方法,或者禁用某些功能
}

代码解释:

  • window.trustedTypes && window.trustedTypes.createPolicy:首先检查浏览器是否支持 Trusted Types。如果不支持,就降级处理,可以使用传统的 XSS 防御方法,或者禁用某些功能。
  • window.trustedTypes.createPolicy('my-app', { ... }):创建一个名为 my-app 的 Policy。Policy 的名字可以随便取,但最好能反映你的应用或者模块的名字。
  • createHTMLcreateScriptcreateScriptURL:这三个函数分别定义了如何创建 TrustedHTML、TrustedScript、TrustedScriptURL 对象。你需要在这些函数里对字符串进行处理,确保它们是安全的。
  • throw new Error('不允许创建动态脚本');:在 createScript 函数里,我们直接抛出了一个错误。这是因为动态创建脚本非常危险,很容易被 XSS 攻击利用。除非你有非常充分的理由,否则最好不要允许创建动态脚本。
  • policy.createHTML('<p>Hello, world!</p>'):使用 Policy 创建一个 TrustedHTML 对象。
  • document.getElementById('my-div').innerHTML = trustedHTML:将 TrustedHTML 对象赋值给 innerHTML 属性。注意,你不能直接将字符串赋值给 innerHTML 属性,必须先将字符串转换为 TrustedHTML 对象。
  • script.src = trustedScriptURL:将 TrustedScriptURL 对象赋值给 script.src 属性。同样,你不能直接将字符串赋值给 script.src 属性,必须先将字符串转换为 TrustedScriptURL 对象。

Policy 的作用:

  • 控制可信任类型的创建: Policy 可以控制哪些字符串可以被转换为可信任类型。
  • 提供安全处理逻辑: Policy 可以对字符串进行安全处理,例如 HTML 转义、URL 白名单检查等。
  • 集中管理信任策略: Policy 可以集中管理你的应用的信任策略,方便维护和更新。

2. Sanitizer:清洁工

Sanitizer 是一个“清洁工”,它可以把那些不信任的字符串清理干净,变成可信任的类型。Sanitizer 通常使用 DOMPurify 等库来实现。

使用 Sanitizer

import DOMPurify from 'dompurify';

if (window.trustedTypes && window.trustedTypes.createPolicy) {
  const policy = window.trustedTypes.createPolicy('my-app', {
    createHTML: (string) => {
      // 使用 DOMPurify 清理字符串
      const cleanHTML = DOMPurify.sanitize(string);
      return cleanHTML;
    },
    createScript: (string) => {
      throw new Error('不允许创建动态脚本');
    },
    createScriptURL: (string) => {
      if (string.startsWith('https://example.com/')) {
        return string;
      } else {
        throw new Error('不允许加载来自该域名的脚本');
      }
    }
  });

  const untrustedHTML = '<img src="x" onerror="alert(1)">';
  const trustedHTML = policy.createHTML(untrustedHTML);

  document.getElementById('my-div').innerHTML = trustedHTML;
} else {
  console.warn('Trusted Types is not supported.');
  // 使用传统的 XSS 防御方法
  const untrustedHTML = '<img src="x" onerror="alert(1)">';
  const cleanHTML = DOMPurify.sanitize(untrustedHTML);
  document.getElementById('my-div').innerHTML = cleanHTML;
}

代码解释:

  • import DOMPurify from 'dompurify':导入 DOMPurify 库。
  • const cleanHTML = DOMPurify.sanitize(string):使用 DOMPurify 清理字符串。DOMPurify 会移除字符串中的恶意代码,例如 onerror 属性。
  • const untrustedHTML = '<img src="x" onerror="alert(1)">':一个包含恶意代码的字符串。
  • const trustedHTML = policy.createHTML(untrustedHTML):使用 Policy 将不信任的字符串转换为 TrustedHTML 对象。DOMPurify 会在 Policy 的 createHTML 函数中被调用,清理字符串。

Sanitizer 的作用:

  • 清理不信任的字符串: Sanitizer 可以移除字符串中的恶意代码,例如 HTML 标签、JavaScript 代码等。
  • 将不信任的字符串转换为可信任类型: Sanitizer 可以将清理后的字符串转换为 TrustedHTML、TrustedScript、TrustedScriptURL 等对象。

Trusted Types 的用法

Trusted Types 主要用于以下场景:

  • 设置 DOM 属性: 例如 innerHTMLsrchref 等。
  • 创建 DOM 元素: 例如 document.createElementdocument.createTextNode 等。
  • 执行 JavaScript 代码: 例如 evalFunction 等。(强烈不建议使用)

示例:设置 innerHTML 属性

<div id="my-div"></div>
<script>
  if (window.trustedTypes && window.trustedTypes.createPolicy) {
    const policy = window.trustedTypes.createPolicy('my-app', {
      createHTML: (string) => {
        return string;
      }
    });

    const trustedHTML = policy.createHTML('<p>Hello, world!</p>');
    document.getElementById('my-div').innerHTML = trustedHTML; // 正确
    // document.getElementById('my-div').innerHTML = '<p>Hello, world!</p>'; // 错误,会抛出 TypeError
  } else {
    document.getElementById('my-div').innerHTML = '<p>Hello, world!</p>';
  }
</script>

代码解释:

  • document.getElementById('my-div').innerHTML = trustedHTML:将 TrustedHTML 对象赋值给 innerHTML 属性。这是正确的用法。
  • document.getElementById('my-div').innerHTML = '<p>Hello, world!</p>':直接将字符串赋值给 innerHTML 属性。这是错误的用法,会抛出一个 TypeError 异常,告诉你不能直接使用字符串。

示例:设置 src 属性

<img id="my-image">
<script>
  if (window.trustedTypes && window.trustedTypes.createPolicy) {
    const policy = window.trustedTypes.createPolicy('my-app', {
      createScriptURL: (string) => {
        if (string.startsWith('https://example.com/')) {
          return string;
        } else {
          throw new Error('不允许加载来自该域名的脚本');
        }
      }
    });

    const trustedScriptURL = policy.createScriptURL('https://example.com/image.jpg');
    document.getElementById('my-image').src = trustedScriptURL; // 正确
    // document.getElementById('my-image').src = 'https://example.com/image.jpg'; // 错误,会抛出 TypeError
  } else {
    document.getElementById('my-image').src = 'https://example.com/image.jpg';
  }
</script>

Trusted Types 的优点:

  • 更强的安全性: Trusted Types 可以从根本上防止 XSS 攻击。
  • 更少的漏洞: Trusted Types 可以减少因人为错误而导致的 XSS 漏洞。
  • 更好的可维护性: Trusted Types 可以集中管理你的应用的信任策略,方便维护和更新。

Trusted Types 的缺点:

  • 兼容性问题: Trusted Types 并不是所有浏览器都支持。
  • 学习成本: Trusted Types 需要一定的学习成本。
  • 代码改造成本: 将现有的代码迁移到 Trusted Types 需要一定的改造成本。

Trusted Types 的兼容性

Trusted Types 的兼容性目前还不是很好。只有 Chrome 和 Edge 等少数浏览器支持 Trusted Types。

你可以使用以下代码来检查浏览器是否支持 Trusted Types:

if (window.trustedTypes) {
  console.log('Trusted Types is supported.');
} else {
  console.log('Trusted Types is not supported.');
}

如果浏览器不支持 Trusted Types,你可以使用传统的 XSS 防御方法,例如 HTML 转义。

Trusted Types 的未来

Trusted Types 是一个很有前景的技术,它可以有效地防止 XSS 攻击。随着浏览器对 Trusted Types 的支持越来越好,它将会成为 Web 开发的标准配置。

总结

Trusted Types 就像给你的应用装了个安检门,专门拦那些来路不明的字符串。它通过 Policy 和 Sanitizer 来控制可信任类型的创建,并提供安全处理逻辑。虽然 Trusted Types 有一定的学习成本和代码改造成本,但它能显著提高你的应用的安全性,减少 XSS 漏洞。

一些建议

  • 尽早开始使用 Trusted Types: 即使你的项目现在还不大,也可以开始尝试使用 Trusted Types。
  • 逐步迁移: 不要试图一次性将所有的代码都迁移到 Trusted Types。可以先从最容易被 XSS 攻击的地方开始。
  • 使用 Sanitizer: 使用 Sanitizer 可以简化你的代码,并提高安全性。
  • 关注 Trusted Types 的发展: 随着 Trusted Types 的发展,会有越来越多的工具和库可以帮助你更好地使用它。

最后的唠叨

各位观众老爷们,安全无小事!希望今天的讲座能帮助大家更好地理解 Trusted Types,并在你的项目中应用它,让你的代码更安全,让你的用户更放心!

感谢大家的收听,我们下期再见!

表格总结

特性 描述
Trusted Types 一个 Web API,旨在防止 XSS 攻击,它强制开发者使用“可信任”的方式处理可能被用来执行脚本的字符串。
Policy 一个“信任工厂”,负责生产各种“可信任类型”的对象,例如 TrustedHTMLTrustedScriptTrustedScriptURL。它可以控制哪些字符串可以被转换为可信任类型,并提供安全处理逻辑。
Sanitizer 一个“清洁工”,可以把那些不信任的字符串清理干净,变成可信任的类型。通常使用 DOMPurify 等库来实现。
优点 更强的安全性,更少的漏洞,更好的可维护性。
缺点 兼容性问题,学习成本,代码改造成本。
兼容性 目前只有 Chrome 和 Edge 等少数浏览器支持。

发表回复

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