1. XSS 的攻擊手段#
XSS(Cross-Site Scripting,跨域腳本攻擊)攻擊是最常見的 Web 攻擊,是一種代碼注入攻擊。攻擊者通過在目標網站上注入惡意腳本,使之在用戶的瀏覽器上運行。利用這些惡意腳本,攻擊者可獲取用戶的敏感信息如 Cookie、SessionID 等,進而危害數據安全。其重點是『跨域』和『客戶端執行』。
XSS 的本質:
- 惡意代碼未經過濾,與網站正常的代碼混在一起;
- 瀏覽器無法分辨哪些腳本是可信的,導致惡意腳本被執行。
XSS 攻擊一般存在以下幾類:
- Reflected XSS(反射型 XSS 攻擊)
- Stored XSS(存儲型 XSS 攻擊)
- DOM XSS
- JSONP XSS
類型 | 存儲區 | 插入點 |
---|---|---|
Reflected XSS | URL | HTML |
Stored XSS | 後端數據庫 | HTML |
DOM XSS | 後端數據庫 / 前端存儲 / URL | 前端 JavaScript |
JSONP XSS | 後端數據庫 / 前端存儲 / URL | 前端 JavaScript |
1.1 Reflected XSS#
反射型的 XSS 攻擊,主要是由於服務端接收到客戶端的不安全輸入,在客戶端觸發執行從而發起 Web 攻擊。
具體而言,反射型 XSS 只是簡單地把用戶輸入的數據 “反射” 給瀏覽器,這種攻擊方式往往需要攻擊者誘使用戶點擊一個惡意鏈接,或者提交一個表單,或者進入一個惡意網站時,注入腳本進入被攻擊者的網站。這是一種非持久型的攻擊。
比如:在某購物網站搜索物品,搜索結果會顯示搜索的關鍵詞。搜索關鍵詞填入<script>alert('handsome boy')</script>
,點擊搜索。頁面沒有對關鍵詞進行過濾,這段代碼就會直接在頁面上執行,彈出 alert。
1.2 Stored XSS#
基於存儲的 XSS 攻擊,是通過提交帶有惡意腳本的內容存儲在服務器上,當其他人看到這些內容時發起 Web 攻擊。一般提交的內容都是通過一些富文本編輯器編輯的,很容易插入危險代碼。
比較常見的一個場景是攻擊者在社區或論壇上寫下一篇包含惡意 JavaScript 代碼的文章或評論,文章或評論發表後,所有訪問該文章或評論的用戶,都会在他們的瀏覽器中執行這段惡意的 JavaScript 代碼。這是一種持久型的攻擊。
1.3 DOM XSS#
基於 DOM 的 XSS 攻擊是指通過惡意腳本修改頁面的 DOM 結構,是純粹發生在客戶端的攻擊。
DOM 型 XSS 跟前兩種 XSS 的區別:DOM 型 XSS 攻擊中,取出和執行惡意代碼由瀏覽器端完成,屬於前端 JavaScript 自身的安全漏洞,而其他兩種 XSS 都屬於服務端的安全漏洞。舉個栗子:
<input type="text" id="input">
<button id="btn">Submit</button>
<div id="div"></div>
<script>
const input = document.getElementById('input');
const btn = document.getElementById('btn');
const div = document.getElementById('div');
let val;
input.addEventListener('change', (e) => {
val = e.target.value;
}, false);
btn.addEventListener('click', () => {
div.innerHTML = `<a href=${val}>testLink</a>`
}, false);
</script>
點擊 Submit 按鈕後,會在當前頁面插入一個鏈接,其地址為用戶的輸入內容。如果用戶在輸入時構造了如下內容:
" onclick=alert(/xss/)
用戶提交之後,頁面代碼就變成了:
<a href onlick="alert(/xss/)">testLink</a>
此時,用戶點擊生成的鏈接,就會執行對應的腳本。**DOM 型 XSS 攻擊,實際上就是網站前端 JavaScript 代碼本身不夠嚴謹,把不可信的數據當作代碼執行了。** 在使用 .innerHTML
、.outerHTML
、document.write()
時要特別小心,不要把不可信的數據作為 HTML 插到頁面上,而應盡量使用 .textContent
、.setAttribute()
等。
DOM 中的內聯事件監聽器,如 location
、onclick
、onerror
、onload
、onmouseover
等,<a>
標籤的 href
屬性,JavaScript 的 eval()
、setTimeout()
、setInterval()
等,都能把字符串作為代碼運行。如果不可信的數據拼接到字符串中傳遞給這些 API,很容易產生安全隱患,請務必避免。
1.4 JSONP XSS#
JSONP 的 callback 參數非常危險,他有兩種風險可能導致 XSS:
- callback 參數意外截斷 js 代碼,特殊字符單引號雙引號,換行符均存在風險。
- callback 參數惡意添加標籤,造成 XSS 漏洞。
瀏覽器為了保證跨域訪問的安全性,會默認發一個 callback 參數到後台,接口拿到這個參數之後,需要將返回的 JSON 數據外面包上 callback 參數。
具體的返回格式:
CALLBACK(JSON)
如果 ajax 請求是 JSONP 請求,返回的內容瀏覽器還會自動檢測,如果不是按這個格式返回或者 callback 的內容不對,這次請求就算失敗了。
這裡有一個機制,那就是請求的 callback 會被放入返回的內容當中,這也是可能出問題的地方。舉個栗子,如果返回的頁面,那麼 Content-Type: text/html
,那麼 callback 注入的 html 元素都可以直接放到頁面上了。那么,html 頁面必然不能支持 callback。支持 JSONP 的鏈接如果直接放到瀏覽器裡面訪問,瀏覽器就不會做 callback 校驗了。
2. XSS 的防禦方式#
2.1 防禦 XSS 的根本之道#
通過前面的介紹可以得知,XSS 攻擊有兩大要素:
- 攻擊者提交惡意代碼。
- 瀏覽器執行惡意代碼。
根本的解決方法:從輸入到輸出都需要過濾、轉義。
輸入#
輸入指客戶端請求參數,具體包括:
- 用戶輸入
- URL 參數
- POST 參數
針對 HTML 代碼的編碼方式是 HTMLEncode,它的作用是將字符串轉換成 HTMLEntities。目前來說,為了對抗 XSS,需要對以下六個字符進行實體化轉義。
特殊符號 | 實體編碼 |
---|---|
& | & |
< | < |
> | > |
" | " |
' | ' |
/ | / |
當然,上面的只是最基本而且是最必要的,HTMLEncode 還有很多很多,具體可以參考:Web 安全系列(四):XSS 的防禦 | 掘金 一文中提及的特殊字符。
除此之外,富文本的輸入需要額外注意:
- 首先例行進行輸入檢查,保證用戶輸入的是完整的 HTML 代碼,而不是有拼接的代碼
- 通過
htmlParser
解析出 HTML 代碼的標籤、屬性、事件 - 富文本的事件肯定要被禁止,因為富文本並不需要事件這種東西,另外一些危險的標籤也需要禁止,例如:
<iframe>
,<script>
,<base>
,<form>
等 - 利用白名單機制,只允許安全的標籤嵌入,例如:
<a>
,<img>
,div
等,白名單不僅僅適用於標籤,也適用於屬性 - 過濾用戶 CSS,檢查是否有危險代碼
輸出#
不要以為在輸入的時候進行過濾就萬事大吉了,惡意攻擊者們可能會層層繞過防禦機制進行 XSS 攻擊,一般來說,所有需要輸出到 HTML 頁面的變量,全部需要使用編碼或者轉義來防禦。輸出需要轉義的部分,具體包括:
- 在 HTML 中輸出
- 在 JavaScript 中輸出
- 在 CSS 中輸出
- 在 URL 中輸出
在 HTML 中的輸出#
HTML 的部分和輸入的轉義方式相同,使用 HTMLEncode,此處不再復述。
在 JavaScript 中的輸出#
JavaScript 的部分同樣需要編碼轉義,比如在 JSONP 中可以通過意外截斷 JSON 數據或者在頁面中玩轉引號來造成 XSS 攻擊。
let a = "我是變量"
// 我是變量 = ";alert(1);//
a = "";alert(1);//"
攻擊者只需要閉合標籤就能實行攻擊,目前的防禦方法就是 JavaScriptEncode。JavaScriptEncode 與 HTMLEncode 的編碼方式不同,它需要用 \
對特殊字符進行轉義。
在 CSS 中的輸出#
在 CSS 中或者 style 標籤中的攻擊花樣特別多,具體可以參考:Web 安全系列(四):XSS 的防禦 | 掘金。此處由於篇幅問題,僅僅談及一下解決方案。
要解決 CSS 的攻擊問題,一方面要嚴格控制用戶將變量輸入 style 標籤內,另一方面不要引用未知的 CSS 文件,如果一定有用戶改變 CSS 變量這種需求的話,可以使用 OWASP ESAPI
中的 encodeForCSS()
函數。
在 URLEncode 中的輸出#
在 URL 中的輸出直接使用 URLEncode 即可,需要轉義變量的部分。
2.2 其他的 XSS 防禦方式#
JSONP XSS 的防禦方式#
- ** 嚴格定義 **
Content-Type: application / json
。瀏覽器渲染就是靠 Content-Type 來做的。如果返回內容標記是 json,哪怕 body 裡面都是 html 的標籤,瀏覽器也不會渲染。所以,如果接口返回的不是 html,千萬不要寫成 html。所以 Content-Type 不要亂用,嚴格按照標準協議來做。目前的框架默認肯定會檢測一下內容類型,如果不是很必要,不要手動設置。因為有可能多轉發幾次 Content-Type 就被改了。 - callback 做長度限制,這個比較 low,一般對函數名限制在 50 個字符內。
- 檢測 callback 裡面的字符。一般 callback 裡面都是字母和數字,別的符號都不能有。函數名只允許
[
,]
,a-zA-Z0123456789_
,$
,.
,防止一般的 XSS,utf-7 XSS 等攻擊。 - 過濾 callback 以及 JSON 數據輸出,原理同輸出轉義。
- 其他一些比較 “猥瑣” 的方法:如在 Callback 輸出之前加入其他字符 (如:
/**/
、回車換行) 這樣不影響 JSON 文件加載,又能一定程度預防其他文件格式的輸出。還比如 Gmail 早期使用 AJAX 的方式獲取 JSON ,聽過在輸出 JSON 之前加入while(1) ;
這樣的代碼來防止 JS 遠程調用。
Web 安全頭支持#
這是瀏覽器自帶的防範能力,一般是通過開啟 Web 安全頭生效的。具體有以下幾個:
- CSP:W3C 的 Content Security Policy,簡稱 CSP,主要是用來定義頁面可以加載哪些資源,減少 XSS 的發生。要配置 CSP , 需要對 CSP 的 policy 策略有了解,具體細節可以參考 CSP 是什麼。
- X-Download-Options: noopen:默認開啟,禁用 IE 下下載框 Open 按鈕,防止 IE 下下載文件默認被打開 XSS。
- X-Content-Type-Options: nosniff:禁用 IE8 自動嗅探 mime 功能例如
text/plain
卻當成text/html
渲染,特別當本站點 server 的內容未必可信的時候。 - X-XSS-Protection:IE 提供的一些 XSS 檢測與防範,默認開啟
HTTP-only Cookie#
HttpOnly 最早由微軟提出,至今已經成為一個標準。瀏覽器將禁止頁面的 Javascript 訪問帶有 HttpOnly 屬性的 Cookie。
攻擊者可以通過注入惡意腳本獲取用戶的 Cookie 信息。通常 Cookie 中都包含了用戶的登錄憑證信息,攻擊者在獲取到 Cookie 之後,則可以發起 Cookie 劫持攻擊。所以,嚴格來說,HttpOnly 並非阻止 XSS 攻擊,而是能阻止 XSS 攻擊後的 Cookie 劫持攻擊。
// 利用 express 設置 cookie 並開啟 httpOnly
res.cookie('myCookie', 'test', {
httpOnly: true
})
添加驗證碼機制#
防止腳本冒充用戶提交危險操作。
3. XSS 的經驗總結#
整體的 XSS 防範是非常複雜和繁瑣的,我們不僅需要在全部需要轉義的位置,對數據進行對應的轉義。而且要防止多餘和錯誤的轉義,避免正常的用戶輸入出現亂碼。雖然很難通過技術手段完全避免 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 的方法比較危險,如果框架允許,使用 createElement、setAttribute 之類的方法實現。或者採用比較成熟的渲染框架,如 Vue/React 等。 -
時刻保持警惕
在插入位置為 DOM 屬性、鏈接等位置時,要打起精神,嚴加防範。 -
增加攻擊難度,降低攻擊後果
通過 CSP、輸入長度配置、接口安全措施等方法,增加攻擊的難度,降低攻擊的後果。 -
主動檢測和發現
可使用 XSS 攻擊字符串和自動掃描工具尋找潛在的 XSS 漏洞。