[+] Author: fridayy
[+] Team: n0tr00t security team
[+] From: http://www.n0tr00t.com
[+] Create: 2016-10-29
XSS 是典型的瀏覽器端漏洞,由于用戶的輸入未經轉義直接輸出到頁面中,惡意代碼在用戶的瀏覽器中被解析,從而造成危害。傳統的反射型 XSS 可以通過判斷頁面源碼是否含有特定字符串來檢測。但由于 Web 2.0 的快速發展交互越來越復雜,DOM-XSS 也層出不窮,導致傳統的檢測方案的漏報率很高。本文主要介紹了如何利用 PhantomJS + Python 完成動態檢測。
0x01 PhantomJS
既然是動態檢測,那么就需要一個瀏覽器,但普通的瀏覽器在渲染頁面上花費了太多的資源和時間,并不適用。怎么辦?當然開源世界早有解決方案:PhantomJS、PyQt、CEF 等等。對比了一下上手難易程度、文檔豐富程度等,我選擇了 PhantomJS 進行開發。
PhantomJS 是無界面的 Webkit 解析器,提供了 JavaScript API 。由于去除了可視化界面,速度比一般 Webkit 瀏覽器要快很多。同時提供了很多監控和觸發接口,可以方便的操作頁面 DOM 節點,模擬用戶操作等。
0x02 漏洞判別標準
XSS 漏洞說到底還是用戶輸入被當成頁面代碼解析了,解析的結果可能是執行了JS代碼,也可能是在頁面中創建/修改了某個 DOM 節點(有部分過濾,無法執行JS代碼的情況下)。所以我們將 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>";
?>
0x03 執行事件代碼
很明顯,我們需要執行onclick中的代碼,才能檢測到漏洞。首先我們想到的是觸發事件,僅僅是觸發 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>';
?>
0x04 觸發事件
所以我們現在需要這樣的接口:能夠觸發某個 DOM 節點的某個事件,包括但不僅限于 click 事件。 PhantomJS 和 JavaScript 都可能存在這樣的接口,但是找遍了 PhantomJS 的接口,也只是發現了觸發 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 組件能夠執行內聯代碼及觸發所有的綁定事件。萬事具備,只欠一個調度系統了~
0x05 調度系統
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)>
當然 Payload 不止一個,會有很多種玩法,簡單提供幾個基礎 Payload :
'"><img src=1 onerror=alert(1)>
'"><script>alert(1)</script>
';alert(1)//
";alert(1)//
'" onmouseover=alert(1)
javascript:alert(1)
'"></script><img src=1 onerror=alert(1)>
"'></textarea><xsstest>
0x06 更多思考
采用了 Webkit 解析器來檢測XSS漏洞,提高了檢測的覆蓋率,也大幅降低了誤報率。但有些僅在 IE 下有效的漏洞,就無法覆蓋到了。上述種種,已經基本將動態XSS檢測的思路分析透徹。XSS有很多種玩法,在payload中可以帶進一些有意思的攻擊代碼,比如釣魚、打Cookie(配合XSS平臺)、甚至探測網絡狀況等等不再贅述。
最后,再次歡迎對 XSS 利用有各種猥瑣想法的同學來交流,微博 @Fr1day
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/93/
暫無評論