0x00 前言

AWVS作为非常强大的web漏洞扫描器,相信大家都不陌生。然而,AWVS必须运行在windows环境,对Linux用户来说,多了一层使用虚拟机的麻烦。鉴于此,我们对AWVS进行了一些简单分析, 并试着对其中几个插件进行了Python化。 在这个过程中, 可以更好地认识到AWVS的优势与不足。

awvs.png

0x01 AWVS插件简介

关于如何编写AWVS插件,以及每个目录下插件的作用, 《编写自己的Acunetix WVS漏洞脚本》一文中已有介绍

Network:此目录下的脚本文件是当扫描器完成了端口扫描模块后执行,这些脚本可以检测TCP端口的开放情况,比如检测FTP的21端口是否开放、是否允许匿名登录;

PerFile:此目录下的脚本是当扫描器爬虫爬到文件后执行,比如你可以检查当前测试文件是否存在备份文件,当前测试文件的内容等;

PerFolder:此目录下的脚本是当扫描器爬虫爬行到目录后执行,比如你可以检测当前测试目录是否存在列目录漏洞等;

PerScheme:此目录下的脚本会对每个URL的 GET、POST结构的参数进行检测,AWVS定义了的参数包括HTTP头、Cookies、GET/POST参数、文件上传(multipart/form-data)……比如你可以检测XSS、SQL注入和其他的应用程序测试;

PerServer:此目录下的脚本只在扫描开始是执行一次,比如你可以检测Web服务器中间件类型;

PostScan:此目录下的脚本只在扫描结束后执行一次,比如你可以检测存储型XSS、存储型SQL注入、存储型文件包含、存储型目录遍历、存储型代码执行、存储型文件篡改、存储型php代码执行等;

XML:漏洞的详细描述文档都在这里。


0x02 已分析的插件介绍

本次分析了以下插件的实现思路

  1. Blind_Sql_Injection.script

  2. classSqlInjection.inc

  3. classCRLFInjection.inc

  4. classXSS.inc

  5. Code_Execute.script

  6. XXE_Folder.script

  7. Server_Side_Request_Forgery.script


为了更好的理解AWVS的插件, 需要首先理解它的几个类:TURLTReportItemTHTTPJob,还有一个scheme变量,前三个类看类名也比较好理解, 分别是处理URL, Report和HTTP请求的类,但是scheme不太理解..目前也查不到相关的资料,如果有知道的小伙伴请联系我. 这些虽然和插件的流程无关,但是对于我们规划自己的工具还是很有帮助的.

 2.1 插件的流程

经过对几个插件的分析, 发现一般都有这么几个函数: startTesting作为入口,类似于python中的main函数, 但是有的插件并没有这一个; alert函数, 作为加载xml文件与TReportItem的函数, 可以不用太再意, request函数, THTTPJob的实例实现

闲言少叙, 开始分析第一个插件,我们先从classXSS开始

 2.2 classXSS

classXSS.inc中, AWVS定义了三种document_type类型: html, xml, invalid, 而所有非htmlxml类型的Content-Type都被定义为了 invalid. 同时还定义一个类THTMLQuery类,用于在非浏览器如casperjs等模型下找到XSS, 举例来说:

    var inputValue = prefix + script_start_tag + script_payload + "(" + rndStr + ")" + script_end_tag;
    ....
    var hq = new THTMLQuery(this.lastJob.response.body);
    if (hq.executeHtmlQuery("tag=script|textwithin=" + script_payload + "(" + rndStr + ")")
        || hq.executeHtmlQuery("tag=script|textwithin=" + script_payload + "('" + rndStr + "')")
        || hq.executeHtmlQuery('tag=script|textwithin=' + script_payload +' ("' + rndStr + '")'))
        {           
            this.alert(inputValue); 
            return false;
        }


可以看到, 在inputValuescript_start_tag,即<ScRiPt >时, 使用tag=|textwithin=这种来确定传入的值是否生成了标签而非content, 但是这个类的实现未曾找到其定义所在的文件. 不过我们可以使用BeasutifulSoup类来实现,如上边的类js代码可以转换为下边的python代码

from bs4 import BeautifulSoup as bsdef htmlquery(html, tag, payload):
    b = bs(html, 'html.parser')
    scripts = b.findAll(tag)
    for s in scripts:
        if payload in s:
            return True
    return False


classXSS.inc中, AWVS用了很多种类型的payload, 我把其中的test*函数过滤了一下, 对于某个函数想了解的可以更进一步参考

    classXSS.prototype.testPartialUserControllableScriptSrc 
    classXSS.prototype.testSimpleScriptTagInjection 
    classXSS.prototype.testSimpleScriptTagInjectionWithoutEncoding 
    classXSS.prototype.testISINDEX 
    classXSS.prototype.testScriptTagInjectionAgainstStringReplace 
    classXSS.prototype.testScriptTagVariantInjection 
    classXSS.prototype.testScriptTagSrcInjection


