JS `Trusted Types` `Policy` `Factory` 与 `Hooking DOM Sinks`

各位观众老爷,大家好!今天咱们来聊聊一个听起来高大上,但实际上是为了保护咱们前端安全的小可爱——Trusted Types。

开场白:前端江湖,暗流涌动

各位在前端江湖摸爬滚打多年,一定听过 XSS 攻击的大名。想象一下,辛辛苦苦写的代码,突然被别有用心的人塞进一段恶意脚本,用户一不小心就中招,那感觉,简直比吃了苍蝇还难受!

Trusted Types 就是来解决这个问题的。它就像一个守门员,严格把控着进入 DOM 的数据,确保咱们的代码安全可靠。

第一幕:什么是 Trusted Types?

简单来说,Trusted Types 是一种浏览器安全机制,它通过限制 DOM 接收的数据类型,来防止 XSS 攻击。 默认情况下,浏览器会阻止将字符串直接赋值给某些“危险”的 DOM 属性,比如 innerHTMLsrchref 等。

划重点:DOM Sink

这些“危险”的 DOM 属性,我们称之为 DOM Sink。 它们是数据流入 DOM 的入口,也是 XSS 攻击最喜欢光顾的地方。

举个例子:

<div id="myDiv"></div>
<script>
  const maliciousCode = '<img src="x" onerror="alert('XSS!')">';
  document.getElementById('myDiv').innerHTML = maliciousCode; //  💥 危险!
</script>

这段代码看起来没什么问题,但如果 maliciousCode 包含恶意代码,就会触发 XSS 攻击。 在开启 Trusted Types 的情况下,浏览器会阻止这种直接赋值。

第二幕:Policy,Factory 和 Trusted Types 的三角恋

Trusted Types 的核心在于 Policy 和 Trusted Types 对象。 它们之间的关系就像三角恋,哦不,是合作关系:

  • Policy: 策略,定义了一套规则,用于创建 Trusted Types 对象。 你可以把它想象成一个工厂的生产线,负责生产符合安全标准的“产品”。
  • Factory: 策略工厂,TrustedTypePolicyFactory 接口的实例。它允许你创建和管理 Policy。
  • Trusted Types 对象: 代表经过安全处理的数据,比如 TrustedHTML, TrustedScript, TrustedScriptURL 等。 这些对象可以安全地赋值给 DOM Sink。

代码说话:创建 Policy

// 检查浏览器是否支持 Trusted Types
if (window.trustedTypes && window.trustedTypes.createPolicy) {
  // 创建一个名为 'myPolicy' 的策略
  const myPolicy = trustedTypes.createPolicy('myPolicy', {
    createHTML: (input) => {
      //  对 HTML 字符串进行安全处理,例如使用 DOMPurify
      //  这里只是一个示例,实际应用中需要更严格的过滤
      const sanitizedHTML = DOMPurify.sanitize(input);
      return sanitizedHTML;
    },
    createScriptURL: (input) => {
      //  验证 URL 是否在白名单中
      const allowedURLs = ['https://example.com/script.js', 'https://cdn.example.com/script.js'];
      if (allowedURLs.includes(input)) {
        return input;
      } else {
        //  拒绝不安全的 URL
        throw new Error('不安全的 Script URL: ' + input);
      }
    },
    createScript: (input) => {
      //  对 Script 字符串进行安全处理,避免执行恶意代码
      //  例如,可以使用沙箱环境执行代码
      //  这里只是一个示例,实际应用中需要更严格的过滤
      if (input.includes('alert(')) {
          throw new Error("脚本包含 alert 函数,禁止执行");
      }
      return input;
    }
  });

  //  使用 myPolicy 创建 TrustedHTML 对象
  const trustedHTML = myPolicy.createHTML('<p>Hello, Trusted Types!</p><img src="x" onerror="alert('XSS!')">'); // XSS 攻击被阻止

  //  将 TrustedHTML 对象赋值给 innerHTML
  document.getElementById('myDiv').innerHTML = trustedHTML;
} else {
  //  浏览器不支持 Trusted Types
  console.warn('Trusted Types not supported!');
  //  提供降级方案,例如使用传统的 XSS 防御方法
}

代码解释:

  1. trustedTypes.createPolicy('myPolicy', { ... }): 创建一个名为 myPolicy 的策略。 第一个参数是策略的名称,第二个参数是一个对象,包含三个函数:createHTML, createScriptURL, createScript
  2. createHTML(input): 接收一个 HTML 字符串,进行安全处理,然后返回一个 TrustedHTML 对象。 在这个例子中,我们使用了 DOMPurify 来进行 HTML 过滤。
  3. createScriptURL(input): 接收一个 URL 字符串,验证 URL 是否在白名单中,如果是,则返回一个 TrustedScriptURL 对象,否则抛出一个错误。
  4. createScript(input): 接收一个 Script 字符串,进行安全处理,然后返回一个 TrustedScript 对象。
  5. myPolicy.createHTML('<p>Hello, Trusted Types!</p>'): 使用 myPolicy 创建一个 TrustedHTML 对象。 注意,即使 HTML 字符串包含恶意代码,也会被 createHTML 函数过滤掉。
  6. document.getElementById('myDiv').innerHTML = trustedHTML: 将 TrustedHTML 对象赋值给 innerHTML。 由于 trustedHTML 是经过安全处理的,因此不会触发 XSS 攻击。

第三幕:Hooking DOM Sinks,让安全无处不在

仅仅创建 Policy 还不够,我们需要告诉浏览器,哪些 DOM Sink 需要使用 Trusted Types。 这就是 Hooking DOM Sinks 的作用。

