歡迎光臨
每天分享高質量文章

如何防止 XSS 攻擊?

來自:美團技術團隊(微信號:meituantech)

作者:李陽,美團點評前端工程師。2016年加入美團點評,負責美團外賣 Hybrid 頁面性能優化相關工作。

鏈接:https://tech.meituan.com/fe_security.html


背景

隨著互聯網的高速發展,信息安全問題已經成為企業最為關註的焦點之一,而前端又是引發企業安全問題的高危據點。在移動互聯網時代,前端人員除了傳統的 XSS、CSRF 等安全問題之外,又時常遭遇網絡劫持、非法呼叫 Hybrid API 等新型安全問題。當然,瀏覽器自身也在不斷在進化和發展,不斷引入 CSP、Same-Site Cookies 等新技術來增強安全性,但是仍存在很多潛在的威脅,這需要前端技術人員不斷進行“查漏補缺”。

前端安全

近幾年,美團業務高速發展,前端隨之面臨很多安全挑戰,因此積累了大量的實踐經驗。我們梳理了常見的前端安全問題以及對應的解決方案,將會做成一個系列,希望可以幫助前端同學在日常開發中不斷預防和修複安全漏洞。本文是該系列的第一篇。

今天我們講解一下 XSS ,主要包括:

  1. XSS 攻擊的介紹

  2. XSS 攻擊的分類

  3. XSS 攻擊的預防和檢測

  4. XSS 攻擊的總結

  5. XSS 攻擊案例

XSS攻擊的介紹


在開始本文之前,我們先提出一個問題,請判斷以下兩個說法是否正確:


  1. XSS 防範是後端 RD (研發人員)的責任,後端 RD 應該在所有用戶提交資料的接口,對敏感字符進行轉義,才能進行下一步操作。

  2. 所有要插入到頁面上的資料,都要通過一個敏感字符過濾函式的轉義,過濾掉通用的敏感字符後,就可以插入到頁面中。

如果你還不能確定答案,那麼可以帶著這些問題向下看,我們將逐步拆解問題。

XSS 漏洞的發生和修複


XSS 攻擊是頁面被註入了惡意的代碼,為了更形象的介紹,我們用發生在小明同學身邊的事例來進行說明。


一個案例


某天,公司需要一個搜索頁面,根據 URL 引數決定關鍵詞的內容。小明很快把頁面寫好並且上線。代碼如下:


<input type="text" value="keyword") %>">
<button>搜索button>

<div>
  您搜索的關鍵詞是:<%= getParameter("keyword") %>
div>

然而,在上線後不久,小明就接到了安全組發來的一個神秘鏈接:

http://xxx/search?keyword=">

小明帶著一種不祥的預感點開了這個鏈接[請勿模仿,確認安全的鏈接才能點開]。果然,頁面中彈出了寫著”XSS”的對話框。

可惡,中招了!小明眉頭一皺,發現了其中的奧秘:

當瀏覽器請求 http://xxx/search?keyword="> 時,服務端會解析出請求引數 keyword,得到 ">,拼接到 HTML 中傳回給瀏覽器。形成瞭如下的 HTML:

<input type="text" value=""><script>alert('XSS');script>">
<button>搜索button>

<div>
  您搜索的關鍵詞是:”><script>alert(‘XSS’);script>
div>

瀏覽器無法分辨出  是惡意代碼,因而將其執行。

這裡不僅僅 div 的內容被註入了,而且 input 的 value 屬性也被註入, alert 會彈出兩次。

面對這種情況,我們應該如何進行防範呢?

其實,這隻是瀏覽器把用戶的輸入當成了腳本進行了執行。那麼只要告訴瀏覽器這段內容是文本就可以了。

聰明的小明很快找到解決方法,把這個漏洞修複:

<input type="text" value="keyword")) %>">
<button>搜索button>

<div>
  您搜索的關鍵詞是:<%= escapeHTML(getParameter("keyword")) %>
div>

escapeHTML() 按照如下規則進行轉義:

經過了轉義函式的處理後,最終瀏覽器接收到的響應為:

<input type="text" value="">">
<button>搜索button>

<div>
  您搜索的關鍵詞是:">
div>

惡意代碼都被轉義,不再被瀏覽器執行,而且搜索詞能夠完美的在頁面顯示出來。