这里特别提一下ServerSideTemplateInjection这个函数, 这是服务器模板注入的一种, 在Python或者jsp中的作用不仅仅是XSS, 在AWVS中, 测试SSTI的payload是:var inputValue = "{{" + num1 + "*" + num2 + "}}"; 同时对于num1*num2的值在返回的body中做检测, 这也可以成功日后检测的一项. 更多的内容可以参考burpsutie的官方博客介绍:Server-Side Template Injection解锁更多姿势.

所以, 对于AWVS如何检测反射XSS,目前已经有了一个直接的理解:

但是对于每一种类型的payload都写一个函数实在是太累了, 如果想简单的使用的话,可以试试以下两种姿势:

  1. 建立一个dict, 每一种payload的原始代码作为key, 其不同的变种作为 value, 使用value作为payload请求,在返回的响应中检测key, 简单粗暴。

  2. 使用casperjs这种headless浏览器,加载返回的响应内容,查看是否有alert自己的randStr


 2.3 classSqlInjection.inc

classSqlInjection.inc中, 检测的是最常见的报错注入. 同样的, 入口函数是startTest, 然后对于不同类型的进行检测与验证. 由下边的代码可以看到:

    // AcuSensor is NOT enabled             
    if (!this.testForError()) continue;         
    // single quote + double quote
    if (!this.testInjection("1'\"", new Array("", "'", '"'))) continue;                
    // backslash
    if (!this.testInjection("\\")) continue;                
    // single quote + double quote (unicode)
    if (!this.testInjection("1\x00\xc0\xa7\xc0\xa2")) continue;             
    // scalar/variable
    if (!this.testInjection('@@' + randStr(5))) continue;
    // single + double quote (base64 encoded)
    if (!this.testInjection('JyI=')) continue;
    // GBK/Big5 encoding
    if (!this.testInjection('\xbf\'\xbf"')) continue;
    // utf8_decode
    if (!this.testInjection('\xF0\x27\x27\xF0\x22\x22')) continue;
    // conversion (ASP)
    if (!this.testInjection('(select convert(int,CHAR(65)))')) continue;


首先, 用testForError来检测原始请求中是否有出现的报错信息,如果有,进行下一个参数的检测,否则,分别使用不同的payload来进行报错注入的检测. 需要注意的是, 仅在使用payload:1'"的时候,需要第二个参数来重复验证.

接着看一下testeInjection函数, 其流程如下:

  1. 检测是否在返回值中存在匹配的errormessage, 如果不存在, 则continue

  2. 如果存在, 并且有confirm value,即函数的第二个参数, 分别使用mysql,mssql的报错语句进行复测

