当来自用户控制的来源(如用户名或从网址片段中获取的重定向网址)的数据到达接收器(一个类似于 eval()
的函数或一个属性 setter,如 .innerHTML
)时,就会发生基于 DOM 的跨站脚本攻击 (DOM XSS)。
DOM XSS 是最常见的 Web 安全漏洞之一,开发团队经常会在其应用中意外引入该漏洞。可信类型为您提供了编写代码、进行安全审核以及确保应用免受 DOM XSS 漏洞攻击的工具,它会默认确保危险的 Web API 函数安全无虞。对于尚不支持可信类型的浏览器,它们可作为 polyfill 使用。
背景
多年来,DOM XSS 一直是最普遍、最危险的网络安全漏洞之一。
跨站脚本攻击有两种。一些 XSS 漏洞是由以不安全的方式创建组成网站的 HTML 代码的服务器端代码引起的。而另一些则是���户端上的根本原因,在这种情况下,JavaScript 代码会使用由用户控制的内容调用危险函数。
为防止服务器端 XSS,请勿通过串联字符串来生成 HTML。请改用安全的上下文自动转义模板库以及基于 Nonce 的内容安全政策,以减少额外的 bug。
现在,浏览器还可以使用可信类型来阻止基于 DOM 的客户端 XSS。
API 简介
可信类型的工作原理是锁定以下有风险的接收器函数。您可能已经认识到其中一些功能,因为浏览器供应商和 Web 框架出于安全考虑已经禁止您使用这些功能。
- 脚本操作:
<script src>
,以及设置<script>
元素的文本内容。 - 从字符串生成 HTML:
- 执行插件内容:
- 运行时 JavaScript 代码编译:
eval
setTimeout
setInterval
new Function()
可信类型要求您先处理数据,然后再将其传递给这些接收器函数。仅使用字符串会失败,因为浏览器不知道数据是否可信:
anElement.innerHTML = location.href;启用可信类型后,浏览器会抛出 TypeError 并阻止使用带有字符串的 DOM XSS 接收器。
为了表示数据已得到安全处理,请创建一个特殊的对象,即信任类型。
anElement.innerHTML = aTrustedHTML;启用可信类型后,对于需要 HTML 代码段的接收器,浏览器会接受
TrustedHTML
对象。此外,还有适用于其他敏感接收器的 TrustedScript
和 TrustedScriptURL
对象。
可信类型可显著减小应用的 DOM XSS 攻击面。它简化了安全审核,并且可让您在浏览器中对代码进行编译、执行 lint 请求或捆绑代码时强制执行基于类型的安全检查。
如何使用可信类型
为收到内容安全政策违规行为举报做好准备
您可以部署报告收集器,例如开源 reporting-api-processor 或 go-csp-collector,也可使用某个商业同类产品。此外,您还可以使用 ReportingObserver 在浏览器中添加自定义日志记录和调试违规行为:
const observer = new ReportingObserver((reports, observer) => {
for (const report of reports) {
if (report.type !== 'csp-violation' ||
report.body.effectiveDirective !== 'require-trusted-types-for') {
continue;
}
const violation = report.body;
console.log('Trusted Types Violation:', violation);
// ... (rest of your logging and reporting logic)
}
}, { buffered: true });
observer.observe();
也可以添加事件监听器:
document.addEventListener('securitypolicyviolation',
console.error.bind(console));
添加仅用于报告的 CSP 标头
将以下 HTTP 响应标头添加到要迁移到可信类型的文档中:
Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example
现在,所有违规行为都会报告给 //my-csp-endpoint.example
,但网站仍可继续运行。下一部分将介绍 //my-csp-endpoint.example
的工作原理。
识别可信类型违规行为
从现在开始,每当可信类型检测到违规行为时,浏览器都会向已配置的 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"
}
}
这表示在第 39 行的 https://my.url.example/script.js
中,使用以 <img src=x
开头的字符串调用了 innerHTML
。这些信息可帮助您缩小可能引入 DOM XSS 且需要更改的代码部分。
修正违规问题
您可以通过多种方法解决可信类型违规行为。您可以移除违规代码、使用��、创建可信类型政策,或者,最后的解决方法是创建默认政策。
重写违规代码
可能不再需要不符合规定的代码,或者可以在不使用导致违规行为的函数的情况下重写这些代码:
el.textContent = ''; const img = document.createElement('img'); img.src = 'xyz.jpg'; el.appendChild(img);
el.innerHTML = '<img src=xyz.jpg>';
使用库
一些库已生成可信类型,您可以将其传递给接收器函数。例如,您可以使用 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, '<')
});
}
此代码会创建一个名为 myEscapePolicy
的政策,该政策可以使用其 createHTML()
函数生成 TrustedHTML
对象。定义的规则会对 <
字符进行 HTML 转义,以防止创建新的 HTML 元素。
请使用如下政策:
const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML); // true
el.innerHTML = escaped; // '<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 漏洞的都是某个政策中的代码,您可以通过限制政策创建来进一步锁定该漏洞。