XSS 攻擊的分類

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

問題是:在提交階段,我們並不確定內容要輸出到哪裡。

這裡的“並不確定內容要輸出到哪裡”有兩層含義:

  1. 用戶的輸入內容可能同時提供給前端和客戶端,而一旦經過了 escapeHTML(),客戶端顯示的內容就變成了亂碼( 5 < 7 )。
  2. 在前端中,不同的位置所需的編碼也不同。
  • 當 5 < 7 作為 HTML 拼接頁面時,可以正常顯示:

5 < 7

1 1
  • 當 5 < 7 通過 Ajax 返回,然後賦值給 JavaScript 的變量時,前端得到的字符串就是轉義後的字符。這個內容不能直接用於 Vue 等模板的展示,也不能直接用於內容長度計算。不能用於標題、alert 等。

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

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

既然輸入過濾並非完全可靠,我們就要通過“防止瀏覽器執行惡意代碼”來防範 XSS。這部分分為兩類:

  • 防止 HTML 中出現注入。
  • 防止 JavaScript 執行時,執行惡意代碼。

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

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

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

  • 改成純前端渲染,把代碼和數據分隔開。
  • 對 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 的官方說明。

 

link

可見,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,很容易產生安全隱患,請務必避免。

 

 
 
1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

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

其他 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//\x3csVg/\x3e
1

它能夠檢測到存在於 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
1

除了手動檢測之外,還可以使用自動掃描工具尋找 XSS 漏洞,例如 Arachni、Mozilla HTTP Observatory、w3af 等。

XSS 攻擊的總結

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

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

不正確。因為:

  • 防範存儲型和反射型 XSS 是後端 RD 的責任。而 DOM 型 XSS 攻擊不發生在後端,是前端 RD 的責任。防範 XSS 是需要後端 RD 和前端 RD 共同參與的系統工程。
  • 轉義應該在輸出 HTML 時進行,而不是在提交用戶輸入時。
  1. 所有要插入到頁面上的數據,都要通過一個敏感字符過濾函數的轉義,過濾掉通用的敏感字符後,就可以插入到頁面中。

不正確。

不同的上下文,如 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 響應中:

 "+"...

瀏覽器接收到響應後就會執行 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 響應中:

  • ">按分類檢索
  • 瀏覽器接收到響應後就會加載執行惡意腳本 //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 可以這樣定義模板,而無需手動實施轉義規則:

     
     
     {{.title}}
     
     
     {{.content}}
     
    
    
    

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

     
     
     {{.title | htmlescaper}}
     
     

    --------------------- 本文來自 meituantech 的CSDN 博客 ,全文地址請點擊:
    https://blog.csdn.net/MeituanTech/article/details/82877451?utm_source=copy

    收藏

    舉報

    深夜食堂城堡


    分享到:


    相關文章: