使用可信类型防范基于 DOM 的跨站脚本攻击漏洞

减小应用的 DOM XSS 攻击面。

Krzysztof Kotowicz
Krzysztof Kotowicz

为什么要关注短视频?

基于 DOM 的跨站脚本攻击 (DOM XSS) 是最常见的网络安全漏洞之一,很容易在应用中引入它。可信类型可将危险的 Web API 函数默认设置为安全状态,从而为您提供编写、安全审核和维护应用不存在 DOM XSS 漏洞的工具。Chrome 83 支持可信类型,并且 polyfill 适用于其他浏览器。如需了解最新的跨浏览器支持信息,请参阅浏览器兼容性

背景

多年来,DOM XSS 一直是最普遍、最危险的 Web 安全漏洞之一。

跨站脚本攻击分为两类。有些 XSS 漏洞是由以不安全的方式创建用来构成网站的 HTML 代码的服务器端代码导致的。而其他代码则是在客户端上存在根本原因,即 JavaScript 代码使用用户控制的内容调用危险函数。

防止服务器端 XSS,请勿通过串联字符串来生成 HTML,而应使用安全的上下文自动转义模板库。使用基于 Nonce 的内容安全政策,在不可避免地发生 bug 时采取额外的缓解措施。

现在,浏览器还可以借助可信类型来帮助防止客户端(也称为基于 DOM)的 XSS。

API 简介

可信类型的工作原理是锁定以下存在风险的接收器函数。您可能已经认识其中一些,因为出于安全考虑,浏览器供应商和 Web 框架已经让您不再使用这些功能。

可信类型要求您先处理数据,然后再将其传递给上述接收器函数。仅使用字符串将会失败,因为浏览器不确定数据是否可信:

错误做法
anElement.innerHTML  = location.href;
启用可信类型后,浏览器会抛出 TypeError 并阻止将 DOM XSS 接收器与字符串搭配使用。

为了表示数据已经过安全处理,请创建一个特殊对象 -“可信类型”。

正确做法
anElement.innerHTML = aTrustedHTML;
启用可信类型后,对于需要 HTML 代码段的接收器,浏览器会接受 TrustedHTML 对象。此外,还有适用于其他敏感接收器的 TrustedScriptTrustedScriptURL 对象。

可信类型可大幅减少应用的 DOM XSS 攻击面。该工具可简化安全审核,并可让您在运行时在浏览器中对代码进行编译、lint 检查或捆绑代码时执行基于类型的安全检查。

如何使用可信类型

为接收内容安全政策违规行为报告做好准备

您可以部署报告收集器(例如开源 go-csp-collector),也可以使用某个等效的商业报告收集器。您还可以在浏览器中调试违规行为: js document.addEventListener('securitypolicyviolation', console.error.bind(console));

添加仅用于报告的 CSP 标头

将以下 HTTP 响应标头添加到要迁移到可信类型的文档中。text Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

现在,所有违规行为都会向 //my-csp-endpoint.example 报告,但网站会继续正常运行。下一部分将介绍 //my-csp-endpoint.example 的工作原理。

识别 Trusted Types 违规问题

从现在起,每当可信类型检测到违规行为时,系统都会向配置的 report-uri 发送报告。例如,当应用将字符串传递给 innerHTML 时,浏览器会发送以下报告:

{
"csp-report": {
    "document-uri": "https://my.url.example",
    "violated-directive": "require-trusted-types-for",
    "disposition": "report",
    "blocked-uri": "trusted-types-sink",
    "line-number": 39,
    "column-number": 12,
    "source-file": "https://my.url.example/script.js",
    "status-code": 0,
    "script-sample": "Element innerHTML <img src=x"
}
}

这表示在 https://my.url.example/script.js 中,第 39 行的 innerHTML 是使用以 <img src=x 开头的字符串调用的。这些信息应该能够帮助您缩小哪些代码部分可能会引入 DOM XSS 并需要更改。

修正违规问题

您可以通过多种方式解决“受信任类型”违规行为。您可以移除违规代码使用库创建“可信类型”政策,或在万不得已的情况下创建默认政策

重写违规代码

也许您不再需要不合规的功能,或者可以在不使用容易出错的函数的情况下以现代方式重写它?

错误做法
el.innerHTML = '';
正确做法
el.textContent = '';
const img = document.createElement('img');
img.src = 'xyz.jpg';
el.appendChild(img);

使用库

某些库已生成可传递给接收器函数的可信类型。例如,您可以使用 DOMPurify 清理 HTML 代码段,移除 XSS 载荷。

import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});

DOMPurify 支持可信类型,并将返回封装在 TrustedHTML 对象中且已经过净化的 HTML,以确保浏览器不会生成违规行为。

创建可信类型政策

有时,无法移除该功能,并且没有库可以清理该值并为您创建可信类型。在这种情况下,请自行创建一个“可信类型”对象。

为此,请先创建一项政策。政策是可信类型的工厂,可对其输入强制执行某些安全规则:

if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
  const escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
    createHTML: string => string.replace(/\</g, '&lt;')
  });
}

这段代码会创建一个名为 myEscapePolicy 的政策,该政策可以通过其 createHTML() 函数生成 TrustedHTML 对象。定义的规则将对 < 字符进行 HTML 转义,以防止创建新的 HTML 元素。

请按如下方式使用该政策:

const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML);  // true
el.innerHTML = escaped;  // '&lt;img src=x onerror=alert(1)>'

使用默认政策

有时您无法更改违规代码。例如,从 CDN 加载第三方库时,就会出现这种情况。在这种情况下,请使用默认政策

if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
  trustedTypes.createPolicy('default', {
    createHTML: (string, sink) => DOMPurify.sanitize(string, {RETURN_TRUSTED_TYPE: true})
  });
}

在仅接受可信类型的接收器中使用字符串的任何位置,都会使用名为 default 的政策。

切换为强制执行内容安全政策

当您的应用不再产生违规行为时,您可以开始强制执行可信类型:

Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

瞧!现在,无论 Web 应用的复杂程度如何,唯一可能会引入 DOM XSS 漏洞的因素就是您的一项政策中的代码。您还可以通过限制政策创建来进一步锁定这一点。

深入阅读