Element: setHTMLUnsafe() 方法
警告:此方法将其输入解析为 HTML,并将结果写入 DOM。此类 API 被称为 注入槽,如果输入最初来自攻击者,则可能成为 跨站点脚本 (XSS) 攻击的载体。
您可以通过始终传递 TrustedHTML 对象而不是字符串并 强制执行可信类型 来缓解此风险。有关更多信息,请参阅 安全注意事项。
注意:在支持的浏览器上,几乎总是应该使用 Element.setHTML() 代替此方法,因为它总是会移除不安全的 XSS HTML 实体。
Element 接口的 setHTMLUnsafe() 方法用于将 HTML 输入解析为 DocumentFragment,可选地过滤掉不需要的元素和属性以及那些不属于上下文的元素,然后使用它来替换 DOM 中元素的子树。
语法
setHTMLUnsafe(input)
setHTMLUnsafe(input, options)
参数
input-
一个
TrustedHTML实例或定义要解析的 HTML 的字符串。 options可选-
一个包含以下可选参数的 options 对象
sanitizer可选-
一个
Sanitizer或SanitizerConfig对象,用于定义输入中允许或移除的元素。这也可以是一个值为"default"的字符串,它会应用一个带有默认(XSS 安全)配置的Sanitizer。如果未指定,则不使用任何清理器。请注意,如果配置要重复使用,通常
Sanitizer预计比SanitizerConfig更高效。
返回值
无 (undefined)。
异常
TypeError-
如果出现以下情况,将抛出此错误
- 当 Trusted Types 由 CSP 强制执行 且未定义默认策略时,
input传递了一个字符串。 options.sanitizer传递了一个- 不是
Sanitizer、SanitizerConfig或字符串的值。 - 非规范化的
SanitizerConfig(包含“允许”和“移除”配置设置)。 - 不具有值
"default"的字符串。
- 不是
- 当 Trusted Types 由 CSP 强制执行 且未定义默认策略时,
描述
setHTMLUnsafe() 方法用于将 HTML 输入解析为 DocumentFragment,可选地清理掉不需要的元素和属性,并丢弃 HTML 规范不允许在目标元素中使用的元素(例如 <li> 在 <div> 内部)。然后使用 DocumentFragment 替换 DOM 中元素的子树。
与 Element.innerHTML 不同,输入中的 声明式 Shadow Root 将被解析到 DOM 中。如果 HTML 字符串在特定的 Shadow Host 中定义了多个 声明式 Shadow Root,则只创建第一个 ShadowRoot — 随后的声明将作为该 Shadow Root 中的 <template> 元素进行解析。
setHTMLUnsafe() 默认不执行任何清理。如果没有将清理器作为参数传递,则输入中的所有 HTML 实体都将被注入。因此,它可能比 Element.innerHTML 更不安全,后者在解析时会禁用 <script> 执行。
安全注意事项
方法名称中的“Unsafe”后缀表示它不强制移除所有不安全的 XSS HTML 实体(与 Element.setHTML() 不同)。虽然如果与适当的清理器一起使用,它可以这样做,但它不必使用有效的清理器,甚至根本不使用任何清理器!因此,该方法是 跨站脚本 (XSS) 攻击的潜在载体,在未经清理的情况下将用户提供的潜在不安全字符串注入到 DOM 中。
您应该通过始终传递 TrustedHTML 对象而不是字符串,并使用 require-trusted-types-for CSP 指令强制执行受信任类型来降低此风险。这确保了输入通过转换函数,该函数有机会清理输入以移除潜在危险的标记(例如 <script> 元素和事件处理程序属性),然后才注入。
使用 TrustedHTML 可以对清理代码的有效性进行少量集中审计和检查,而不是分散在所有注入点中。当使用 TrustedHTML 时,您不应该向该方法传递清理器。
如果由于任何原因您无法使用 TrustedHTML(或者更好的是 setHTML()),那么下一个最安全的选择是使用带有 XSS 安全默认 Sanitizer 的 setHTMLUnsafe()。
何时应使用 setHTMLUnsafe()?
如果 Element.setHTML() 可用,则几乎不应该使用 setHTMLUnsafe(),因为很少有(如果有的话)情况下用户提供的 HTML 输入需要包含 XSS 不安全的元素。setHTML() 不仅安全,而且还避免了考虑受信任类型。
在以下情况下,使用 setHTMLUnsafe() 可能是合适的:
-
您不能使用
setHTML()或受信任类型(无论出于何种原因),并且您希望进行尽可能安全的过滤。在这种情况下,您可以使用带有默认Sanitizer的setHTMLUnsafe()来过滤所有 XSS 不安全的元素。 -
您不能使用
setHTML()并且输入可能包含声明式 Shadow Root,因此您不能使用Element.innerHTML。 -
您有一个特殊情况,必须允许包含已知不安全 HTML 实体集的 HTML 输入。
在这种情况下,您不能使用
setHTML(),因为它会剥离所有不安全的实体。您可以使用不带清理器或innerHTML的setHTMLUnsafe(),但这会允许所有不安全的实体。这里一个更好的选择是使用一个清理器调用
setHTMLUnsafe(),该清理器只允许我们实际需要的那些危险元素和属性。虽然这仍然不安全,但它比允许所有这些元素和属性更安全。
对于最后一点,考虑您的代码依赖于能够使用不安全的 onclick 处理程序的情况。以下代码显示了此情况下不同方法和清理器的效果。
const target = document.querySelector("#target");
const input = "<img src=x onclick=alert('onclick') onerror=alert('onerror')>";
// Safe - removes all XSS-unsafe entities.
target.setHTML(input);
// Removes no event handler attributes
target.setHTMLUnsafe(input);
target.innerHTML = input;
// Safe - removes all XSS-unsafe entities.
const configSafe = new Sanitizer();
target.setHTMLUnsafe(input, { sanitizer: configSafe });
// Removes all XSS-unsafe entities except `onclick`
const configLessSafe = new Sanitizer();
config.allowAttribute("onclick");
target.setHTMLUnsafe(input, { sanitizer: configLessSafe });
示例
setHTMLUnsafe() 与受信任类型
为了减轻 XSS 的风险,我们将首先从包含 HTML 的字符串创建一个 TrustedHTML 对象,然后将该对象传递给 setHTMLUnsafe()。由于受信任类型尚未在所有浏览器上受支持,我们定义了 受信任类型微型填充。它充当受信任类型 JavaScript API 的透明替代品。
if (typeof trustedTypes === "undefined")
trustedTypes = { createPolicy: (n, rules) => rules };
接下来,我们创建一个 TrustedTypePolicy,它定义了一个 createHTML(),用于将输入字符串转换为 TrustedHTML 实例。通常,createHTML() 的实现使用像 DOMPurify 这样的库来清理输入,如下所示
const policy = trustedTypes.createPolicy("my-policy", {
createHTML: (input) => DOMPurify.sanitize(input),
});
然后我们使用这个 policy 对象从潜在不安全的输入字符串创建 TrustedHTML 对象
// The potentially malicious string
const untrustedString = "abc <script>alert(1)<" + "/script> def";
// Create a TrustedHTML instance using the policy
const trustedHTML = policy.createHTML(untrustedString);
现在我们有了 trustedHTML,下面的代码展示了如何将其与 setHTMLUnsafe() 一起使用。输入已经通过了转换函数,所以我们没有向该方法传递清理器。
// Get the target Element with id "target"
const target = document.getElementById("target");
// setHTMLUnsafe() with no sanitizer
target.setHTMLUnsafe(trustedHTML);
在没有受信任类型的情况下使用 setHTMLUnsafe()
此示例演示了我们不使用受信任类型的情况,因此我们将传递清理器参数。
代码创建了一个不受信任的字符串,并展示了将清理器传递给该方法的多种方式。
// The potentially malicious string
const untrustedString = "abc <script>alert(1)<" + "/script> def";
// Get the target Element with id "target"
const target = document.getElementById("target");
// Define custom Sanitizer and use in setHTMLUnsafe()
// This allows only elements: div, p, button, script
const sanitizer1 = new Sanitizer({
elements: ["div", "p", "button", "script"],
});
target.setHTMLUnsafe(untrustedString, { sanitizer: sanitizer1 });
// Define custom SanitizerConfig within setHTMLUnsafe()
// Removes the <script> element but allows other potentially unsafe entities.
target.setHTMLUnsafe(untrustedString, {
sanitizer: { removeElements: ["script"] },
});
setHTMLUnsafe() 实时示例
此示例提供了该方法在调用不同清理器时的“实时”演示。代码定义了可以单击的按钮来注入 HTML 字符串。一个按钮完全不清理就注入 HTML,第二个按钮使用自定义清理器,允许 <script> 元素但不允许其他不安全的项目。原始字符串和注入的 HTML 都已记录,因此您可以在每种情况下检查结果。
注意:因为我们想展示清理器参数是如何使用的,所以以下代码注入的是字符串而不是受信任类型。您不应该在生产代码中这样做。
HTML
HTML 定义了两个 <button> 元素,用于调用具有不同清理器的方法,另一个按钮用于重置示例,以及一个 <div> 元素用于注入字符串。
<button id="buttonNoSanitizer" type="button">None</button>
<button id="buttonAllowScript" type="button">allowScript</button>
<button id="reload" type="button">Reload</button>
<div id="target">Original content of target element</div>
JavaScript
首先,我们定义要清理的字符串,所有情况下都相同。它包含 <script> 元素和 onclick 处理程序,两者都被认为是 XSS 不安全的。我们还定义了重新加载按钮的处理程序。
// Define unsafe string of HTML
const unsanitizedString = `
<div>
<p>This is a paragraph. <button onclick="alert('You clicked the button!')">Click me</button></p>
<script src="path/to/a/module.js" type="module"><script>
</div>
`;
const reload = document.querySelector("#reload");
reload.addEventListener("click", () => document.location.reload());
接下来,我们为不带清理器设置 HTML 的按钮定义点击处理程序。通常,我们希望该方法删除字符串中不属于上下文的元素(例如 <div> 元素中的表格特定元素),但其他方面与输入字符串匹配。在这种情况下,字符串应该匹配。
const buttonNoSanitizer = document.querySelector("#buttonNoSanitizer");
buttonNoSanitizer.addEventListener("click", () => {
// Set unsafe HTML without specifying a sanitizer
target.setHTMLUnsafe(unsanitizedString);
// Log HTML before sanitization and after being injected
logElement.textContent =
"No sanitizer: string should be injected without filtering\n\n";
log(`\nunsanitized: ${unsanitizedString}`);
log(`\nsanitized: ${target.innerHTML}`);
});
下一个点击处理程序使用自定义清理器设置目标 HTML,该清理器只允许 <div>、<p> 和 <script> 元素。请注意,因为我们使用的是 setHTMLUnsafe() 方法,所以 <script> 不会被移除!
const allowScriptButton = document.querySelector("#buttonAllowScript");
allowScriptButton.addEventListener("click", () => {
// Set the content of the element using a custom sanitizer
const sanitizer1 = new Sanitizer({
elements: ["div", "p", "script"],
});
target.setHTMLUnsafe(unsanitizedString, { sanitizer: sanitizer1 });
// Log HTML before sanitization and after being injected
logElement.textContent = "Sanitizer: {elements: ['div', 'p', 'script']}\n";
log(`\nunsanitized: ${unsanitizedString}`);
log(`\nsanitized: ${target.innerHTML}`);
});
结果
单击“无”和“allowScript”按钮,分别查看无清理器和自定义清理器的效果。
当您单击“无”按钮时,您应该看到输入和输出匹配,因为没有应用清理器。当您单击“allowScript”按钮时,<script> 元素仍然存在,但 <button> 元素被移除。使用这种方法,您可以创建安全的 HTML,但您不必这样做。
规范
| 规范 |
|---|
| HTML # dom-element-sethtmlunsafe |
浏览器兼容性
加载中…