Vue3 中使用 DOMPurify 避免 XSS 攻擊

XSS

前言

在 Vue 中,我們可以使用 v-html 來將 HTML 字串轉換成 DOM 元素,但是這樣會有一個問題,就是容易受到 XSS 攻擊,因為我們無法確保使用者輸入的內容是否安全,所以我們需要對使用者輸入的內容進行過濾,避免惡意的腳本被執行。

DOMPurify

在 Vue 中,總共有三種渲染資料的方式,分別是:

  • mustache interpolations,也就是 {{ myName }} 的方式
  • v-text
  • v-html

其中,{{ myName }}v-text 都是將資料轉換成純文字,而 v-html 則是將資料轉換成 HTML 元素,而這個 v-html 類似於 innerHTML,但本質上不是 innerHTML,因為 v-html 是具備雙向綁定能力的,而 innerHTML 則不具備雙向綁定能力。

See the Pen v-html by Ray (@hsiangfeng) on CodePen.

我們都知道使用 innerHTML 直接將 HTML 渲染出來是一件很危險的事情,是有可能發生 XSS 攻擊的,雖然使用 mustache interpolations or v-text 指令是可以避免這個問題存在,因為框架本身就會幫我們過濾掉 HTML 標籤,但是有時候我們還是需要使用 v-html 來渲染 HTML。

那…什麼狀況下會需要使用 v-html 呢?比較常見就是讓使用者可以輸入 HTML 內容的場景,最常見的功能就是 Editor,像是部落格、留言板等等,這時候我們就需要使用 v-html 來渲染使用者輸入的 HTML 內容。

這時候我們就可以使用 DOMPurify 來避免 XSS 攻擊,DOMPurify 是可以將危險的標籤給過濾掉的套件,所以我們可以使用 DOMPurify 來過濾掉危險的標籤,避免惡意的腳本被執行。

首先我先寫一個範例,假裝當使用者輸入了一個惡意的腳本,然後我們使用 v-html 來渲染這個腳本,看看會發生什麼事情

See the Pen v-html ajax by Ray (@hsiangfeng) on CodePen.

上方的範例是一個 Vue3 Composition API,當你點下 Get Data 的按鈕後,接著過五秒後會將資料渲染到畫面上,這時候你會發現畫面上出現了一個 Image 的標籤,但是這個標籤卻是一個惡意的腳本,因為這個標籤的圖片並不會出現,因此就會觸發了 onerror 事件,接著就會執行 alert,這就是一個 XSS 攻擊。

那我們要怎麼避免這個問題呢?前面有提到我們將會使用 DOMPurify 來過濾掉危險的標籤,所以安裝方式有兩種,一種是使用 npm 安裝,另一種是使用 CDN,這邊為了方便使用以及介紹,所以我會使用 CDN 的方式

1
<script src='https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.6/purify.min.js' integrity='sha512-H+rglffZ6f5gF7UJgvH4Naa+fGCgjrHKMgoFOGmcPTRwR6oILo5R+gtzNrpDp7iMV3udbymBVjkeZGNz1Em4rQ==' crossorigin='anonymous'></script>

底下我也提供一個範例 CodePen,方便你可以 Fork 回家使用:

See the Pen v-html ajax XSS by Ray (@hsiangfeng) on CodePen.

那麼該如何使用 DOMPurify 呢?其實非常簡單,你只需要使用 sanitize 這個方法,然後將你要過濾的 HTML 字串傳入,就可以得到一個過濾過的 HTML 字串,接著你就可以使用 v-html 來渲染這個字串,這樣就可以避免 XSS 攻擊了

See the Pen v-html DOMPurify by Ray (@hsiangfeng) on CodePen.

上方範例你可以發現當你點下 Get Data 按鈕後,雖然還是會出現死圖,但是卻不會觸發 onerror 事件,因此就不會有 XSS 攻擊的問題了,而這就是 DOMPurify 的功用。

關於 DOMPurify 其他的用法

這時候你就好奇了,那我要怎麼知道 DOMPurify 過濾掉了哪些標籤呢?其實你可以使用 DOMPurify.removed 來得知,這個屬性會回傳一個陣列,裡面包含了被過濾掉的標籤,這樣你就可以知道 DOMPurify 到底過濾掉了哪些標籤了。

1
2
3
const clean = DOMPurify.sanitize('<img src="x" onerror="alert(1)">');

console.log(DOMPurify.removed); // [{...}]

過濾的標籤

我們可以看到陣列中包含了一個物件,然後物件中有兩個屬性,分別是

  • attribute:被過濾掉的屬性
  • from:被過濾後的 HTML 標籤

但要稍微注意一下 DOMPurify.removed 這屬性只是讓你看一下而已,如官方文件所說,請不要使用這個屬性作為你的程式邏輯。

通常我們實戰開發上一定不會通通過濾掉,可能會有一些標籤是可以被接受的,這時候我們就可以使用 DOMPurify.sanitize 來自訂過濾的規則,這個方法可以接受兩個參數,分別是:

  • dirty:要過濾的 HTML 字串
  • config:過濾的規則

那麼這部分就滿多的,我就不多做介紹建議直接觀看 官方文件 為主哩~

什麼時候該使用 DOMPurify?

其實使用的場景比較常見於,資料是由使用者輸入的,像是 Editor、留言板等等,如同前面所講的,現今的框架都具備基本的 XSS 防禦機制,並且我們將資料儲存到資料庫時,後端也會對資料進行過濾。

那竟然後端以及前端框架都有防禦機制,那我們還需要使用 DOMPurify 嗎?其實這取決於使用場景,以 Vue 來講,如果你渲染資料的方式是使用 {{ myName }} 或是 v-text,那其實就不需要使用 DOMPurify 了,因為 Vue 會幫你過濾掉 HTML 標籤,但是如果你使用 v-html 來渲染資料,那就需要使用 DOMPurify 來過濾掉危險的標籤。

所以簡單來講,如果你的功能如果是需要直接渲染 HTML 字串,那就會建議你在前端使用 DOMPurify 來過濾掉危險的標籤,這樣子就可以避免惡意的腳本被執行哩

Liker 讚賞

這篇文章如果對你有幫助,你可以花 30 秒登入 LikeCoin 並點擊下方拍手按鈕(最多五下)免費支持與牡蠣鼓勵我。
或者你可以也可以請我「喝一杯咖啡(Donate)」。

Buy Me A Coffee Buy Me A Coffee

Google AD

撰寫一篇文章其實真的很花時間,如果你願意「關閉 Adblock (廣告阻擋器)」來支持我的話,我會非常感謝你 ヽ(・∀・)ノ