攻擊者可以利用XSS漏洞向用戶發送攻擊腳本,而用戶的浏覽器因為沒有辦法知道這段腳本是不可信的,所以依然會執行它。對於浏覽器而言,它認為這段腳本是來自可以信任的服務器的,所以腳本可以光明正大地訪問Cookie,或者保存在浏覽器裡被當前網站所用的敏感信息,甚至可以知道用戶電腦安裝了哪些軟件。這些腳本還可以改寫HTML頁面,進行釣魚攻擊。
雖然產生XSS漏洞的原因各種各樣,對於漏洞的利用也是花樣百出,但是如果我們遵循本文提到防御原則,我們依然可以做到防止XSS攻擊的發生。
有人可能會問,防御XSS的核心不就是在輸出不可信數據的時候進行編碼,而現如今流行的Web框架(比如Rails)大多都在默認情況下就對不可信數據進行了HTML編碼,幫我們做了防御,還用得著我們自己再花時間研究如何防御XSS嗎?答案是肯定的,對於將要放置到HTML頁面body裡的不可信數據,進行HTML編碼已經足夠防御XSS攻擊了,甚至將HTML編碼後的數據放到HTML標簽(TAG)的屬性(attribute)裡也不會產生XSS漏洞(但前提是這些屬性都正確使用了引號),但是,如果你將HTML編碼後的數據放到了<SCRIPT>標簽裡的任何地方,甚至是HTML標簽的事件處理屬性裡(如onmouseover),又或者是放到了CSS、URL裡,XSS攻擊依然會發生,在這種情況下,HTML編碼不起作用了。所以就算你到處使用了HTML編碼,XSS漏洞依然可能存在。下面這幾條規則就將告訴你,如何在正確的地方使用正確的編碼來消除XSS漏洞。
原則1:不要在頁面中插入任何不可信數據,除非這些數已經據根據下面幾個原則進行了編碼
第一條原則其實是“Secure By Default”原則:不要往HTML頁面中插入任何不可信數據,除非這些數據已經根據下面幾條原則進行了編碼。
之所以有這樣一條原則存在,是因為HTML裡有太多的地方容易形成XSS漏洞,而且形成漏洞的原因又有差別,比如有些漏洞發生在HTML標簽裡,有些發生在HTML標簽的屬性裡,還有的發生在頁面的<Script>裡,甚至有些還出現在CSS裡,再加上不同的浏覽器對頁面的解析或多或少有些不同,使得有些漏洞只在特定浏覽器裡才會產生。如果想要通過XSS過濾器(XSS Filter)對不可信數據進行轉義或替換,那麼XSS過濾器的過濾規則將會變得異常復雜,難以維護而且會有被繞過的風險。
所以實在想不出有什麼理由要直接往HTML頁面裡插入不可信數據,就算是有XSS過濾器幫你做過濾,產生XSS漏洞的風險還是很高。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <script>…不要在這裡直接插入不可信數據…</script> 直接插入到SCRIPT標簽裡 <!– …不要在這裡直接插入不可信數據… –> 插入到HTML注釋裡 <div 不要在這裡直接插入不可信數據=”…”></div> 插入到HTML標簽的屬性名裡 <div name=”…不要在這裡直接插入不可信數據…”></div> 插入到HTML標簽的屬性值裡 <不要在這裡直接插入不可信數據 href=”…”></a> 作為HTML標簽的名字 <style>…不要在這裡直接插入不可信數據…</style> 直接插入到CSS裡最重要的是,千萬不要引入任何不可信的第三方JavaScript到頁面裡,一旦引入了,這些腳本就能夠操縱你的HTML頁面,竊取敏感信息或者發起釣魚攻擊等等。
原則2:在將不可信數據插入到HTML標簽之間時,對這些數據進行HTML Entity編碼
在這裡相當強調是往HTML標簽之間插入不可信數據,以區別於往HTML標簽屬性部分插入不可信數據,因為這兩者需要進行不同類型的編碼。當你確實需要往HTML標簽之間插入不可信數據的時候,首先要做的就是對不可信數據進行HTML Entity編碼。比如,我們經常需要往DIV,P,TD這些標簽裡放入一些用戶提交的數據,這些數據是不可信的,需要對它們進行HTML Entity編碼。很多Web框架都提供了HTML Entity編碼的函數,我們只需要調用這些函數就好,而有些Web框架似乎更“智能”,比如Rails,它能在默認情況下對所有插入到HTML頁面的數據進行HTML Entity編碼,盡管不能完全防御XSS,但著實減輕了開發人員的負擔。
1 2 3 4 5 6 7 <body>…插入不可信數據前,對其進行HTML Entity編碼…</body> <div>…插入不可信數據前,對其進行HTML Entity編碼…</div> <p>…插入不可信數據前,對其進行HTML Entity編碼…</p> 以此類推,往其他HTML標簽之間插入不可信數據前,對其進行HTML Entity編碼[編碼規則]
那麼HTML Entity編碼具體應該做哪些事情呢?它需要對下面這6個特殊字符進行編碼:
& –> &
< –> <
> –> >
” –> "
‘ –> '
/ –> /
有兩點需要特別說明的是:
推薦使用OWASP提供的ESAPI函數庫,它提供了一系列非常嚴格的用於進行各種安全編碼的函數。在當前這個例子裡,你可以使用:
1 String encodedContent = ESAPI.encoder().encodeForHTML(request.getParameter(“input”));原則3:在將不可信數據插入到HTML屬性裡時,對這些數據進行HTML屬性編碼
這條原則是指,當你要往HTML屬性(例如width、name、value屬性)的值部分(data value)插入不可信數據的時候,應該對數據進行HTML屬性編碼。不過需要注意的是,當要往HTML標簽的事件處理屬性(例如onmouseover)裡插入數據的時候,本條原則不適用,應該用下面介紹的原則4對其進行JavaScript編碼。
1 2 3 4 5 6 7 8 9 10 11 <div attr=…插入不可信數據前,進行HTML屬性編碼…></div> 屬性值部分沒有使用引號,不推薦 <div attr=’…插入不可信數據前,進行HTML屬性編碼…’></div> 屬性值部分使用了單引號 <div attr=”…插入不可信數據前,進行HTML屬性編碼…”></div> 屬性值部分使用了雙引號[編碼規則]
除了阿拉伯數字和字母,對其他所有的字符進行編碼,只要該字符的ASCII碼小於256。編碼後輸出的格式為 &#xHH; (以&#x開頭,HH則是指該字符對應的十六進制數字,分號作為結束符)
之所以編碼規則如此嚴格,是因為開發者有時會忘記給屬性的值部分加上引號。如果屬性值部分沒有使用引號的話,攻擊者很容易就能閉合掉當前屬性,隨後即可插入攻擊腳本。例如,如果屬性沒有使用引號,又沒有對數據進行嚴格編碼,那麼一個空格符就可以閉合掉當前屬性。請看下面這個攻擊:
假設HTML代碼是這樣的:
<div width=$INPUT> …content… </div>
攻擊者可以構造這樣的輸入:
x onmouseover=”javascript:alert(/xss/)”
最後,在用戶的浏覽器裡的