0x00 起
前一段時間,因為工作原因接觸到XSS漏洞檢測。前人留下的鍋,是采用pyqt webkit來解析網頁內容。作為Python webkit框架,相比於PhantomJS,pyqt在捕獲錯誤,重載函數等方面有比較多的優勢,但pyqt也有很有缺點:占用資源較多、底層解析還是用C++,許多錯誤是C++直接拋出的,Python依然無法捕獲、歷史遺留等等問題。
最近做畢設,考慮到以上的優缺點,動態解析部分采用了PhantomJS來編寫。本文就介紹一下XSS動態解析的思路及部分關鍵代碼。
0x10 漏洞判別標准
XSS漏洞,說到底還是用戶輸入被當成頁面代碼解析了。解析的結果,可能是執行了JS代碼,也可能是在頁面中創建/修改了某個DOM節點。所以我們將Payload分為兩類:第一類,執行了指定的JS代碼(alert(1)),第二類,創建了新的DOM節點(<xsstest></xsstest>)。
根據這兩種Payload,自然而然的推出了漏洞判別標准:頁面彈窗(在PhantomJS中重載window.alert)、新節點(解析玩頁面后,判斷document.getElementsByTagName('xsstest')是否為空)。
page.onAlert = function (message) {
if(message == xss_mark) {
xss_exists = 1;
ret = "Success, xss exists";
phantom_exit(ret);
}
console.log('Alert: ' + message);
return true;
};
function check_dom_xss_vul(){
return document.getElementsByTagName(dom_xss_mark).length;
}
為了驗證檢測代碼,編寫一個簡單存在XSS漏洞的頁面。
<?php
echo $_GET['test'];
?>
經測試,訪問 http://127.0.0.1:8000/xss.php?test=<img src=1 onerror=alert(1)>,我們的檢測代碼成功檢測到了彈窗,並返回了正確的結果。但是,如果是下面這種情況呢?
<?php $click = $_GET['test']; echo "<div onclick=$click></div>"; ?>
0x20 執行事件代碼
很明顯,我們需要執行onclick中的代碼,才能檢測到漏洞。首先我們想到的是觸發事件,僅僅是觸發click事件,很簡單,javascript本身就提供了click事件: document.getElementsByTagName('div')[0].click()。但是javascript也就僅僅提供了click事件的觸發函數而已。


但既然代碼直接輸出在了onclick/onmouseover之類的屬性里,我們遍歷所有節點的屬性,針對onxxxxx的屬性值,直接調用eval方法,執行對應的代碼就可以了。
var nodes = document.all; for(var i=0;i<nodes.length;i++){ var attrs = nodes[i].attributes; for(var j=0;j<attrs.length;j++){ attr_name = attrs[j].nodeName; attr_value = attrs[j].nodeValue; if(attr_name.substr(0,2) == "on"){ console.log(attrs[j].nodeName + ' : ' + attr_value); eval(attr_value); } } }
訪問http://127.0.0.1:8000/xss.php?test=alert(1) 成功執行代碼,但新的問題很快出現:並不是所有的JS代碼都是以內聯的形式寫入到HTML代碼中的,程序猿們往往更喜歡通過 document.addEventListener 或者 jQuery中的 $('dom').click 直接綁定事件。例子如下:
<script type="text/javascript" src="http://apps.bdimg.com/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="link-area"></div>
<?php echo '<script>$("#image").click(function(){$(".link-area").html("'.$_GET['test'].'")});</script>'; ?>
0x30 觸發事件
所以我們現在需要這樣的接口: 能夠觸發某個DOM節點的某個事件,包括但不僅限於click事件。PhantomJS和Javascript都可能存在這樣的接口,但是找遍了PhantomJS,甚至是CasperJS的接口,也只是發現了觸發click事件的接口。所以聚焦點重新回到Javascript上來。很快,我們發現了dispatchEvent函數。
// phantom_finish.js
var evt = document.createEvent('CustomEvent'); evt.initCustomEvent(click, true, true, null); document.getElementsByTagName("div")[0].dispatchEvent(evt);
成功執行了click事件,但是如何能獲取到所有節點的綁定事件呢?有兩種方法:
- 遍歷所有節點,獲取每個節點綁定的事件
- 在dom節點加載前,重寫addEventListener方法,並將所有的綁定的事件及節點記錄到一個數組中。
方法一在遇到jQuery綁定事件的時候撲街了。方法二明顯比方法一節省資源,並且測試通過。核心代碼如下:
// phantom_init.js
_addEventListener = Element.prototype.addEventListener Element.prototype.addEventListener = function(a,b,c) { save_event_dom(this, a); // 將所有的綁定事件節點信息存儲起來
_addEventListener.apply(this, arguments); };
這樣,我們的JS代碼也算告一段落,PhantomJS組件能夠執行內聯代碼及觸發所有的綁定事件。萬事具備,只欠一個調度系統了~
0x40 調度系統
XSS掃描是URL粒度掃描,針對網站的每一個鏈接都要進行測試。XSS檢測系統的輸入值包括:
- URL (如:http://127.0.0.1:8000/xss.php?a=1&b=2)
- method
- post_data
- headers
調度系統的功能就是處理這個URL,拼接對應的payload,並調用PhantomJS組件,檢測是否含有XSS漏洞。舉個例子,當payload為 <img src=1 onerror=alert(1)> 時,需要調用兩次PhantomJS組件,輸入的URL分別為:
- http://127.0.0.1:8000/xss.php?a=<img src=1 onerror=alert(1)>&b=2
- http://127.0.0.1:8000/xss.php?a=1&b=<img src=1 onerror=alert(1)>
0x50 合
上述種種,已經基本將動態XSS檢測的思路分析透徹。XSS有很多種玩法,在payload中可以帶進一些有意思的攻擊代碼,比如釣魚、打Cookie、甚至探測網絡狀況等等不再贅述。