var matchedText = this.errorMessages.searchOnText(job.response.toString());     if (matchedText) {
    ...
    var markerPlain = '4Cu'+randStr(8);
    var markerEncodedMSSQL = encodeStringAsChar(markerPlain, '+');                            
    var markerEncodedMYSQL = encodeStringAsChar(markerPlain, ',');                            

    for (var i=0;i<confirmData.length;i++) {        
        // msyql variant 1
        confirmValue = confirmData[i] + 'and(select 1 from(select count(*),concat((select concat(' + markerEncodedMYSQL + ') from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)and' + confirmData[i];
        if ( this.confirmRequest(confirmValue) && this.lastJobConfirm.response.toString().indexOf(markerPlain) != -1) 
        {               
            verified = true;                     
            break;
        }....}


由上部分代码可以看到, 对于MySQL与MSSQL,使用不同的字符串连接符来将字符以CHAR(),|+这种形式连接起来, 然后分别使用MySQL中报错语句:

'and(select 1 from(select count(*),concat((select concat(' + markerEncodedMYSQL + ') from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)and'

再前后附加上字符来检测是响应中是否包含了markerPlain的值, 而对于除1'"之外的payload, 只需要检测其响应中是否包含了sql的报错语句即可.

 2.4 Server_Side_Request_Forgery.script

同样我们从startTest函数开始, 这里就需要用到dnslog这种,当然也有付费的cloudeye或者免费的ceye, 不过awvs的作为不太一样, 因为它不仅仅是一个dnslog平台,而是相当于自己的一个站点bxss.me, 通过请求它的二级域名,使用接口http://bxss.s3.amazonaws.com/hits/{二级域名}来验证是否访问过二级域名, 当然如果不想另外再搭建dnslog和使用负责的及免费的平台,直接使用这个也是可以的…

Server_Side_Request_Forgery.script中, 首先通过一个"http://hit" + rndToken + '.bxss.me/' 随机的二级域名作为payload, 来代换原始的参数值发送请求, 这里没有具体的了解到THTTPJob的参数, 这里只能猜测, 在对SSRF的验证中, 应该是不允许跳转的,不然的话并不能准确的判断到是跳转访问到了二级域名还是服务端访问到了. 另外在分析这个插件的时候, 发现它不仅仅这么简单, 换句话说, 这个插件不单纯..

它不仅包含着SSRF的探测,还顺路探测了一波XXE问题与file协议重定向问题. 它是这样的:

在上段中我们所说的随机二级域名中, 如果我们访问了该二级域名

http://hit' + {rndToken} + '.bxss.me/', 这个URL会返回一个XML格式的内容类似如下

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE myopenid [        <!ENTITY myopenident SYSTEM "http://hitcngulq53jkXXE.bxss.me/">
        ]><xrds:XRDS
        xmlns:xrds="xri://$xrds"
        xmlns:openid="http://openid.net/xmlns/1.0"
        xmlns="xri://$xrd*($v*2.0)">
    <XRD version="2.0">
        <xxx>&myopenident;</xxx>
        <Service priority="0">
            <Type>http://specs.openid.net/auth/2.0/signon</Type>
            <Type>http://openid.net/sreg/1.0</Type>
            <Type>http://openid.net/extensions/sreg/1.1</Type>
            <Type>http://schemas.openid.net/pape/policies/2007/06/phishing-resistant</Type>
            <Type>http://openid.net/srv/ax/1.0</Type>
            <URI>http://www.myopenid.com/server</URI>
            <LocalID>http://acunetix31337.myopenid.com/</LocalID>
        </Service>

        <Service priority="1">
            <Type>http://openid.net/signon/1.1</Type>
            <Type>http://openid.net/sreg/1.0</Type>
            <Type>http://openid.net/extensions/sreg/1.1</Type>
            <Type>http://schemas.openid.net/pape/policies/2007/06/phishing-resistant</Type>
            <Type>http://openid.net/srv/ax/1.0</Type>
            <URI>http://www.myopenid.com/server</URI>
            <openid:Delegate>http://acunetix31337.myopenid.com/</openid:Delegate>
        </Service>

        <Service priority="2">
            <Type>http://openid.net/signon/1.0</Type>
            <Type>http://openid.net/sreg/1.0</Type>
            <Type>http://openid.net/extensions/sreg/1.1</Type>
            <Type>http://schemas.openid.net/pape/policies/2007/06/phishing-resistant</Type>
            <Type>http://openid.net/srv/ax/1.0</Type>
            <URI>http://www.myopenid.com/server</URI>
            <openid:Delegate>http://acunetix31337.myopenid.com/</openid:Delegate>
        </Service>
    </XRD>* Connection #0 to host hitcngulq53jk.bxss.me left intact</xrds:XRDS>
在上边的xml返回值中,我们可以看到这样的一段
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE myopenid [
        <!ENTITY myopenident SYSTEM "http://hitcngulq53jkXXE.bxss.me/">
        ]>


相信了解过XXE漏洞的表哥应该都知道,这是XXE的一种payload, 所以在Server_Side_Request_Forgery.script这个脚本中, 还检测了是否存在XXE漏洞. 同时,对其代码分析, 还存在一个URL如下:http://bxss.me/redirToFile, 对其进行请求,可以看到以下的返回


➜  leanote curl -v http://bxss.me/redirToFile
* Trying 54.214.14.19...
* Connected to bxss.me (54.214.14.19) port 80 (#0)
> GET /redirToFile HTTP/1.1
> Host: bxss.me
> User-Agent: curl/7.47.0
> Accept: */*
> 

< HTTP/1.1 302 FOUND
< Server: nginx/1.10.3 (Ubuntu)
< Date: Wed, 09 May 2018 14:18:04 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 243
< Connection: keep-alive
< Location: file:///etc/passwd
< 

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
* Connection #0 to host bxss.me left intact
<p>You should be redirected automatically to target URL: 
<a href="file:///etc/passwd">file:///etc/passwd</a>.  If not click the link.


其Location指向的是file:///etc/passwd这个, 顺手检测一把file重定向,这个姿势真是可以啊.

另外, 在这个脚本中还发现'http://bxss.me/xslt?t=' + rndToken 这个URL, 是对于XSLT这个标记语言的一种利用,但是这一块内容还不太熟悉,所以不在此进行分析.

0x03 总结

这三个脚本看起来并不复杂, 但在分析的过程中, 还有很多的类实现机制不甚了解, 比如TURL, THTTPJob, THTMLQuery等, 但是正是因为不了解, 这种分析也更能推动自己的工具编写, 像可以试着自己实现一个类来模拟其中类的实现机制, 去实现AWVS中类的相关接口, 并且在分析这些插件的同时,还可以着手自己的插件改造计划. 可以按照 AWVS的分类,将插件分为几个部分,PORTSCHEME等不同的文件夹下对应不同插件. 更重要的一点, 对于AWVS插件的分析,能够提高自己写插件的信心..因为一看其实它的插件难度也不是太大嘛(ORZ).

下一篇会对awvs的盲注进行分析,TO BE CONTINUED.

0x04 参考

  1. 对AWVS的一次简单分析

  2. 编写自己的Acunetix WVS漏洞脚本

  3. AWVS解密脚本pwd: jntw


【原文:从AWVS插件到伪代理扫描   由71src入驻安全脉搏专栏作者 发布】

源链接

Hacking more

...