Airing

Airing

哲学系学生 / 小学教师 / 程序员,个人网站: ursb.me
github
email
zhihu
medium
tg_channel
twitter_id

XSS 的攻擊手段與防禦

1. XSS 的攻擊手段#

XSS(Cross-Site Scripting,跨域腳本攻擊)攻擊是最常見的 Web 攻擊,是一種代碼注入攻擊。攻擊者通過在目標網站上注入惡意腳本,使之在用戶的瀏覽器上運行。利用這些惡意腳本,攻擊者可獲取用戶的敏感信息如 Cookie、SessionID 等,進而危害數據安全。其重點是『跨域』和『客戶端執行』。

XSS 的本質:

  • 惡意代碼未經過濾,與網站正常的代碼混在一起;
  • 瀏覽器無法分辨哪些腳本是可信的,導致惡意腳本被執行。

XSS 攻擊一般存在以下幾類:

  • Reflected XSS(反射型 XSS 攻擊)
  • Stored XSS(存儲型 XSS 攻擊)
  • DOM XSS
  • JSONP XSS
類型存儲區插入點
Reflected XSSURLHTML
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.outerHTMLdocument.write() 時要特別小心,不要把不可信的數據作為 HTML 插到頁面上,而應盡量使用 .textContent.setAttribute() 等。

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

1.4 JSONP XSS#

JSONP 的 callback 參數非常危險,他有兩種風險可能導致 XSS:

  1. callback 參數意外截斷 js 代碼,特殊字符單引號雙引號,換行符均存在風險。
  2. 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 攻擊有兩大要素:

  1. 攻擊者提交惡意代碼。
  2. 瀏覽器執行惡意代碼。

根本的解決方法:從輸入到輸出都需要過濾、轉義。

輸入#

輸入指客戶端請求參數,具體包括:

  • 用戶輸入
  • URL 參數
  • POST 參數

針對 HTML 代碼的編碼方式是 HTMLEncode,它的作用是將字符串轉換成 HTMLEntities。目前來說,為了對抗 XSS,需要對以下六個字符進行實體化轉義。

特殊符號實體編碼
&&amp;
<&lt;
>&gt;
"&quot;
'&#x27;
/&#x2F;

當然,上面的只是最基本而且是最必要的,HTMLEncode 還有很多很多,具體可以參考:Web 安全系列(四):XSS 的防禦 | 掘金 一文中提及的特殊字符。

除此之外,富文本的輸入需要額外注意:

  1. 首先例行進行輸入檢查,保證用戶輸入的是完整的 HTML 代碼,而不是有拼接的代碼
  2. 通過 htmlParser 解析出 HTML 代碼的標籤、屬性、事件
  3. 富文本的事件肯定要被禁止,因為富文本並不需要事件這種東西,另外一些危險的標籤也需要禁止,例如: <iframe><script><base><form>
  4. 利用白名單機制,只允許安全的標籤嵌入,例如:<a><img>div等,白名單不僅僅適用於標籤,也適用於屬性
  5. 過濾用戶 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 的防禦方式#

  1. ** 嚴格定義 **Content-Type: application / json。瀏覽器渲染就是靠 Content-Type 來做的。如果返回內容標記是 json,哪怕 body 裡面都是 html 的標籤,瀏覽器也不會渲染。所以,如果接口返回的不是 html,千萬不要寫成 html。所以 Content-Type 不要亂用,嚴格按照標準協議來做。目前的框架默認肯定會檢測一下內容類型,如果不是很必要,不要手動設置。因為有可能多轉發幾次 Content-Type 就被改了。
  2. callback 做長度限制,這個比較 low,一般對函數名限制在 50 個字符內。
  3. 檢測 callback 裡面的字符。一般 callback 裡面都是字母和數字,別的符號都不能有。函數名只允許 [, ], a-zA-Z0123456789_, $, .,防止一般的 XSS,utf-7 XSS 等攻擊。
  4. 過濾 callback 以及 JSON 數據輸出,原理同輸出轉義。
  5. 其他一些比較 “猥瑣” 的方法:如在 Callback 輸出之前加入其他字符 (如:/**/、回車換行) 這樣不影響 JSON 文件加載,又能一定程度預防其他文件格式的輸出。還比如 Gmail 早期使用 AJAX 的方式獲取 JSON ,聽過在輸出 JSON 之前加入 while(1) ;這樣的代碼來防止 JS 遠程調用。

Web 安全頭支持#

這是瀏覽器自帶的防範能力,一般是通過開啟 Web 安全頭生效的。具體有以下幾個:

  1. CSP:W3C 的 Content Security Policy,簡稱 CSP,主要是用來定義頁面可以加載哪些資源,減少 XSS 的發生。要配置 CSP , 需要對 CSP 的 policy 策略有了解,具體細節可以參考 CSP 是什麼
  2. X-Download-Options: noopen:默認開啟,禁用 IE 下下載框 Open 按鈕,防止 IE 下下載文件默認被打開 XSS。
  3. X-Content-Type-Options: nosniff:禁用 IE8 自動嗅探 mime 功能例如 text/plain 卻當成 text/html 渲染,特別當本站點 server 的內容未必可信的時候。
  4. X-XSS-Protection:IE 提供的一些 XSS 檢測與防範,默認開啟

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 漏洞。

參考資料#

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。