作者:w7ay @知道創宇404實驗室
時間:2020年2月12日
為了實現自動刷SRC的目標,過年前就開始對w13scan的xss掃描功能進行優化,靈感來源于xray所宣稱的基于語義的掃描技術。
之前xss掃描是參考w3af中的源碼,原理也很簡單就是暴力的使用xss的payload進行請求,最后在返回文本中查找關鍵字,xss payload一般有以下幾個部分。

后面我認真的學習了一下Xsstrike、Xray、Awvs中的檢測技巧以及檢測參數,想將它們的優點和為一體。
XSStrike
先說說Xsstrike,里面帶有xss掃描和fuzz,但感覺xss掃描的效果也不是太理想。不過它的一些特性還是可以學習的。
DOM XSS
Xsstrike的dom掃描,是通過正則來分析敏感函數
sources = r'''document\.(URL|documentURI|URLUnencoded|baseURI|cookie|referrer)|location\.(href|search|hash|pathname)|window\.name|history\.(pushState|replaceState)(local|session)Storage'''
sinks = r'''eval|evaluate|execCommand|assign|navigate|getResponseHeaderopen|showModalDialog|Function|set(Timeout|Interval|Immediate)|execScript|crypto.generateCRMFRequest|ScriptElement\.(src|text|textContent|innerText)|.*?\.onEventName|document\.(write|writeln)|.*?\.innerHTML|Range\.createContextualFragment|(document|window)\.location'''
scripts = re.findall(r'(?i)(?s)<script[^>]*>(.*?)</script>', response)
通過將script腳本內的內容提取出來,通過一些正則來獲取,最后輸出。但這種方式準確度很低,只能用于輔助,不太適合自動化掃描。
內置參數
它里面有內置一些參數,在檢測時會將這些參數也一起發送
blindParams = [ # common paramtere names to be bruteforced for parameter discovery
'redirect', 'redir', 'url', 'link', 'goto', 'debug', '_debug', 'test', 'get', 'index', 'src', 'source', 'file',
'frame', 'config', 'new', 'old', 'var', 'rurl', 'return_to', '_return', 'returl', 'last', 'text', 'load', 'email',
'mail', 'user', 'username', 'password', 'pass', 'passwd', 'first_name', 'last_name', 'back', 'href', 'ref', 'data', 'input',
'out', 'net', 'host', 'address', 'code', 'auth', 'userid', 'auth_token', 'token', 'error', 'keyword', 'key', 'q', 'query', 'aid',
'bid', 'cid', 'did', 'eid', 'fid', 'gid', 'hid', 'iid', 'jid', 'kid', 'lid', 'mid', 'nid', 'oid', 'pid', 'qid', 'rid', 'sid',
'tid', 'uid', 'vid', 'wid', 'xid', 'yid', 'zid', 'cal', 'country', 'x', 'y', 'topic', 'title', 'head', 'higher', 'lower', 'width',
'height', 'add', 'result', 'log', 'demo', 'example', 'message']
很好的思路,后面我的掃描器中也使用了這一點,從烏云鏡像XSS分類中提取出了top10參數,在掃描時也會將這些參數加上。
HTML解析&分析反射
如果參數可以回顯,那么通過html解析就可以獲得參數位置,分析回顯的環境(比如是否在html標簽內,是否在html屬性內,是否在注釋中,是否在js中)等等,以此來確定檢測的payload。
后面我的掃描器的檢測流程也是這樣,非常準確和效率,不過Xsstrike分析html是自己寫的分析函數,剛開始我也想直接用它的來著,但是這個函數內容過多,調試困難,代碼也很難理解。
其實如果把html解析理解為html的語義分析,用python3自帶的html提取函數很容易就能完成這一點。
Xray
XSStrike讓我學習到了新一代xss掃描器應該如何編寫,但新一代xss掃描器的payload是在Xray上學到的。
由于Xray沒有開源,所以就通過分析日志的方式來看它的工作原理。
準備工作
<html>
<body>
<a href="?q=1&w=2&e=3&r=4&t=5" />
<script>
<php
foreach($_GET as $key => $value){
// $_GET[$key] = htmlspecialchars($value);
}
$q = $_GET["q"];
$w = $_GET["w"];
$e = $_GET["e"];
$r = $_GET["r"];
$t = $_GET["t"];
if(stripos($q,"prompt") > 0){
die("error");
}
$var = 'var a = "'.$q.'";';
echo $var;
>
</script>
<div>
<textarea><?php echo $w;?></textarea>
</div>
<input style="color:<?php echo $e;?>" value="<?php echo $r;?>"/>
<!--
this is comment
<?php echo $t;?>
-->
</body>
</html>
簡單寫了一個腳本,用來分別測試xss在script,style內,html標簽內,注釋這幾種情況下xray的發包過程。
發包探索
1.對于在script的腳本內的回顯內容,對于以下case
<script>
$var = 'var a = "'.$_GET['q'].'";';
echo $var;
</script>
xray順序發送了以下payload:pdrjzsqc,"-pdrjzsqc-",</sCrIpT><ojyrqvrzar>
最后會給出payload,但這個包并沒有發送。后面把prompt作為關鍵詞屏蔽,發現最后還是給出這個payload。
還有一種情況,在script中的注釋中輸出
<html>
<body>
<script>
var a = 11;
// inline <?php echo $_GET["a"];?>
/* <?php echo $_GET["b"];?> */
</script>
</body>
</html>
xray會發送`\n;chxdsdkm;//`來判定,最后給出payload `\n;prompt(1);//`
2.對于在標簽內的內容,對于以下case
<textarea><?php echo $_GET["w"];?></textarea>
xray順序發送了以下payload:spzzmsntfzikatuchsvu,</tExTaReA><lixoorqfwj>,當確定尖括號沒有被過濾時,會繼續發送以下payload:</TeXtArEa>sCrIpTjhymehqbkrScRiPt,</TeXtArEa>iMgSrCoNeRrOrjhymehqbkr>,</TeXtArEa>SvGoNlOaDjhymehqbkr>,</TeXtArEa>IfRaMeSrCjAvAsCrIpTjhymehqbkr>,</TeXtArEa>aHrEfJaVaScRiPtjhymehqbkrClIcKa,</TeXtArEa>iNpUtAuToFoCuSoNfOcUsjhymehqbkr>,進行關鍵詞的試探,最后給出payload為</TeXtArEa><img src=1>
3.對于在style里內容,以下case
<input style="color:<?php echo $_GET["e"];?>" />
xray順序發送了以下payload:kmbrocvz,expression(a(kmbrocvz))
4.對于在html標簽內的內容,以下case
<input style="color:3" value="<?php echo $_GET["r"];?>"/>
xray順序發送了以下payload:spzzmsntfzikatuchsvu,"ljxxrwom=",'ljxxrwom=',ljxxrwom=,當確認引號沒有被過濾時,會繼續發送以下payload:"><vkvjfzrtgi>,">ScRiPtvkvjfzrtgiScRiPt,">ImGsRcOnErRoRvkvjfzrtgi>,">SvGoNlOaDvkvjfzrtgi>,">iFrAmEsRcJaVaScRiPtvkvjfzrtgi>,">aHrEfJaVaScRiPtvkvjfzrtgicLiCkA,">InPuTaUtOfOcUsOnFoCuSvkvjfzrtgi>," OnMoUsEoVeR=xviinqws,最后可以確定payload為"><img src=1>,"OnMoUsEoVeR=prompt(1)//
如果針對此類case:
<img src=1 onerror="a<?php echo htmlspecialchars($_GET["a"]);?>" />
xray返回payload為prompt(1),說明xray會把onerror后面的內容當作JavaScript腳本來執行,如果把onerror改為onerror1,同樣會返回prompt。在awvs規則中也看到過類似的規則
parName == "ONAFTERPRINT" ||
parName == "ONBEFOREPRINT" ||
parName == "ONBEFOREONLOAD" ||
parName == "ONBLUR" ||
parName == "ONERROR" ||
parName == "ONFOCUS" ||
parName == "ONHASCHANGE" ||
parName == "ONLOAD" ||
parName == "ONMESSAGE" ||
parName == "ONOFFLINE" ||
parName == "ONONLINE" ||
parName == "ONPAGEHIDE" ||
parName == "ONPAGESHOW" ||
parName == "ONPOPSTATE" ||
parName == "ONREDO" ||
parName == "ONRESIZE" ||
parName == "ONSTORAGE" ||
parName == "ONUNDO" ||
parName == "ONUNLOAD" ||
parName == "ONBLUR" ||
parName == "ONCHANGE" ||
parName == "ONCONTEXTMENU" ||
parName == "ONFOCUS" ||
parName == "ONFORMCHANGE" ||
parName == "ONFORMINPUT" ||
parName == "ONINPUT" ||
parName == "ONINVALID" ||
parName == "ONRESET" ||
parName == "ONSELECT" ||
parName == "ONSUBMIT" ||
parName == "ONKEYDOWN" ||
parName == "ONKEYPRESS" ||
parName == "ONKEYUP" ||
parName == "ONCLICK" ||
parName == "ONDBLCLICK" ||
parName == "ONDRAG" ||
parName == "ONDRAGEND" ||
parName == "ONDRAGENTER" ||
parName == "ONDRAGLEAVE" ||
parName == "ONDRAGOVER" ||
parName == "ONDRAGSTART" ||
parName == "ONDROP" ||
parName == "ONMOUSEDOWN" ||
parName == "ONMOUSEMOVE" ||
parName == "ONMOUSEOUT" ||
parName == "ONMOUSEOVER" ||
parName == "ONMOUSEUP" ||
parName == "ONMOUSEWHEEL" ||
parName == "ONSCROLL" ||
parName == "ONABORT" ||
parName == "ONCANPLAY" ||
parName == "ONCANPLAYTHROUGH" ||
parName == "ONDURATIONCHANGE" ||
parName == "ONEMPTIED" ||
parName == "ONENDED" ||
parName == "ONERROR" ||
parName == "ONLOADEDDATA" ||
parName == "ONLOADEDMETADATA" ||
parName == "ONLOADSTART" ||
parName == "ONPAUSE" ||
parName == "ONPLAY" ||
parName == "ONPLAYING" ||
parName == "ONPROGRESS" ||
parName == "ONRATECHANGE" ||
parName == "ONREADYSTATECHANGE" ||
parName == "ONSEEKED" ||
parName == "ONSEEKING" ||
parName == "ONSTALLED" ||
parName == "ONSUSPEND" ||
parName == "ONTIMEUPDATE" ||
parName == "ONVOLUMECHANGE" ||
parName == "ONWAITING" ||
parName == "ONTOUCHSTART" ||
parName == "ONTOUCHMOVE" ||
parName == "ONTOUCHEND" ||
parName == "ONTOUCHENTER" ||
parName == "ONTOUCHLEAVE" ||
parName == "ONTOUCHCANCEL" ||
parName == "ONGESTURESTART" ||
parName == "ONGESTURECHANGE" ||
parName == "ONGESTUREEND" ||
parName == "ONPOINTERDOWN" ||
parName == "ONPOINTERUP" ||
parName == "ONPOINTERCANCEL" ||
parName == "ONPOINTERMOVE" ||
parName == "ONPOINTEROVER" ||
parName == "ONPOINTEROUT" ||
parName == "ONPOINTERENTER" ||
parName == "ONPOINTERLEAVE" ||
parName == "ONGOTPOINTERCAPTURE" ||
parName == "ONLOSTPOINTERCAPTURE" ||
parName == "ONCUT" ||
parName == "ONCOPY" ||
parName == "ONPASTE" ||
parName == "ONBEFORECUT" ||
parName == "ONBEFORECOPY" ||
parName == "ONBEFOREPASTE" ||
parName == "ONAFTERUPDATE" ||
parName == "ONBEFOREUPDATE" ||
parName == "ONCELLCHANGE" ||
parName == "ONDATAAVAILABLE" ||
parName == "ONDATASETCHANGED" ||
parName == "ONDATASETCOMPLETE" ||
parName == "ONERRORUPDATE" ||
parName == "ONROWENTER" ||
parName == "ONROWEXIT" ||
parName == "ONROWSDELETE" ||
parName == "ONROWINSERTED" ||
parName == "ONCONTEXTMENU" ||
parName == "ONDRAG" ||
parName == "ONDRAGSTART" ||
parName == "ONDRAGENTER" ||
parName == "ONDRAGOVER" ||
parName == "ONDRAGLEAVE" ||
parName == "ONDRAGEND" ||
parName == "ONDROP" ||
parName == "ONSELECTSTART" ||
parName == "ONHELP" ||
parName == "ONBEFOREUNLOAD" ||
parName == "ONSTOP" ||
parName == "ONBEFOREEDITFOCUS" ||
parName == "ONSTART" ||
parName == "ONFINISH" ||
parName == "ONBOUNCE" ||
parName == "ONBEFOREPRINT" ||
parName == "ONAFTERPRINT" ||
parName == "ONPROPERTYCHANGE" ||
parName == "ONFILTERCHANGE" ||
parName == "ONREADYSTATECHANGE" ||
parName == "ONLOSECAPTURE" ||
parName == "ONDRAGDROP" ||
parName == "ONDRAGENTER" ||
parName == "ONDRAGEXIT" ||
parName == "ONDRAGGESTURE" ||
parName == "ONDRAGOVER" ||
parName == "ONCLOSE" ||
parName == "ONCOMMAND" ||
parName == "ONINPUT" ||
parName == "ONCONTEXTMENU" ||
parName == "ONOVERFLOW" ||
parName == "ONOVERFLOWCHANGED" ||
parName == "ONUNDERFLOW" ||
parName == "ONPOPUPHIDDEN" ||
parName == "ONPOPUPHIDING" ||
parName == "ONPOPUPSHOWING" ||
parName == "ONPOPUPSHOWN" ||
parName == "ONBROADCAST" ||
parName == "ONCOMMANDUPDATE" ||
parName == "STYLE"
awvs會比較參數名稱來確定。在后面的自動化掃描中,發現這種方式的誤報還是很高,最后我將這種情況調整到了awvs的方式,只檢測指定的屬性key。
從這兩處細微的差別可以看到,awvs寧愿漏報也不誤報,結果會很準確,xray更多針對白帽子,結果會寬泛一些。
5.對于在html注釋內的內容,以下case
<!--
this is comment
<?php echo $t;?>
-->
xray順序發送了以下payload:spzzmsntfzikatuchsvu,--><husyfmzvuq>,--!><oamtgwmoiz>,和上面類似,當確定-->或--!>沒有過濾時,會發送
以 --> 或 --!> 開頭,添加如下內容
<bvwpmjtngz>
sCrIpTbvwpmjtngzsCrIpT
ImGsRcOnErRoRbvwpmjtngz>
sVgOnLoAdbvwpmjtngz>
iFrAmEsRcJaVaScRiPtbvwpmjtngz>
aHrEfJaVaScRiPtbvwpmjtngzcLiCkA
InPuTaUtOfOcUsOnFoCuSbvwpmjtngz>
Awvs
Awvs的掃描規則很多,針對的情況也很多,沒有仔細看它的工作方式是怎樣的,主要是看它的payload以及檢測的情況,和上面兩種查漏補缺,最終合成了我的xss掃描器~比如它會對meta標簽的content內容進行處理,會對你srcipt,src等tag的屬性處理,也有一些對AngularJs等一些流行的框架的XSS探測payload。
我的掃描器
我的XSS掃描器就是綜合上面三種掃描器而來,如果仔細觀察,還會發現上面掃描器的一些不同尋常的細節。
比如xray不會發送xss的payload,都是用一些隨機字符來代替,同時也會隨機大小寫對一些標簽名稱,屬性名稱等等。
這些精致的技巧我的掃描器也都一一吸取了,嘿嘿!
掃描流程
我的掃描器掃描流程是這樣的
發送隨機flag -> 確定參數回顯 -> 確定回顯位置以及情況(html,js語法解析) -> 根據情況根據不同payload探測 -> 使用html,js語法解析確定是否多出來了標簽,屬性,js語句等等
使用html語法樹檢測有很多優勢,可以準確判定回顯所處的位置,然后通過發送一個隨機payload,例如<Asfaa>,再使用語法檢測是否有Asfaa這個標簽,就能確定payload是否執行成功了。
html語法樹用python自帶的庫
from html.parser import HTMLParser
js檢測也是如此,如果回顯內容在JavaScript腳本中,發送隨機flag后,通過js語法解析只需要確定Identifier和Literal這兩個類型中是否包含,如果flag是Identifier類型,就能直接判斷存在xss,payload是alert(1)//,如果flag是Literal類型,再通過單雙引號來閉合進行檢測。
Debug之旅
整個xss掃描代碼不過1000行,但debug的過程是道阻且長。
本地靶機測試后就對在線的靶機進行了測試 https://brutelogic.com.br/knoxss.html
查漏補缺后就開始了自動化掃描。
整個自動化架構如下
1. 提供url -> 爬蟲爬取 -> 參數入庫 -> 消息隊列 -> xss掃描器
-> 子域名入庫
-> url入庫
- 爬蟲使用的crawlergo,效果挺不錯的,但還是不太滿足我的需求(造輪子的心態又膨脹了)
- 數據庫使用的mongodb
- 用celery分布式調用,由于用到了celery,又用到了rabbitmq消息隊列,flower監控
- 用了server醬進行微信推送(得到一個漏洞微信就會響一次 )
剛開始打把游戲微信就會不停的響,然后就查找誤報,優化邏輯,以此往復
經過了不懈的改造,優化了檢測邏輯,加入了去重處理后,現在不僅掃描的慢而且推送的消息也變少了。
一些成果
經過一段時間對src的掃描后,成功還是挺多的(很多都歸功于爬蟲)

甚至發現了微軟分站某處xss

未完,待續。。。
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1119/