JavaScript Sanitizer API:原生WEB安全API出現啦

語言: CN / TW / HK

10月18號, W3C中網絡平台孵化器小組(Web Platform Incubator Community Group)公佈了HTML Sanitizer API的規範草案。這份草案用來解決瀏覽器如何解決XSS攻擊問題。

網絡安全中比較讓開發者們頭疼的一類是XSS跨站點腳本攻擊。這種攻擊通常指的是通過利用網頁開發時留下的漏洞,即將惡意指令代碼注入到網頁,使用户加載並執行攻擊者惡意製造的網頁程序。

這些惡意代碼沒有經過過濾,與網站的正常代碼混在一起,瀏覽器無法分辨哪些內容是可信的,惡意腳本就會被執行。而XSS攻擊的核心有兩個步驟:1、處理攻擊者提交惡意代碼;2、瀏覽器執行惡意代碼。

為了解決在這兩步惡意攻擊中解決這個問題,通常有以下手段,

  1. 增加過濾條件
  2. 只進行純前端行渲染,將數據和代碼內容分開
  3. 對HTML充分轉義

以上手段這些步驟繁瑣,需要注意的內容也很多。為了讓開發者更加便捷地解決XSS攻擊的問題,瀏覽器現提供了原生的XSS攻擊消毒能力。

HTML Sanitizer API——這份由谷歌、Mozilla和Cure53聯手發起提供的API即將最終完成,通過這個瀏覽器原生API我們可以更加輕鬆地保護Web應用程序免受XSS的攻擊。

接下來我們一起來了解一下這個安全API吧。

Sanitizer API簡介

Sanitizer API可以讓瀏覽器直接從網站動態更新的標記中刪除惡意代碼。當有惡意HTML字符串、和文檔或文檔片段對象想插入現有DOM之中,我們可以使用HTML Sanitizer API直接將這些內容清理。有點像電腦的安全衞士應用,可以清除風險內容。

使用Sanitizer API有以下三個優點:

  • 減少Web應用程序中跨站點腳本的攻擊次數
  • 保證HTML輸出內容在當前用户代理中安全使用
  • Sanitizer API 的可用性很強

Sanitizer API的特性

Sanitizer API為HTML字符串安全打開新世界大門,將所有的功能大致分類,可以分為以下三個主要特性:

1.對用户輸入進行殺毒

Sanitizer API的主要功能是接受字符串並將其轉換為更安全的字符串。這些轉換後的字符串不會執行額外的JavaScript,並確保應用程序受到XSS攻擊的保護。

2.瀏覽器內置

該庫在瀏覽器安裝的時候一同預裝,並在發現bug或出現新的攻擊時進行更新。相當於我們的瀏覽器有了內置的殺毒措施,無需導入任何外部庫。

3.使用簡潔安全

在使用了Sanitizer API之後,瀏覽器此時就有了一個強大又安全的解析器,作為一個成熟的瀏覽器,它知道如何處理DOM中每個元素的活動。相比之下,用JavaScript開發的外部解析器不僅成本高昂,同時很容易跟不上前端大環境的更新速度。

説完了這些使用上的亮點特性,讓我們一起來看看這個API的具體用法。

Sanitizer API的使用

Sanitizer API使用Sanitizer()方法構造函數,Sanitizer類進行配置。

官方提供了三種基礎清理方式:

1、清理隱藏上下文的字符串

Element.setHTML() 用於解析和清理字符串,並立即將其插入DOM,這個方法適用於目標DOM元素已知且HTML內容為字符串的情況。

const $div = document.querySelector('div')
const user_input = `<em>Hello There</em><img src="" onerror="alert(0)">` // The user string.
const sanitizer = new Sanitizer() // Our Sanitizer
// We want to insert the HTML in user_string into a target element with id
// target. That is, we want the equivalent of target.innerHTML = value, except
// without the XSS risks.
$div.setHTML(user_input, sanitizer) // <div><em>Hello There</em><img src=""></div>



2、清理給定上下的文字符串

Sanitizer.sanitizeFor() 用於解析、清理和準備稍後準備添加到DOM中的字符串。

適用於HTML內容是字符串,並且目標DOM元素類型已知(例如div、span)的情況。

