可信类型是一项安全功能,它要求将传入“危险”浏览器 API(称为接收器)的数据首先转换为特定类型(例如 TrustedHTML),从而帮助防范基于 DOM 的跨站脚本攻击 (DOM XSS)。
默认情况下,许多 DOM API(例如 innerHTML 或 document.write)都接受原始字符串。如果字符串包含来自不可信来源的恶意脚本,则该脚本可能会被执行。可信类型会改变这种行为:通过 CSP 启用后,这些接收器将仅接受由授权政策创建的 TrustedHTML 对象。
关键实现细节
如需使用 Trusted Types,请遵循以下四个主要步骤:
- 定义安全政策,指定如何清理或转换字符串。
- 使用该政策从原始字符串创建可信类型对象。
- 写入 DOM 接收器时,请使用 Trusted Type 对象。
- 使用内容安全政策 (CSP) 强制执行可信类型。
1. 创建政策
政策是一组用于清理内容的规则。您可以使用 trustedTypes.createPolicy() 创建它,通常会委托给净化器库(例如 DOMPurify)或应用的 HTML 转义规则:
import DOMPurify from 'dompurify';
const myPolicy = trustedTypes.createPolicy('sanitize-html', {
createHTML: (input) =>
// Return a sanitized string; createPolicy() wraps it in a TrustedHTML object.
// Example using DOMPurify:
DOMPurify.sanitize(input, { RETURN_TRUSTED_TYPE: false }),
});
// Example using HTML escaping (for environments without third-party sanitizers):
const escapePolicy = trustedTypes.createPolicy('escape-html', {
createHTML: (input) =>
String(input)
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", '''),
});
2. 使用政策
您无需将字符串直接传递给接收器,而是调用政策的创建方法。
const untrustedInput = '<p>Hello, <strong>world</strong>!</p><img src="x" onerror="alert(1)">';
// In an enforced environment, this would throw a TypeError:
// element.innerHTML = untrustedInput;
// This returns a TrustedHTML object with unsafe elements and attributes removed:
const cleanHTML = myPolicy.createHTML(untrustedInput);
// Result: "<p>Hello, <strong>world</strong>!</p><img src=\"x\">"
3. 将接收器转换为使用可信类型
更新您向接收器写入内容的每个实例,以便先将其转换为 Trusted Type 对象。为了帮助找到需要更新代码的位置,您可以定义一个临时 default 政策,以便在接收器被写入字符串(而非 TrustedType)时进行记录。
trustedTypes.createPolicy("default", {
createHTML(value) {
console.warn("String passed instead of Trusted Type.");
return DOMPurify.sanitize(value, { RETURN_TRUSTED_TYPE: false });
},
});
支持可信类型的常见接收器包括:
- HTML:
element.innerHTML、element.outerHTML、document.write()、element.setHTMLUnsafe()、Document.parseHTMLUnsafe() - 脚本:
<script>文本内容,eval() - ScriptURL:
<script src>、Worker()、SharedWorker()
// Assign the TrustedHTML object.
element.innerHTML = cleanHTML;
请注意,“安全”HTML 清理方法(element.setHTML() 和 Document.parseHTML(),在清理不受信任的 HTML 中介绍)不需要可信类型政策,因为它们始终会以原生方式清理潜在的 XSS 攻击。
4. 通过 CSP 强制执行 Trusted Types
应逐步部署可信类型,以免破坏应用。虽然全面强制执行可提供最强大的保护,但如果并非所有接收器写入都已迁移,则存在网站损坏的风险。
前提条件
- 框架协作:如果应用的框架(或任何写入 DOM 接收器的第三方库)不生成
TrustedHTML或TrustedScript值,则您无法在不破坏该代码的情况下强制执行可信类型。在开始发布之前,请先审核框架支持。 - 接收器重构:确保您已识别并重构危险接收器,以便使用可信类型政策。
发布策略
仅报告:首先,使用
Content-Security-Policy-Report-Only标头进行发布。这会将违规行为记录到报告端点,而不会实际阻止写入。Content-Security-Policy-Report-Only: require-trusted-types-for 'script';强制执行:集成政策且违规报告降至零后,即可进入全面强制执行阶段。
Content-Security-Policy: require-trusted-types-for 'script';
您还可以限制允许创建哪些政策(列出您的政策名称,例如 sanitize-html 或 escape-html;如果您的政策委托给 DOMPurify.sanitize(),则添加 dompurify,这会在内部创建 dompurify 政策;如果您在迁移期间注册了默认政策,则添加 default):
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types sanitize-html escape-html dompurify;
实施此 CSP 后,任何不使用可信类型的接收器写入操作都会抛出错误。此外,如果尝试创建名称未在 trusted-types 中列出的可信类型政策,系统会抛出错误。
您也可以在文档的 <head> 中将此 CSP 设置为 <meta> 标记,但这样做的安全性可能较低。
<!-- First child of `<head>`, as earlier tags will not enforce this CSP. -->
<meta
http-equiv="Content-Security-Policy"
content="require-trusted-types-for 'script'; trusted-types sanitize-html escape-html dompurify;"
/>
后备策略
对于尚不支持可信类型的浏览器,您可以使用最精简的“tinyfill”来确保代码不会中断,同时仍提供清理功能。
if (!window.trustedTypes) {
window.trustedTypes = {
createPolicy: (name, rules) => rules,
};
}
这样,您现在就可以使用可信类型模式编写代码。支持该 API 的浏览器会强制执行这些类型,而旧版浏览器仍会通过模拟政策执行您的清理逻辑。
如果浏览器不支持可信类型,则只会将 TrustedHTML 对象视为字符串(如果使用 tinyfill),或者不会在接收器级别进行强制执行检查。