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

瀏覽器相容性

另見