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 |
瀏覽器相容性
載入中…