使用 Trusted Types 防止基于 DOM 的跨站点脚本漏洞
减小应用程序的 DOM XSS 攻击面。
为什么应关注? #
基于 DOM 的跨站点脚本 (DOM XSS) 是最常见的 Web 安全漏洞之一,并且您的应用程序很容易将其引入。Trusted Types 默认对危险的 Web API 函数加以保护,从而提供了编写、安全审核和维护无 DOM XSS 漏洞的应用程序的工具。Chrome 83 支持 Trusted Types,其他浏览器可以使用 polyfill。有关最新的跨浏览器支持信息,请参阅浏览器兼容性。
背景 #
多年来, DOM XSS 一直是最普遍和最危险的 Web 安全漏洞之一。
有两组不同的跨站点脚本。一些 XSS 漏洞是服务器端代码引起的,这些代码不安全地创建了形成网站的 HTML 代码。另外一些漏洞的根本原因在于客户端,其中的 JavaScript 代码对用户控制的内容调用了危险的函数。
为了防止服务器端 XSS,不要通过连接字符串来生成 HTML,而是使用安全的上下文自动转义模板库。对于不可避免发生的错误,使用基于 nonce 的内容安全策略来进一步缓解。
现在,浏览器还可以通过 Trusted Types 帮助防止客户端(也称为基于 DOM)XSS。
API 简介 #
Trusted Types 的工作原理是锁定以下有风险的接收器函数。您可能已经认出其中一些,因为浏览器供应商和 Web 框架已经出于安全原因引导您避免使用这些功能。
脚本操作:
<script src>
和设置<script>
元素的文本内容。从字符串生成 HTML :
innerHTML
、outerHTML
、insertAdjacentHTML
、<iframe> srcdoc
、document.write
、document.writeln
和DOMParser.parseFromString
执行插件内容:
<embed src>
、<object data>
和<object codebase>
运行时 JavaScript 代码编译:
eval
、setTimeout
、setInterval
、new Function()
Trusted Types 要求您在将数据传递给上述接收器函数之前对其进行处理。仅使用字符串将失败,因为浏览器不知道数据是否可信: 错误做法anElement.innerHTML = location.href;
要表示数据已被安全处理,请创建一个特殊对象 - Trusted Type。 正确做法anElement.innerHTML = aTrustedHTML;
Trusted Types 大大减小了应用程序的 DOM XSS 攻击面。它简化了安全审核,并允许您在浏览器中在运行时编译、lint 或捆绑代码时强制执行基于类型的安全检查。
如何使用 Trusted Types #
准备内容安全策略违规报告 #
您可以部署报告收集器(例如开源的 go-csp-collector),或使用具有同等功能的商业工具。还可以在浏览器中调试违规:
window.addEventListener('securitypolicyviolation',
console.error.bind(console));
添加仅报告 CSP 标头 #
将以下 HTTP 响应标头添加到要迁移到 Trusted Types 的文档。
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 违规 #
从现在开始,每次 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 行,使用以 <img src=x
开头的字符串调用了 innerHTML
。此信息应该可以帮助您缩小可能会引入 DOM XSS 并需要更改的代码范围。
修复违规 #
有几个选项用于修复 Trusted Type 违规。您可以删除违规代码,使用库,创建 Trusted Type 策略,或者创建默认策略作为最后手段。
重写违规代码 #
也许不再需要不合规的功能,或者以现代方式(不使用易出错的函数)重写这些功能? 错误做法 正确做法el.innerHTML = '<img src=xyz.jpg>';
el.textContent = '';
const img = document.createElement('img');
img.src = 'xyz.jpg';
el.appendChild(img);
使用库 #
一些库已经生成了可以传递给接收器函数的 Trusted Types。例如,可以使用 DOMPurify 清理 HTML 片段,删除 XSS 有效载荷。
import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});
DOMPurify 支持 Trusted Types,并将返回包装在 TrustedHTML
对象中的经过清理的 HTML,以使浏览器不会产生违规。
创建 Trusted Type 策略 #
有时,无法删除功能,并且没有库来清理值和创建 Trusted Type。在这种情况下,请自行创建 Trusted Type 对象。
为此,首先创建一个策略。策略是 Trusted Types 的工厂,会对其输入强制执行某些安全规则:
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})
});
}
只要在仅接受 Trusted Type 的接收器中使用字符串,就会使用名为 default
的策略。
切换到强制执行内容安全策略 #
当应用程序不再产生违规时,可以开始强制执行 Trusted Types:
Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example
瞧!现在,无论 Web 应用程序有多复杂,唯一可能引入 DOM XSS 漏洞的是您的某个策略中的代码 - 您可以通过限制策略创建来进一步锁定该漏洞。