Sanitizer:給你的DOM消消毒
大家好,我卡頌。
業(yè)務(wù)中經(jīng)常遇到需要處理「有風險的DOM」的場景,比如:
- 各種工具的文本粘貼功能
- 需要渲染服務(wù)端返回HTML的場景
為了阻止?jié)撛诘腦SS攻擊,有兩個選擇:
- escape(轉(zhuǎn)義)
- sanitize(消毒)
本文會介紹這兩者的區(qū)別以及為DOM消毒的API —— Sanitizer。
本文內(nèi)容來自Safe DOM manipulation with the Sanitizer API[1]
轉(zhuǎn)義與消毒
假設(shè),我們想將這樣一段HTML字符串插入DOM:
- const str = "<img src='' onerror='alert(0)'>";
如果直接將其作為某個元素的innerHTML,img的onerror回調(diào)執(zhí)行JS代碼的能力會帶來XSS風險。
一種常見解決方案是:轉(zhuǎn)義字符串。
什么是escape
瀏覽器會將一些保留字符解析為HTML代碼,比如:
- <被解析為標簽的開頭
- >被解析為標簽的結(jié)尾
- ''被解析為屬性值的開頭和結(jié)尾
為了將這些保留字符顯示為文本(不被解析為HTML代碼),可以將其替換為對應(yīng)的entity(HTML實體):
- <的實體為<
- >的實體為>
- ''的實體為"
這種將HTML字符替換為entity的方式被稱為escape(轉(zhuǎn)義)
什么是sanitize
對于上面的HTML字符串:
- const str = "<img src='' onerror='alert(0)'>";
除了轉(zhuǎn)義''來規(guī)避XSS風險,還有一種更直觀的思路:直接過濾掉onerror屬性。
這種直接移除HTML字符串中有害的代碼(比如<script>)的方式被稱為sanitize(消毒)
需要用到一個API——Sanitizer[2]。
首先我們通過Sanitizer構(gòu)造實例:
- const sanitizer = new Sanitizer();
調(diào)用實例的sanitizeFor方法,傳入容器元素類型以及要消毒的HTML字符串:
- sanitizer.sanitizeFor("div", str);
會得到一個HTMLDivElement(即我們傳入的容器元素類型),其內(nèi)部包含一個沒有onerror屬性的img:
默認情況下Sanitizer會移除所有可能導致JS執(zhí)行的代碼。
豐富的配置
Sanitizer不僅開箱即用,還提供豐富的白名單、黑名單配置:
- const config = {
- allowElements: [],
- blockElements: [],
- dropElements: [],
- allowAttributes: {},
- dropAttributes: {},
- allowCustomElements: true,
- allowComments: true
- };
- new Sanitizer(config)
比如,allowElements定義元素白名單,只有名單內(nèi)的元素會被保留,與之對應(yīng)的blockElements是元素黑名單:
- const str = `hello <b><i>world</i></b>`
- new Sanitizer().sanitizeFor("div", str)
- // <div>hello <b><i>world</i></b></div>
- new Sanitizer({allowElements: [ "b" ]}).sanitizeFor("div", str)
- // <div>hello <b>world</b></div>
- new Sanitizer({blockElements: [ "b" ]}).sanitizeFor("div", str)
- // <div>hello <i>world</i></div>
- new Sanitizer({allowElements: []}).sanitizeFor("div", str)
- // <div>hello world</div>
allowAttributes是屬性白名單,與之對應(yīng)的dropAttributes是屬性黑名單,對于如下配置:
- {
- allowAttributes: {"style": ["span"]},
- dropAttributes: {"id": ["*"]}}
- }
代表消毒后的HTML:
- 只允許span元素擁有style屬性
- 移除所有元素(*通配符代表所有元素)的id屬性
兼容性
這么香的API兼容性怎么樣呢:
當前只有在Chrome 93之后,開啟試驗標識后可使用:
- about://flags/#enable-experimental-web-platform-features
雖然原生Sanitizer離穩(wěn)定還遙遙無期,但你可以使用DOMPurify[3]庫實現(xiàn)類似功能。
后記
日常你更傾向使用escape還是sanitize呢?
參考資料
[1]Safe DOM manipulation with the Sanitizer API:
https://web.dev/sanitizer/
[2]Sanitizer:
https://wicg.github.io/sanitizer-api/
[3]DOMPurify:
https://github.com/cure53/DOMPurify