Element: setHTMLUnsafe() 方法

基准线 2025
新推出

自 ⁨2025 年 9 月⁩起,此功能适用于最新设备和浏览器版本。此功能可能不适用于较旧的设备或浏览器。

警告:此方法将其输入解析为 HTML,并将结果写入 DOM。此类 API 被称为 注入槽,如果输入最初来自攻击者,则可能成为 跨站点脚本 (XSS) 攻击的载体。

您可以通过始终传递 TrustedHTML 对象而不是字符串并 强制执行可信类型 来缓解此风险。有关更多信息,请参阅 安全注意事项

注意:在支持的浏览器上,几乎总是应该使用 Element.setHTML() 代替此方法,因为它总是会移除不安全的 XSS HTML 实体。

Element 接口的 setHTMLUnsafe() 方法用于将 HTML 输入解析为 DocumentFragment,可选地过滤掉不需要的元素和属性以及那些不属于上下文的元素,然后使用它来替换 DOM 中元素的子树。

语法

js
setHTMLUnsafe(input)
setHTMLUnsafe(input, options)

参数

input

一个 TrustedHTML 实例或定义要解析的 HTML 的字符串。

options 可选

一个包含以下可选参数的 options 对象

sanitizer 可选

一个 SanitizerSanitizerConfig 对象,用于定义输入中允许或移除的元素。这也可以是一个值为 "default" 的字符串,它会应用一个带有默认(XSS 安全)配置的 Sanitizer。如果未指定,则不使用任何清理器。

请注意,如果配置要重复使用,通常 Sanitizer 预计比 SanitizerConfig 更高效。

返回值

无 (undefined)。

异常

TypeError

如果出现以下情况,将抛出此错误

描述

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 安全默认 SanitizersetHTMLUnsafe()

何时应使用 setHTMLUnsafe()

如果 Element.setHTML() 可用,则几乎不应该使用 setHTMLUnsafe(),因为很少有(如果有的话)情况下用户提供的 HTML 输入需要包含 XSS 不安全的元素。setHTML() 不仅安全,而且还避免了考虑受信任类型。

在以下情况下,使用 setHTMLUnsafe() 可能是合适的:

  • 您不能使用 setHTML() 或受信任类型(无论出于何种原因),并且您希望进行尽可能安全的过滤。在这种情况下,您可以使用带有默认 SanitizersetHTMLUnsafe() 来过滤所有 XSS 不安全的元素。

  • 您不能使用 setHTML() 并且输入可能包含声明式 Shadow Root,因此您不能使用 Element.innerHTML

  • 您有一个特殊情况,必须允许包含已知不安全 HTML 实体集的 HTML 输入。

    在这种情况下,您不能使用 setHTML(),因为它会剥离所有不安全的实体。您可以使用不带清理器或 innerHTMLsetHTMLUnsafe(),但这会允许所有不安全的实体。

    这里一个更好的选择是使用一个清理器调用 setHTMLUnsafe(),该清理器只允许我们实际需要的那些危险元素和属性。虽然这仍然不安全,但它比允许所有这些元素和属性更安全。

对于最后一点,考虑您的代码依赖于能够使用不安全的 onclick 处理程序的情况。以下代码显示了此情况下不同方法和清理器的效果。

js
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 的透明替代品。

js
if (typeof trustedTypes === "undefined")
  trustedTypes = { createPolicy: (n, rules) => rules };

接下来,我们创建一个 TrustedTypePolicy,它定义了一个 createHTML(),用于将输入字符串转换为 TrustedHTML 实例。通常,createHTML() 的实现使用像 DOMPurify 这样的库来清理输入,如下所示

js
const policy = trustedTypes.createPolicy("my-policy", {
  createHTML: (input) => DOMPurify.sanitize(input),
});

然后我们使用这个 policy 对象从潜在不安全的输入字符串创建 TrustedHTML 对象

js
// 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() 一起使用。输入已经通过了转换函数,所以我们没有向该方法传递清理器。

js
// Get the target Element with id "target"
const target = document.getElementById("target");

// setHTMLUnsafe() with no sanitizer
target.setHTMLUnsafe(trustedHTML);

在没有受信任类型的情况下使用 setHTMLUnsafe()

此示例演示了我们不使用受信任类型的情况,因此我们将传递清理器参数。

代码创建了一个不受信任的字符串,并展示了将清理器传递给该方法的多种方式。

js
// 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> 元素用于注入字符串。

html
<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 不安全的。我们还定义了重新加载按钮的处理程序。

js
// 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> 元素中的表格特定元素),但其他方面与输入字符串匹配。在这种情况下,字符串应该匹配。

js
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> 不会被移除!

js
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

浏览器兼容性

另见