XSS 攻擊和防堵

XSS(Cross Site Scripting) 是一種從網頁的漏洞下手,插入惡意程式碼的攻擊方式。攻擊本身是不給 server 帶來傷害,會造成傷害的是其他使用者。手法是在網站上一些可以讓使用者輸入的地方埋入 html 或是 JavaScript 的惡意腳本,讓其他使用者在瀏覽這個網頁時可以在背後竊取使用者的 cookie 送到指定伺服器或是引導到虛假頁面。

大致分成三種類型:

Stored XSS (儲存型)

經由使用者輸入,然後被存在 server 資料庫中的 JavaScript,若其後用來作為網頁顯示的時候,沒有過濾或是 encode 處理會被視為正常的 JavaScript 執行,藉此達到攻擊別的使用者的效果。常見的場景如論壇文章、留言板等公開的頁面。

舉個例子,假設有一個 input 可以給使用者留言,如下

1
<input type="text" placeholder="請留言" />

然後攻擊者在 input 輸入下面的內容

1
><script>alert("XSS攻擊");</script>

這樣惡意程式碼會被存在資料庫,之後在瀏覽器解析的時候若沒有經過處理則會被當成一般的 JavaScript 執行,達到攻擊的效果。

Reflected XSS (反射型)

反射型 xss 不會被儲存在資料庫中,而是 server 收到來自使用者的請求後,未做檢查就將使用者的請求原封不動顯示於回應的網頁當中以達成攻擊效用。可能會出現在 GET 請求中,常見的場景如網頁的搜尋功能,並且只要將帶有惡意程式碼的網址埋在受害者可能會點擊的地方或是分享給受害者點擊,等待受害者上鉤即可。

假設有一個網站有個搜尋功能如下

1
https://example.com?search=xxx

在搜尋完後會顯示結果在網頁上,DOM 可能會長這樣:

1
有關<span name="search">{{$search}}</span>的搜尋結果:

如果我們在搜尋的時候加入如下的惡意程式碼,等到網頁回傳搜尋結果時就達成攻擊

1
2
3
4
5
6
7
http://example.com?search=<script>alert(document.cookie);</script>

http://example.com?search="><script >alert(document.cookie)</script >

http://example.com?search="><ScRiPt>alert(document.cookie)</ScRiPt>

http://example.com?search="%3cscript%3ealert(document.cookie)%3c/script%3e

DOM-Based XSS

藉由 DOM 的漏洞來攻擊,在 DOM 埋入惡意程式碼,藉此來攻擊。

假設網頁上有一段 DOM 如下,

1
2
3
4
5
6
7
8
9
10
<script>
var send = function() {
var name = document.getElementById('your_name').value;
document.getElementById('name').innerHTML = name;
}

</script>
<span id="name"></span>
<input type="text" id="your_name" />
<button id="btn" onclick="send();">send</button>

如果輸入下面的內容,就可以破壞 DOM,執行 JavaScript 達成攻擊了,可以到這裡測試。

1
<img src=# onerror="alert('XSS 攻擊');">

但被害者不可能自己輸入這種惡意程式碼,除非攻擊者親自到受害者電腦前輸入,否則無法達成攻擊。因此 DOM-Based XSS 通常需要搭配前面兩個手法。讓內容保存在伺服器資料庫中,或是以反射型的方式製造出內容,再藉由 JavaScript 動態產生有效的 DOM 物件來運行惡意程式碼。

根據這三種類型可以整理出以下的表格:

惡意程式碼 存放的位置 插入點
儲存型 XSS 後端資料庫 HTML
反射型 XSS URL HTML
DOM 型 XSS 後端資料庫/前端儲存/URL 前端 JavaScript

防堵方法

一般來說,防堵的方式不外乎幾種,像是對於使用者傳來的內容做驗證、過濾或是轉譯。驗證的話,有些若是有明確需求的欄位如名字、電話、email 可以使用正則式做驗證,拒絕驗證失敗的內容,此為白名單的作法。而另外一種是只要遇到指定的字元如 <、>、”、’、& 一律過濾掉或是轉譯,但是即便是這樣,攻擊者還是有很多手法可以規避,例如改變字元大小寫、填充額外字元像是空白、使用 URL 編碼、或是使用等價字元等都可以規避驗證。而且可以被惡意注入的地方從 HTML 到 JS 再到 CSS 都有可能 ,所以 XSS 手法變化萬千,如何防堵是個長遠的議題。

個人傾向在輸出時做防堵,不要在存進資料庫時做 encode。
原因如下:

  • 如果 encode 的方式選錯或是要修改處理會很麻煩
  • 如果有搜尋的需求一樣要根據 encode 的方式處理
  • 根據資料之後可能會被應用的場景不同,可能需要做不同的處理,事先 encode 可能會很麻煩

特別說明一下,這邊是指輸入至 database 不過濾或是 encode html 和 JavaScript,但是仍要防堵 SQL injection。

那麼具體來說防堵要怎麼做呢?
以下針對不同類型的 XSS 討論一下各別的防堵方法:
首先,儲存型和反射型 XSS 都是在前端把惡意程式碼插入到 HTML 上,然後讓瀏覽器執行惡意的 JavaScript 達成攻擊。

防堵的方法有兩種:

  • 改成純前端渲染,把程式碼和資料分隔開
    一開始先載入一個不含資料的 HTML,資料都是後來由 js append 上 HTML,並且使用屬性明確的函式處理,例如 .innerText、.setAttribute、.style,避免惡意的程式碼被執行。
  • 對 HTML 做充分轉義
    如果應用不適合使用純前端渲染的模式,例如有 SEO 需求的考量,需要拼接 HTML,就必須善用模版引擎(template engine) 或是轉譯庫套件

而 DOM-Based XSS 則是因為 JavaScript 在執行過程中未對使用者輸入的資料做完善的檢查,導致惡意程式碼被插入 DOM 所產生的攻擊。防堵的方式為儘量減少使用 .innerHTML、.outerHTML、.document.write()等方法,轉而使用.textContent、.setAttribute等。此外,DOM 裡的一些監聽事件如 location、onclick、onerror、onload、onmouseover、以及 <a> 標籤的 href 屬性都能把字串作為程式碼執行,使用時需要非常小心。

結論

XSS 的重點在於輸出端的防堵,所以謹慎處理從使用者端取得的資料以及過濾敏感字元是可以防堵簡單的 XSS 攻擊,但仍可能被破解。XSS 的攻擊往往道高一尺魔高一丈,善用成熟的模版引擎以及轉譯套件庫也是可以參考的選項。並且時常閱讀 XSS Prevention Cheat Sheet 相關的文件以及注意 OWASP ESAPI(Enterprise Security API)這個專案上的最新攻擊手法,才能儘量降低服務被 XSS 攻擊的風險。

Reference

XSS Filter Evasion Cheat Sheet

前端安全系列(一):如何防止XSS攻擊?

DOM Based XSS

XSS與ESAPI

Testing for Reflected Cross site scripting (OTG-INPVAL-001)