通過這個事件,小明學習到瞭如下知識:

  • 通常頁面中包含的用戶輸入內容都在固定的容器或者屬性內,以文本的形式展示。

  • 攻擊者利用這些頁面的用戶輸入片段,拼接特殊格式的字串,突破原有位置的限制,形成了代碼片段。

  • 攻擊者通過在標的網站上註入腳本,使之在用戶的瀏覽器上運行,從而引發潛在風險。

  • 通過 HTML 轉義,可以防止 XSS 攻擊。[事情當然沒有這麼簡單啦!請繼續往下看]


註意特殊的 HTML 屬性、JavaScript API


自從上次事件之後,小明會小心的把插入到頁面中的資料進行轉義。而且他還發現了大部分模板都帶有的轉義配置,讓所有插入到頁面中的資料都預設進行轉義。這樣就不怕不小心漏掉未轉義的變數啦,於是小明的工作又漸漸變得輕鬆起來。

但是,作為導演的我,不可能讓小明這麼簡單、開心地改 Bug 。

不久,小明又收到安全組的神秘鏈接:http://xxx/?redirect_to=javascript:alert('XSS')。小明不敢大意,趕忙點開頁面。然而,頁面並沒有自動彈出萬惡的“XSS”。

小明打開對應頁面的原始碼,發現有以下內容:

<a href="redirect_to")) %>">跳轉...a>

這段代碼,當攻擊 URL 為 http://xxx/?redirect_to=javascript:alert('XSS'),服務端響應就成了:

<a href="javascript:alert('XSS')">跳轉...a>

雖然代碼不會立即執行,但一旦用戶點擊 a 標簽時,瀏覽器會就會彈出alert(‘xss’)

可惡,又失策了…

在這裡,用戶的資料並沒有在位置上突破我們的限制,仍然是正確的 href 屬性。但其內容並不是我們所預期的型別。

原來不僅僅是特殊字符,連 javascript: 這樣的字串如果出現在特定的位置也會引發 XSS 攻擊。

小明眉頭一皺,想到瞭解決辦法:

// 禁止 URL 以 "javascript:" 開頭
xss = getParameter("redirect_to").startsWith('javascript:');
if (!xss) {
  "redirect_to"))%>">
    跳轉...
  

else {
  “/404″>
    跳轉…
  
}

只要 URL 的開頭不是 javascript:,就安全了吧?

安全組隨手又扔了一個連接:http://xxx/?redirect_to=jAvascRipt:alert('XSS')

這也能執行?…..好吧,瀏覽器就是這麼強大。

小明欲哭無淚,在判斷 URL 開頭是否為 javascript: 時,先把用戶輸入轉成了小寫,然後再進行比對。

不過,所謂“道高一尺,魔高一丈”。面對小明的防護策略,安全組就構造了這樣一個連接:

http://xxx/?redirect_to=%20javascript:alert('XSS')

%20javascript:alert('XSS') 經過 URL 解析後變成 javascript:alert('XSS'),這個字串以空格開頭。這樣攻擊者可以繞過後端的關鍵詞規則,又成功的完成了註入。

最終,小明選擇了白名單的方法,徹底解決了這個漏洞:

// 根據專案情況進行過濾,禁止掉 "javascript:" 鏈接、非法 scheme 等
allowSchemes = ["http""https"];

valid = isValid(getParameter("redirect_to"), allowSchemes);

if (valid) {
  
"redirect_to"))%>">
    跳轉...
  

else {
  “/404″>
    跳轉…
  
}

通過這個事件,小明學習到瞭如下知識:


根據背景關係採用不同的轉義規則


某天,小明為了加快網頁的加載速度,把一個資料通過 JSON 的方式行內到 HTML 中:


<script>
var initData = <%= data.toJSON() %>
script
>

插入 JSON 的地方不能使用 escapeHTML(),因為轉義 " 後,JSON 格式會被破壞。

但安全組又發現有漏洞,原來這樣行內 JSON 也是不安全的:

  • 當 JSON 中包含 U+2028 或 U+2029 這兩個字符時,不能作為 JavaScript 的字面量使用,否則會丟擲語法錯誤。

  • 當 JSON 中包含字串

分享創造快樂