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

Krzysztof Kotowicz
Krzysztof Kotowicz

浏览器支持

  • 83
  • 83
  • x
  • x

来源

当来自用户控制的来源(如用户名或从网址片段中获取的重定向网址)的数据到达接收器(一个类似于 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 框架出于安全考虑已经禁止您使用这些功能。

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

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

为了表示数据已得到安全处理,请创建一个特殊的对象,即信任类型。

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

可信类型可显著减小应用的 DOM XSS 攻击面。它简化了安全审核,并且可让您在浏览器中对代码进行编译、执行 lint 请求或捆绑代码时强制执行基于类型的安全检查。

如何使用可信类型

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

您可以部署报告收集器,例如开源 reporting-api-processorgo-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, '&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 漏洞的都是某个政策中的代码,您可以通过限制政策创建来进一步锁定该漏洞。

深入阅读