const user_input = `<em>Hello There</em><img src="" onerror="alert(0)">`
const sanitizer = new Sanitizer()
// Later:
// The first parameter describes the node type this result is intended for.
sanitizer.sanitizeFor("div", user_input) // HTMLDivElement <div>


需要注意的是, HTMLElement中 .innerHTML 的清理輸出結果是字符串格式。

sanitizer.sanitizeFor("div", user_input).innerHTML // <em>Hello There</em><img src="">

3、清理請理節點

對於已經有用户控制的DocumentFragment,Sanitizer.sanitize()可以直接對DOM樹節點進行清理。

// Case: The input data is available as a tree of DOM nodes.
const sanitizer = new Sanitizer()
const $userDiv = ...;
$div.replaceChildren(s.sanitize($userDiv));

除了以上提到的三種方式之外,SanitizerAPI通過刪除和、過濾屬性和標記來修改HTML字符串。

舉個“栗子”。

  • 刪除某些標記(script, marquee, head, frame, menu, object, etc.)並保留content標籤。
  • 移除大多屬性,只保留<a>標籤和colspanson,標籤上的HREF。
  • 篩選出可能導致風險腳本執行的內容。

默認設置中,這個安全API只用來防止XSS的出現。但是一些情況下我們也需要自定義自義設置,下面介紹一些常用的配置。

自定義消毒

創建一個配置對象,並在初始化Sanitizer API時將其傳遞給構造函數。

const config = {
  allowElements: [],
  blockElements: [],
  dropElements: [],
  allowAttributes: {},
  dropAttributes: {},
  allowCustomElements: true,
  allowComments: true
};
// sanitized result is customized by configuration
new Sanitizer(config)


下面是一些常用方法:

  • allowElements 對指定輸入進行保留
  • blockElements blockElements 刪除內容中需要保留的部分
  • dropElements dropElements 刪除指定內容,包括輸入的內容
const str = `hello <b><i>there</i></b>`

new Sanitizer().sanitizeFor("div", str)
// <div>hello <b><i>there</i></b></div>

new Sanitizer({allowElements: [ "b" ]}).sanitizeFor("div", str)
// <div>hello <b>there</b></div>

new Sanitizer({blockElements: [ "b" ]}).sanitizeFor("div", str)
// <div>hello <i>there</i></div>

new Sanitizer({allowElements: []}).sanitizeFor("div", str)
// <div>hello there</div>

  • allowAttributes和dropAttributes這兩個參數可以自定義需要保留或者需要刪除的部分。
const str = `<span id="foo" class="bar" style="color: red">hello there</span>`

new Sanitizer().sanitizeFor("div", str)
// <div><span id="foo" class="bar" style="color: red">hello there</span></div>

new Sanitizer({allowAttributes: {"style": ["span"]}}).sanitizeFor("div", str)
// <div><span style="color: red">hello there</span></div>

new Sanitizer({dropAttributes: {"id": ["span"]}}).sanitizeFor("div", str)
// <div><span class="bar" style="color: red">hello there</span></div>

  • AllowCustomElements開啟是否使用自定義元素
const str = `<elem>hello there</elem>`

new Sanitizer().sanitizeFor("div", str);
// <div></div>

new Sanitizer({ allowCustomElements: true,
                allowElements: ["div", "elem"]
              }).sanitizeFor("div", str);
// <div><elem>hello there</elem></div>


如果沒有進行任何配置,會直接使用默認配置內容。

這個API看起來能為我們解決不小少的問題,但是現在瀏覽器對其的支持還有限,更多功能還在持續完善中。我們也很期待看到功能更加完善的SanitizerAPI

對它感興趣的小夥伴在Chrome93+中可以通過about://flags/#enable-experimental-web-platform-features啟用,Firefox中目前也在實驗階段,可以在about:config將dom.security.sanitizer.enabled 設為true來啟用。

瞭解更多內容可以查看:https://developer.mozilla.org/en-US/docs/Web/API/HTML_Sanitizer_API

關於數據安全的擔憂

根據 Verizon 2020 年數據泄露調查報告(Verizon Business,2020 年)顯示,約90% 的數據泄露事件是由於跨站點腳本((XSS))和安全漏洞造成的。對於前端開發者而言,面對越發頻繁的網絡攻擊,除了藉助Sanitizer API等安全機制外,還可以考慮使用"數據與代碼分離"的SpreadJS等前端表格控件。</a></div>