ShadowRoot: innerHTML 属性

警告:此属性将输入解析为 HTML,并将结果写入 DOM。像这样的 API 被称为注入槽,如果输入最初来自攻击者,它们可能是跨站脚本(XSS)攻击的潜在途径。

您可以通过始终分配 TrustedHTML 对象而不是字符串并强制执行可信类型来减轻这种风险。有关更多信息,请参阅安全注意事项

ShadowRoot 接口的 innerHTML 属性用于获取或设置 ShadowRoot 内部 DOM 树的 HTML 标记。

获取该属性会返回一个字符串,其中包含阴影根(shadow root)的后代节点的 HTML 序列化表示。

设置该属性接受一个 TrustedHTML 对象或一个字符串。它会将此值解析为 HTML,并用解析结果替换元素的所有后代节点。当设置为 null 值时,该 null 值会被转换为空字符串(""),因此 shadowRoot.innerHTML = null 等同于 shadowRoot.innerHTML = ""

异常

SyntaxError DOMException

如果尝试使用格式不正确的 HTML 字符串来设置 innerHTML 的值,将抛出此异常。

TypeError

如果在可信类型CSP 强制执行且未定义默认策略时将属性设置为字符串,则抛出此错误。

描述

innerHTML 获取阴影根内嵌套的子 DOM 元素的序列化表示,或者设置应该被解析以替换阴影根内 DOM 树的 HTML 或 XML。

请注意,当 <> 字符出现在属性值中时,某些浏览器会将其序列化为 &lt;&gt;(请参阅浏览器兼容性)。这是为了防止潜在的安全漏洞(变异 XSS),其中攻击者可以精心构造绕过净化函数的输入,从而实现跨站脚本(XSS)攻击。

安全注意事项

innerHTML 属性是 跨站脚本(XSS)攻击的潜在攻击向量,在这种攻击中,用户提供的潜在不安全字符串会在未经验证的情况下注入 DOM。虽然该属性可以阻止注入的 <script> 元素执行,但它仍然容易受到攻击者精心构造的、用于运行恶意 JavaScript 的其他 HTML 方法的攻击。例如,下面的示例将在 error 事件处理程序中执行代码,因为 <img>src 值不是有效的图像 URL。

js
const name = "<img src='x' onerror='alert(1)'>";
shadowRoot.innerHTML = name; // shows the alert

您可以通过始终分配 TrustedHTML 对象而不是字符串来缓解这些问题,并通过使用 require-trusted-types-for CSP 指令 强制执行受信任类型。这确保输入会通过一个转换函数,该函数有机会在注入之前 清理输入以移除潜在危险的标记。

示例

读取元素的 HTML 内容

读取 innerHTML 会导致用户代理序列化阴影根的后代节点。

给定以下 HTML

html
<div class="host">
  <template shadowrootmode="open">
    <p>My name is Joe</p>
  </template>
</div>

您可以按照如下所示获取并记录阴影根的标记。

js
const shadowHost = document.querySelector("#host");
const shadowRoot = shadowHost.shadowRoot;
const contents = shadowRoot.innerHTML;
console.log(contents); // "\n  <p>My name is Joe</p>\n"

设置 ShadowRoot 的 innerHTML

在此示例中,我们将通过将 HTML 分配给元素的 innerHTML 属性来替换元素的 DOM。为了降低 XSS 风险,我们将首先从包含 HTML 的字符串创建 TrustedHTML 对象,然后将该对象分配给 innerHTML

受信任的类型尚未在所有浏览器中得到支持,因此我们首先定义 受信任类型 tinyfill。它充当受信任类型 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 = "<p>I might be XSS</p><img src='x' onerror='alert(1)'>";

// Create a TrustedHTML instance using the policy
const trustedHTML = policy.createHTML(untrustedString);

// Get the shadow root
const shadowHost = document.querySelector("#host");
const shadowRoot = shadowHost.shadowRoot;

// Inject the TrustedHTML (which contains a trusted string)
shadowRoot.innerHTML = trustedHTML;

警告:虽然您可以直接将字符串分配给 innerHTML,但这是一种 安全风险,如果待插入的字符串可能包含潜在的恶意内容。

规范

规范
HTML
# dom-shadowroot-innerhtml

浏览器兼容性