Hooking DOM Sinks 可以通过以下两种方式实现:

  1. Content Security Policy (CSP): 通过 CSP 策略,可以强制浏览器使用 Trusted Types。
  2. JavaScript 代码: 通过 JavaScript 代码,可以拦截对 DOM Sink 的赋值操作,并进行安全检查。

CSP 策略:

Content-Security-Policy: require-trusted-types-for 'script'; trusted-types myPolicy;

这条 CSP 策略表示:

  • require-trusted-types-for 'script': 强制所有脚本都必须使用 Trusted Types。
  • trusted-types myPolicy: 允许使用名为 myPolicy 的策略。

JavaScript 代码:

//  拦截对 innerHTML 的赋值操作
const originalInnerHTML = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'innerHTML').set;

Object.defineProperty(HTMLElement.prototype, 'innerHTML', {
  set: function(value) {
    if (typeof value === 'string') {
      //  如果赋值的是字符串,则使用 myPolicy 创建 TrustedHTML 对象
      const trustedHTML = myPolicy.createHTML(value);
      originalInnerHTML.call(this, trustedHTML);
    } else {
      //  如果赋值的是 TrustedHTML 对象,则直接赋值
      originalInnerHTML.call(this, value);
    }
  }
});

代码解释:

  1. Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'innerHTML').set: 获取 innerHTML 属性的 setter 函数。
  2. Object.defineProperty(HTMLElement.prototype, 'innerHTML', { ... }): 重新定义 innerHTML 属性的 setter 函数。
  3. if (typeof value === 'string'): 判断赋值的是否是字符串。
  4. const trustedHTML = myPolicy.createHTML(value): 如果是字符串,则使用 myPolicy 创建 TrustedHTML 对象。
  5. originalInnerHTML.call(this, trustedHTML): 将 TrustedHTML 对象赋值给 innerHTML
  6. else { originalInnerHTML.call(this, value); }: 如果赋值的是 TrustedHTML 对象,则直接赋值。

第四幕:实战演练,防患于未然

光说不练假把式,咱们来几个实战例子:

例子 1:动态加载 JavaScript 文件

//  不安全的方式
const scriptURL = 'https://example.com/malicious.js';
const script = document.createElement('script');
script.src = scriptURL; // 💥 危险!
document.head.appendChild(script);

//  安全的方式
if (window.trustedTypes && window.trustedTypes.createPolicy) {
  const myPolicy = trustedTypes.createPolicy('myPolicy', {
    createScriptURL: (input) => {
      const allowedURLs = ['https://example.com/script.js', 'https://cdn.example.com/script.js'];
      if (allowedURLs.includes(input)) {
        return input;
      } else {
        throw new Error('不安全的 Script URL: ' + input);
      }
    }
  });

  const scriptURL = 'https://example.com/script.js';
  const trustedScriptURL = myPolicy.createScriptURL(scriptURL);
  const script = document.createElement('script');
  script.src = trustedScriptURL; //  安全!
  document.head.appendChild(script);
}

例子 2:动态创建 iframe

//  不安全的方式
const iframeHTML = '<iframe src="javascript:alert('XSS!')"></iframe>';
document.body.innerHTML = iframeHTML; // 💥 危险!

//  安全的方式
if (window.trustedTypes && window.trustedTypes.createPolicy) {
  const myPolicy = trustedTypes.createPolicy('myPolicy', {
    createHTML: (input) => {
      const sanitizedHTML = DOMPurify.sanitize(input);
      return sanitizedHTML;
    }
  });

  const iframeHTML = '<iframe src="javascript:alert('XSS!')"></iframe>';
  const trustedHTML = myPolicy.createHTML(iframeHTML);
  document.body.innerHTML = trustedHTML; //  安全!
}

第五幕:兼容性与最佳实践

Trusted Types 虽然强大,但也有一些需要注意的地方:

  • 兼容性: 并非所有浏览器都支持 Trusted Types。 需要进行 feature detection,并提供降级方案。
  • 性能: Trusted Types 会增加一些性能开销,需要进行权衡。
  • 复杂性: Trusted Types 增加了代码的复杂性,需要进行良好的设计和规划。

最佳实践:

  • 最小化信任范围: 只对必要的地方使用 Trusted Types。
  • 使用成熟的 HTML 过滤库: 例如 DOMPurify。
  • 进行充分的测试: 确保 Trusted Types 的配置正确,并且没有引入新的漏洞。
  • 结合其他安全措施: 例如 CSP, Subresource Integrity (SRI)。

第六幕:总结与展望

Trusted Types 是一种强大的安全机制,可以有效地防止 XSS 攻击。 虽然它有一定的学习成本和复杂性,但为了咱们的代码安全,付出一些努力是值得的。

未来,Trusted Types 将会得到更广泛的应用,成为前端安全的标配。 让我们一起拥抱 Trusted Types,共建安全可靠的前端生态!

表格总结:

特性 描述
Trusted Types 一种浏览器安全机制,通过限制 DOM 接收的数据类型,来防止 XSS 攻击。
DOM Sink “危险”的 DOM 属性,是数据流入 DOM 的入口,也是 XSS 攻击最喜欢光顾的地方。
Policy 策略,定义了一套规则,用于创建 Trusted Types 对象。
Factory 策略工厂,TrustedTypePolicyFactory 接口的实例。它允许你创建和管理 Policy。
TrustedHTML 代表经过安全处理的 HTML 字符串。
TrustedScriptURL 代表经过安全处理的 URL 字符串,通常用于加载 JavaScript 文件。
TrustedScript 代表经过安全处理的 JavaScript 字符串。
Hooking DOM Sinks 通过 CSP 策略或 JavaScript 代码,拦截对 DOM Sink 的赋值操作,并进行安全检查。

结束语:

今天的讲座就到这里,希望大家有所收获。 记住,安全无小事,防患于未然! 感谢各位的观看,咱们下期再见!

发表回复

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