1、HttpOnly
嚴格的說,httponly並非為了對抗XSS,它解決的是XSS后的Cookie劫持攻擊。Cookie設置了httponly之后,JavaScript讀不到該cookie的值。
一個cookie的使用過程如下:
step1:瀏覽器向服務器發起請求,這時候沒有cookie
step2:服務器返回時發送set-cookie頭,向客戶端瀏覽器寫入cookie
step3:在該cookie到期前,瀏覽器訪問該域下的所有頁面,都將發送該cookie
HttpOnly是在set-cookie時標記的:
Set-Cookie: <name>=<value>[; <Max-Age>=<age>] [; expires=<date>][; domain=<domain_name>] [; path=<some_path>][; secure][; HttpOnly]
HttpOnly可以有選擇性地加在任何一個cookie值上:在某些時候,應用可能需要JavaScript訪問某幾項cookie,這種cookie可以不設置HttpOnly標記,而僅把HttpOnly標記給用於認證的關鍵cookie
舉個例子:
<?php header("Set-Cookie: cookie1=test1;"); header("Set-Cookie: cookie2=test2;httponly",false); ?> <script> alert(document.cookie); </script>
在這段代碼中,cookie1沒有HttpOnly,cookie2被標記為HttpOnly,兩個cookie均被寫入瀏覽
但是只有cookie1被JavaScript讀取到:
這就是HttpOnly的作用。
2、輸入檢查
輸入檢查的邏輯,必須放在服務器端代碼中實現。如果只是在客戶端使用JavaScript進行輸入檢查,是很容易被攻擊者繞過的。目前Web開發的普遍做法,是同時在客戶端JavaScript中和服務器端代碼中實現相同的輸入檢查。客戶端JavaScript的輸入檢查,可以阻擋大部分誤操作的用戶,從而節約服務器資源。
3、輸出檢查
一般來說,除了富文本的輸出外,在變量輸出到HTML頁面時,可以使用編碼或轉義的方式來防御XSS攻擊。
針對HTML代碼的編碼方式是
HtmlEncode
HtmlEncode並非專有名詞,它只是一種函數實現。它的作用是將字符轉化成HTMLEntities。
為了對抗XSS,在HtmlEncode中要求至少轉換一下字符:
& --> &
< --> <
> --> >
" --> "
' --> ' $apos;不推薦
/ --> / 包含斜杠是因為它可能會閉合一些HTML entity
在PHP中,有htmlentities()和htmlspecialchars()兩個函數可以滿足安全要求。
htmlspecialchars — 將特殊字符轉換為 HTML 實體
htmlentities — 將字符轉換為 HTML 轉義字符,本函數各方面都和
htmlspecialchars() 一樣, 除了 htmlentities() 會轉換所有具有 HTML 實體的字符。
相應地,JavaScript的編碼方式可以使用
JavaScriptEncode
JavaScriptEncode與HtmlEncode的編碼方式不同,它需要使用反斜杠"\"對特殊字符進行轉義。
在對抗XSS時,還要求輸出的變量必須在引號內部,以避免造成安全問題。比較下面兩種寫法:
var x = escapeJavascript($evil); var y = '"'+escapeJavascript($evil)+'"';
如果escapeJavascript()函數只轉義了幾個危險字符,比如 ' " < > \ & # 等,那么上面的兩行代碼輸出后可能會變成:
var x = 1;alert(2); var y = "1;alert(2)";
第一行執行了額外的代碼了;第二行則是安全的。對於后者,攻擊者即使想要逃逸出引號的范圍,也會遇到困難:
var y = "\";alert(1);\/\/";
所以要求
JavaScript的變量輸出一定要在引號內。
可是很多開發者沒有這個習慣怎么辦?這就只能使用一個更加嚴格的JavaScriptEncode函數來保證安全——
除了數字、字母外的所有字符,都使用十六進制“\xHH”的方式進行編碼。
在本例中:
var x = 1;alert(2);
變成了:
var x = 1\x3balert\x282\x29;
如此代碼可以保證是安全的。
此外,XSS是很復雜的問題,需要"
在正確的地方使用正確的編碼方式"。 舉個例子:
<body> <a href=# onclick="alert('$var');" >test</a> </body>
開發者希望看到的結果是,用戶點擊鏈接后,彈出變量"$var"的內容。可是如果用戶輸入:
$var = htmlencode("');alert('2");
對變量"$var"進行htmlencode后,渲染的結果是:
<body> <a href=# onclick="alert('');alert('2');">test</a> </body>
對於瀏覽器來說,htmlparser會優先於JavaScript Parser執行,所以解析過程是,被HtmlEncode的字符先被解碼,然后執行JavaScript事件。
因此,經過htmlparser解析后相當於:
<body> <a href=# onclick="alert('');alert('2');">test</a> </body>
成功在onclick事件中注入了XSS代碼。
第一次彈框
第二次彈框
導致XSS攻擊發送的原因,是由於沒有分清楚輸出變量的語境。
4、正確地防御XSS
XSS的本質還是一種"HTML注入",用戶的數據被當成了HTML代碼一部分來執行,從而混淆了原本的語義,產生了新的語義。
下面將用變量"$var"表示用戶數據,它將被填充入HTML代碼中。可能存在以下場景。
(1)、在HTML標簽中輸出
<div>$var</div> <a href=# >$var</a>
所有在標簽中輸出的變量,如果未做任何處理,都能導致直接產生XSS。
在這種場景下,XSS的利用方式一般是構造一個<script>標簽,或者是任何能夠產生腳本執行的方式。比如:
<div><script>alert(/xss/)</script></div> 或者 <a href=# ><img src=# onerror=alert(1) /></a>
防御方法是對變量使用
HtmlEncode
(2)、在HTML屬性中輸出
<div id="abc" name="$var" ></div>
與在HTML標簽中輸出類似,可能的攻擊方式是:
<div id="abc" name=""><script>alert(/xss/)</script><""></div>
防御方法也是采用
HtmlEncode
在OWASP ESAPI中推薦了一種更嚴格的HtmlEncode——除了字母、數字外,其他所有的特殊字符都被編碼成HTMLEntities
String safe = ESAPI.encoder().encodeForHTMLAttribute(request.getParameter("input"));
這種嚴格的編碼方式,可以保證不會出現任何安全問題。
(3)、在<script>標簽中輸出
在<script>標簽中輸出時,首先應該確保輸出的變量在引號中:
<script> var x = "$var"; </script>
攻擊者需要先閉合引號才能實施XSS攻擊:
<script> var x= "";alert(/xss/);//"; </script>
防御時使用
JavascriptEncode
(4)、在事件中輸出
在事件中輸出和在<script>標簽中輸出類似:
<a href=# onclick="funcA('$var')" >test</a>
可能的攻擊方法:
<a href=# onclick="funcA('');alert(/xss/);//')" >test</a>
在防御時需要使用
JavascriptEncode
(5)、在CSS中輸出
在CSS和style、style attribute中形成XSS的方式非常多元化,參考下面幾個XSS的例子:
<STYLE>@import'http://ha.ckers.org/xss.css';</STYLE>
<STYLE>BODY{-moz-binding:url("http://ha.ckers.org/xssmoz.xml#xss")}</STYLE>
<XSS STYLE="behavior: url(xss.htc);">
<STYLE>li {list-style-image: url("javascript:alert('xss')");}>/STYLE><UL><LI>XSS
<DIV STYLE="background-image: url(javascript:alert('XSS'))">
<DIV STYLE="width: expression(alert('XSS'));">
所以,一般來說,盡可能禁止用戶可控制的變量在"<style>標簽"、“HTML標簽的style屬性”以及“CSS文件”中輸出。如果一定有這樣的需要,則推薦使用OWASP ESAPI中的
encodeForCSS()函數
String safe = ESAPI.encoder().encodeForCSS(request.getParameter("input"));
其實現原理類似於ESAPI.encoder().encoderForJavaScript()函數,除了字母、數字外的所有字符都被編碼成十六進制形式"\uHH"
(6)、在地址中輸出
一般來說,在URL的path(路徑)或者search(參數)中輸出,使用
URLEncode即可。
但是還有一種情況,就是整個URL能夠被用戶完全控制。這時URL的protocal和Host部分是不能夠使用URLEncode的,否則會改變URL的語義。
一個URL的組成如下:
[protocal][host][path][search][hash]
例如:
https://www.evil.com/a/b/c/test?abc=123#ssss [protocal] = "https://" [host] = "www.evil.com" [path] = "/a/b/c/test" [search] = "?abc=123" [hash] = "#ssss"
在protocal與host中,如果使用嚴格的URLEncode函數,則會把"://"、"."等都編碼掉。
對於如下的輸出方式:
<a href="$var" >test</a>
攻擊者可能會構造偽協議實施攻擊:
<a href="javascript:alert(1);" >test</a>
除了"
javascript"作為偽協議可以執行代碼外,還有"
vbscript"、“
dataURI”等偽協議可能導致腳本執行。
“dataURI”這個偽協議是Mozilla所支持的,能夠將一段代碼寫在RUL里。如下例:
<a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTs8L3NjcmlwdD4=">test</a>
這段代碼的意思是,以test/html的格式加載編碼為base64的數據,加載完成后實際上的:
<script>alert(1)</script>
點擊<a>標簽的鏈接,將導致執行腳本。
由此可見,如果用戶能夠完全控制URL,則可以執行腳本的方式有很多。如果解決這種情況呢?
一般來說,如果變量是整個URL,則應該先檢查變量是否以"http"開頭(如果不是則自動添加),以保證不會出現
偽協議類的XSS攻擊。
<a href="$var" >test</a>
在此之后,再對變量進行URLEncode,即可保證不會再有此類的XSS發生了。
5、處理富文本
有些時候,網站需要允許用戶提交一些自定義的HTML代碼,稱之為“富文本”。
過濾富文本時,“事件”應該被嚴格禁止,因為“富文本”的展示要求里不應該包含“事件”這種動態效果。而一些危險的標簽,比如<iframe>、<script>、<base>、<form>等,也是應該嚴格禁止的。
在標簽的選擇上,應該使用白名單,避免使用黑名單。
6、防御DOM Based XSS
DOM Based XSS是從JavaScript中輸出數據到HTML頁面里。而前文提到的方法都是針對“從服務器應用直接輸出到HTML頁面”的XSS漏洞,因此並不適用於DOM Based XSS。
例如:
<script> var x="$var"; document.write("<a href='"+x+"'>test</a>"); </script>
變量"$var"輸出在<script>標簽里,可是最后又被輸出到HTML頁面中。
假設為了保護"$var"直接在<script>標簽中產生XSS,服務器端對其進行了javascriptEscape。可是,$var在document.write時,仍然能夠產生XSS,如下所示:
<script> var x="\x20\x27onclick\x3dalert\x281\x29\x3b\x2f\x2f\x27"; document.write("<a href='"+x+"'>test</a>"); </script>
頁面渲染效果之后的實際結果如下:
XSS攻擊成功:
其原因在於,第一次執行javascriptEscape之后,只保護了
var x="$var";
但是當document.write輸出數據到HTML頁面時,瀏覽器重新渲染了頁面。在<script>標簽執行時,已經對變量x進行了解密,其后document.write再運行時,其參數就變成了:
<a href='_' onclick="alert(1);//'' ">test</a>
xss因此而產生。
那是不是因為對"$var"用錯了編碼函數呢?如果改成HtmlEncode會怎么樣?繼續看下面的這個例子:
<script> var x="1");alert(2);//""; document.write("<a href=# onclick='alert(\""+x+"\")'>test</a>"); </script>
服務器把變量HtmlEncode后再輸出到<script>中,然后變量x作為onclick事件的一個函數參數被document.write到了HTML頁面里
onclick事件執行了兩次"alert",第二次是被XSS注入的。
那么正確的防御方法是什么呢?
首先,在"$var"輸出到<script>時,應該執行一次javascriptEncode;其次,在document.write輸出到HTML頁面時,要分具體情況看待:如果輸出到事件或者腳本中,則要再做一次javascriptEncode;如果是輸出到HTML內容或屬性,則要做一次HtmlEncode
也就是說,從JavaScript輸出到HTML頁面,也相當於一次XSS輸出的過程,需要分語境使用不同的編碼函數。
會觸發DOM Based XSS的地方很多,以下幾個地方是JavaScript輸出到HTML頁面的必經之路:
-
document.write()
-
document.writeIn()
-
xxx.innerHTML=
-
xxx.outerHTML=
-
innerHTML.replace
-
document.attachEvent()
-
window.attachEvent()
-
document.location.replace()
-
document.location.assign()
...........
除了服務器端直接輸出變量到JavaScript外,還有以下幾個地方可能會成為DOM Based XSS的輸出點:
-
頁面中所有的inputs框
-
window.location(href、hash等)
-
window.name
-
document.referer
-
document.cookie
-
localstorage
-
XMLHttpRequest返回的數據
