AWVS作为非常强大的web漏洞扫描器,相信大家都不陌生。然而,AWVS必须运行在windows环境,对Linux用户来说,多了一层使用虚拟机的麻烦。鉴于此,我们对AWVS进行了一些简单分析, 并试着对其中几个插件进行了Python化。 在这个过程中, 可以更好地认识到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:漏洞的详细描述文档都在这里。
首先是XML
文件夹, 存放的都是不同类型的漏洞的详情介绍与修复方式, 相信使用过的小伙伴都在右侧的漏洞详情见过, 这里不再做分析;
其次是NetWork
文件夹, 这里存放的是对网络端口进行的一些漏洞检测与信息发现,比如其中的dns_zone_transfer.script
看名称也知道这是对域传送漏洞的检测, 其他还有ftp匿名登录等, 因为这系列的文章是从插件分析到代理扫描, 所以不再对这一部分进行详细介绍. 不过这里可以推荐看一下proxy*
系列相关的script, 毕竟每个公司可能都会开一些代理端口, 而且这一系列的漏洞在wooyun中也有介绍.
再就是PerFile
文件夹, 包括Backup_File.script
与Bash_RCE.script
等, 还有XSS_in_URI.script
, 这时对XSS进行了一个检测,稍后我们会对这里进行分析
PerFolder
文件夹,包括Access_Control_Allow_Origin.script
与Directory_Listing.script
等,还有SQL_Injection_In_Basic.script
这种检测SQL注入的. 同样,我们也会对注入进行一个检测
PerScheme
文件夹将是重点关注的内容,同quote中的内容一样, 这里包括常用的漏洞包括XSS/SQLI/XXE/SSRF等
再接下来是PerServer
文件夹: 是对不同中间件的检测如Apache_Proxy_CONNECT_Enable.script
是对apache代理检测, 还包括solr
,Flask Debug
等不同中间件的检测, 有兴趣的可以看一下, 包括JMX_RMI_service.script
也提供了一个检测插件
PostCrawl
这个,讲实话没有细看..就不过多叙述误导大家了;
文件夹PostScan
, 只有几个插件大家可以看一下名称: Stored_XSS
, Stored_SQL_Injection
, Stored_File_Inclusion
, Stored_Code_Execution
, Stored_File_Tampering
, 这随随便便拎出来一个就是高危呀同志们;
最后一个WebApps
是对一些常见的CMS如joomla
, drupal
, wordpress
进行漏洞检测;
本次分析了以下插件的实现思路
Blind_Sql_Injection.script
classSqlInjection.inc
classCRLFInjection.inc
classXSS.inc
Code_Execute.script
XXE_Folder.script
Server_Side_Request_Forgery.script
为了更好的理解AWVS的插件, 需要首先理解它的几个类:TURL
, TReportItem
, THTTPJob,
还有一个scheme
变量,前三个类看类名也比较好理解, 分别是处理URL, Report和HTTP请求的类,但是scheme
不太理解..目前也查不到相关的资料,如果有知道的小伙伴请联系我. 这些虽然和插件的流程无关,但是对于我们规划自己的工具还是很有帮助的.
经过对几个插件的分析, 发现一般都有这么几个函数: startTesting
作为入口,类似于python中的main
函数, 但是有的插件并没有这一个; alert
函数, 作为加载xml
文件与TReportItem
的函数, 可以不用太再意, request
函数, THTTPJob
的实例实现
闲言少叙, 开始分析第一个插件,我们先从classXSS
开始
在classXSS.inc
中, AWVS定义了三种document_type类型: html, xml, invalid, 而所有非html
与xml
类型的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; }
可以看到, 在inputValue
为script_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,
判断返回的Content-Type
类型
使用THTMLQuery
类来搜索对应的payload是否生成了相应的标签与内容
返回alert报告
但是对于每一种类型的payload都写一个函数实在是太累了, 如果想简单的使用的话,可以试试以下两种姿势:
建立一个dict
, 每一种payload的原始代码作为key
, 其不同的变种作为 value
, 使用value作为payload请求,在返回的响应中检测key
, 简单粗暴。
使用casperjs
这种headless浏览器,加载返回的响应内容,查看是否有alert自己的randStr。
在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函数, 其流程如下:
检测是否在返回值中存在匹配的errormessage, 如果不存在, 则continue
如果存在, 并且有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的报错语句即可.
同样我们从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这个标记语言的一种利用,但是这一块内容还不太熟悉,所以不在此进行分析.
这三个脚本看起来并不复杂, 但在分析的过程中, 还有很多的类实现机制不甚了解, 比如TURL, THTTPJob, THTMLQuery等, 但是正是因为不了解, 这种分析也更能推动自己的工具编写, 像可以试着自己实现一个类来模拟其中类的实现机制, 去实现AWVS中类的相关接口, 并且在分析这些插件的同时,还可以着手自己的插件改造计划. 可以按照 AWVS的分类,将插件分为几个部分,PORT
, SCHEME
等不同的文件夹下对应不同插件. 更重要的一点, 对于AWVS插件的分析,能够提高自己写插件的信心..因为一看其实它的插件难度也不是太大嘛(ORZ).
下一篇会对awvs的盲注进行分析,TO BE CONTINUED.
【原文:从AWVS插件到伪代理扫描 由71src入驻安全脉搏专栏作者 发布】