10月18號, W3C中網路平臺孵化器小組(Web Platform Incubator Community Group)公佈了HTML Sanitizer API的規範草案。這份草案用來解決瀏覽器如何解決XSS攻擊問題。
網路安全中比較讓開發者們頭疼的一類是XSS跨站點指令碼攻擊。這種攻擊通常指的是通過利用網頁開發時留下的漏洞,即將惡意指令程式碼注入到網頁,使使用者載入並執行攻擊者惡意製造的網頁程式。
這些惡意程式碼沒有經過過濾,與網站的正常程式碼混在一起,瀏覽器無法分辨哪些內容是可信的,惡意指令碼就會被執行。而XSS攻擊的核心有兩個步驟:1、處理攻擊者提交惡意程式碼;2、瀏覽器執行惡意程式碼。
為了解決在這兩步惡意攻擊中解決這個問題,通常有以下手段,
- 增加過濾條件
- 只進行純前端行渲染,將資料和程式碼內容分開
- 對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等前端表格控制元件。