4. XSS攻擊

了解了如何優化我們的系統後,看似已經堅不可摧。但優化架構充其量只是提高效能,這很明顯不是駭客不攻擊你的原因,在高效能的系統如果系統存在安全漏洞,駭客還是能輕易繞過所有的防線 !!

這篇就來聊聊,蟬聯網頁安全漏洞排行榜(OWASP Top 10)多年的頭號殺手——XSS 攻擊(Cross-Site Scripting,跨網站指令碼攻擊)

簡介

XSS 的核心原理非常簡單:「借刀殺人」

駭客利用網站對使用者輸入內容缺乏檢查的漏洞,將惡意指令(JavaScript 程式碼)注入到網頁中。因為瀏覽器不會檢查下載下來的JS,因此當其他無辜的使用者瀏覽這個網頁時,瀏覽器就會乖乖執行這段惡意指令。

一小段惡意 JS 程式碼能做什麼?它能偷走使用者的 Cookie(代表你的登入狀態)、盜刷信用卡,甚至直接竄改網頁內容。

XSS 攻擊依據惡意程式碼「藏在哪裡」,主要分為三大流派:

Stored XSS

儲存型XSS,這是破壞力最強、影響範圍最廣的 XSS 類型。顧名思義,儲存型XSS就是將一段惡意指令,直接儲存在伺服器中,只要有人試圖調用伺服器的內容就會觸發指令。

駭客會在網站的留言板、評論區或個人簡介中,輸入一段惡意程式碼(如:<script>偷Cookie的程式碼</script>),然後按下送出。這段惡意程式碼穿過 Web Server,被永久儲存到了後端的資料庫(Database)倉庫裡

從此以後,只要有任何人(Client)點進這個留言板,Application Server 就會從資料庫把這段有毒的留言撈出來、回傳給瀏覽器。瀏覽器不知道有毒,當場執行,登入憑證等瞬間被駭客偷走。

Reflected XSS

反射型XSS,這種攻擊不會把資料存進資料庫,而是像迴力鏢一樣,點下去的瞬間立刻反射回來。

現代網站通常都會有一個搜尋功能,實現「搜尋」這件事,通常是對後端發起帶參數的GET,假如你在google中搜尋貓咪圖片,Client實際會向google的伺服器發送一段GET請求,長這樣:https://www.google.com/search?q=貓咪+圖片

聰明的駭客便想到將關鍵字改成一段代碼,組裝成一個惡意連結:https://www.google.com/search?q=<script>惡意程式</script>,這個 Request 帶著有毒的參數衝向 Web Server,Application Server 沒多想,直接在 Response 中把這個關鍵字吐回給瀏覽器:「您搜尋的關鍵字是 <script>惡意程式</script>」。瀏覽器一讀到,當場當作指令執行,受害者在不知不覺中被攻擊。

DOM-based XSS

DOM 型XSS完全發生在「使用者的瀏覽器(前端)」內部,完全不會碰到後端 Server

同樣是誘騙使用者點擊惡意網址:https://www.google.com/search?q=<script>惡意程式</script> ,但這一次,前端網頁的 JavaScript 程式碼寫得不夠安全,如果裡面有一段寫法是:「直接讀取網址列 # 後面的文字,並用 document.write() 寫進網頁畫面上」,當網頁載入時,瀏覽器內部的 JS 引擎在解析網頁(DOM)的過程中,自己去抓網址列的病毒字串,然後自己動手把惡意程式碼塞進了 DOM 樹裡。

這種攻擊的特性是它是屬於純前端漏洞,後端防禦完全防不到它。

防範措施

面對這三種無孔不入的 JS 毒素,身為網頁開發者,除了避免使用 innerHTML ,改用 textContent 或 DOMPurify之外,我們還能怎麼防禦呢?

對使用者的輸入「徹底不信任」(Input Validation & Sanitization)

不論前端還是後端,收到使用者傳來的任何資料(留言、搜尋字詞),都要進行過濾或阻擋。

  • 黑名單限制:只要發現輸入內容包含 <script>javascript: 等敏感字眼,直接拒絕接收。
  • 使用知名套件套件(如 DOMPurify):在資料要存進資料庫或渲染到前端前,先用專門的工具把危險的 HTML 標籤與屬性徹底「洗乾淨」。

全面字元跳脫(HTML Entity Encoding)—— 最核心的絕招

瀏覽器之所以會中毒,是因為它把使用者輸入的 <> 誤當成了 HTML 標籤指令。

我們只要把這些特殊符號,轉換成「純文字的化身(HTML 實體編碼)」即可:

  • < 轉換為 &lt;
  • > 轉換為 &gt;

當瀏覽器看到 &lt;script&gt; 時,JS 引擎就不會把它當成程式碼執行,而是把它當作一般的文字「字串」,原封不動地在畫面上顯示出 <script> 的字樣。這招能瞬間讓 99% 的 Stored 和 Reflected XSS 失去武功。

駭客大費周章打入 XSS,最主要的目的就是用 document.cookie 去偷使用者的登入權杖。

  • 解法:後端伺服器在發放敏感的 Session Cookie 時,必須在 Header 中加上 ; HttpOnly
  • 效果:一旦開啟,瀏覽器的 JavaScript 就會被徹底剝奪讀取該 Cookie 的權限。就算駭客成功發動了 XSS 攻擊、在網頁裡塞了惡意代碼,也絕對偷不走你的登入狀態。

設定內容安全政策(CSP, Content Security Policy)

這是在 HTTP 回應標頭(Response Header)中加入的最高安全指導原則。

  • 開發者可以告訴瀏覽器:「這個網頁只允許下載並執行來自 google.com 或是我們自己網域的 JS 檔案。如果發現網頁裡有來路不明、或者是使用者自己貼在留言板上的 inline JS 程式碼,一律強制封鎖不准執行!」

問!

Q:在現代網頁開發中(例如使用 React 或 Next.js 框架),如果我們直接在畫面顯示變數:<div>{userInput}</div>,React 會自動幫我們做好「HTML 跳脫(Encoding)」,讓 XSS 難以得逞。但 React 卻保留了一個名字很可怕的語法叫做 dangerouslySetInnerHTML。請結合本章所學,思考為什麼這個語法會被冠上「dangerously(危險)」的字眼?如果非用它不可,工程師該做好什麼準備?

  • 答案引導建議
    1. 危險原因:因為這個語法會跳過 React 內建的防禦機制,將使用者輸入的字串「毫無保留、原汁原味」地當作 HTML 指令直接塞進 DOM 樹中。如果 userInput 裡藏有惡意程式碼,就會當場引爆 DOM 型或儲存型 XSS 攻擊
    2. 防範準備:如果業務需求非得渲染富文本(如部落格編輯器輸出的 HTML),在將資料傳入 dangerouslySetInnerHTML 之前,工程師必須絕對確保該資料已經過 DOMPurify 等快取/洗滌套件過濾,將危險的指令標籤徹底清除。

練習

是看看當駭客是什麼感覺吧 (?)

XSS Game

小結

了解了網頁效能再高,也難防「 XSS(跨網站指令碼攻擊)將惡意 JavaScript 程式碼偽裝成正常資料,借瀏覽器的手來傷害使用者」後,相信大家對資安的重要性也有了深刻的體悟,網路世界沒有絕對的安全! 不管今天是身為使用者還是開發者,都要一再的小心!網路世界不要跟人家談什麼信任,你不相信人家,人家也不相信你,謹慎為上就是最好的防禦!!