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

葡萄城技術團隊發表於2021-12-01

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<td>,<th>標籤上的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等前端表格控制元件。

相關文章