XSS 掃描器成長記

做者:w7ay @知道創宇404實驗室
時間:2020年2月12日
原文地址:https://paper.seebug.org/1119/php

爲了實現自動刷SRC的目標,過年前就開始對w13scan的xss掃描功能進行優化,靈感來源於xray所宣稱的基於語義的掃描技術。html

以前xss掃描是參考w3af中的源碼,原理也很簡單就是暴力的使用xss的payload進行請求,最後在返回文本中查找關鍵字,xss payload通常有如下幾個部分。
1.png
後面我認真的學習了一下XsstrikeXrayAwvs中的檢測技巧以及檢測參數,想將它們的優勢和爲一體。python

XSStrike

先說說Xsstrike,裏面帶有xss掃描和fuzz,但感受xss掃描的效果也不是太理想。不過它的一些特性仍是能夠學習的。mongodb

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腳本內的內容提取出來,經過一些正則來獲取,最後輸出。但這種方式準確度很低,只能用於輔助,不太適合自動化掃描。express

內置參數

它裏面有內置一些參數,在檢測時會將這些參數也一塊兒發送微信

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參數,在掃描時也會將這些參數加上。cookie

HTML解析&分析反射

若是參數能夠回顯,那麼經過html解析就能夠得到參數位置,分析回顯的環境(好比是否在html標籤內,是否在html屬性內,是否在註釋中,是否在js中)等等,以此來肯定檢測的payload。session

後面個人掃描器的檢測流程也是這樣,很是準確和效率,不過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 e"];?>" />

xray順序發送瞭如下payload:kmbrocvz,expression(a(kmbrocvz))

4.對於在html標籤內的內容,如下case

<input  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語法解析只須要肯定IdentifierLiteral這兩個類型中是否包含,若是flag是Identifier類型,就能直接判斷存在xss,payload是alert(1)//,若是flag是Literal類型,再經過單雙引號來閉合進行檢測。

Debug之旅

整個xss掃描代碼不過1000行,但debug的過程是道阻且長。

本地靶機測試後就對在線的靶機進行了測試https://brutelogic.com.br/knoxss.html

查漏補缺後就開始了自動化掃描。

整個自動化架構以下

1. 提供url -> 爬蟲爬取 -> 參數入庫 -> 消息隊列 -> xss掃描器
                                -> 子域名入庫
                                -> url入庫
  1. 爬蟲使用的crawlergo,效果挺不錯的,但仍是不太知足個人需求(造輪子的心態又膨脹了)
  2. 數據庫使用的mongodb
  3. 用celery分佈式調用,因爲用到了celery,又用到了rabbitmq消息隊列,flower監控
  4. 用了server醬進行微信推送(獲得一個漏洞微信就會響一次 )

剛開始打把遊戲微信就會不停的響,而後就查找誤報,優化邏輯,以此往復

通過了不懈的改造,優化了檢測邏輯,加入了去重處理後,如今不只掃描的慢並且推送的消息也變少了。

一些成果

通過一段時間對src的掃描後,成功仍是挺多的(不少都歸功於爬蟲)
2.png

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

3.png

未完,待續。。。

相關文章
相關標籤/搜索