XSStrike
XSStrike是一款XSS扫描工具。
Github地址: https://github.com/UltimateHackers/XSStrike
特点如下
- WAF识别与绕过
- 自动POC生成
- 支持GET与POST请求
- 支持Cookie/HTTP认证
- 隐藏参数发现
- Blind XSS 爆破
接下来也主要基于以上特点进行源码分析。
源码分析
程序初始化
在导入相关package后,XSStrike进行了一系列的设置初始化工作。按顺序梳理如下:
定义颜色参数:
初始化浏览器对象br
,并设置相关参数:
主要的几个参数:
- set_handle_robots = False,即不跟随robots.txt
- set_handle_equiv = True,作者也不知道为啥这样设置:)笑
- set_handle_redirect = True,跟随跳转
- set_handle_referer = True,在每次请求中添加Reffer头
接下来的部分初始化了一些变量和函数,如下:
xsschecker
被设定为d3v
,用于做xss的检测。这个d3v
是无害的,因此可以利用其来检测页面的输出点。之所以不使用payload,是因为有可能waf会直接过滤掉payload中的敏感关键字,使得检测失效,因此一般在xss扫描器中,会先使用无害的字符串来验证,之后再逐步调整payload。paranames
和paravalues
分别用来存放参数名和参数值。
|
|
因为在很多页面中html/body/br标签都不闭合,因此直接添加进了黑名单,而input/textarea作为输入和输出点很有可能出现xss因此予以优先考虑,添加进入whitelist
|
|
这里定义了后续fuzz/scan过程中用到的payload。
接着XSStrike进行update检查,随后程序流程来到第781行input()
,真正的扫描工作从这里开始。
input() - 扫描入口点
input()
是扫描的起始点,设定扫描目标及参数。源码如下:
接受URL地址,检测是否有URL地址中是否有协议,并对相应的URL进行连接测试。
|
|
接着接受输入cookie,作为后续扫描的身份认证。
|
|
接着input()
从给定的URL中解析出相应的参数。如果URL中包含查询参数,也即包含=
,说明为GET
请求,否则进行询问,并手动输入对应的参数名与参数值,并根据请求方式调用param_parser(target, param_data, GET, POST)
设置相应的参数。param_parser()定义在第626行,源码如下。param_parser()将对应的参数名和值分别添加入前面定义的paranames和paravalues中。
|
|
最后input()调用initiator()
进行扫描。
initiator() - xss 扫描
initiator()定义在第642行。代码大体框架如下:
第一步
先询问是否要查询隐藏参数,是的话则调用paramfinder(url, GET, POST)
。关于paramfinder()的解析见paramfinder() - 查找隐藏参数 - 查找隐藏参数)。
第二步
确认paranames
长度不为零后,根据请求方法的不同进行初步不同方式的扫描。此处GET
和POST
的请求的处理流程类似,可以归结为如下代码:
这个流程中,先用paranames[0]通过WAF_detector()
检测是否有WAF存在,对函数WAF_detector()
的解析见后。之后根据paranames中的参数,选择当前测试的参数paranames[current_param]
,对其余param_data中的参数则保留并存放于new_param_data
中。,根据GET或POST方式生成对应的param_data
。
比如url为:http://127.0.0.1/?input_r=f&input_d=e 。这里有两个参数input_r
和input_d
。当测试input_d
时,其值为xsschecker即d3v
。而new_param_data
为&input_r=f
。最后生成的初始测试参数param_data
即为?input_d=d3v&input_r=f
根据前面的检测WAF是否存在,程序会进行不同的分支。
有WAF情况
对应源码第671行即:
由于检测到了WAF,因此询问是否减缓请求速度来防止被办。然后调用fuzzer()
进行xss payload的fuzz。关于fuzzer()
部分见后。
无WAF情况
源码第681行:
先调用filter_checker(url, param_data, GET, POST)
进行基本的过滤检查,其中如果检查的字符串直接能触发xss则可以直接退出,否则进行进一步检查,对filter_checker()
的分析见后。
接着调用locater(url, param_data, GET, POST)
根据参数,对页面中所有可能的输出点进行一一定位,并将结果保存在occur_number
和occur_location
中。关于locater()
的分析见后
最后调用inject(url, param_data, GET, POST)
真正进行地xss扫描/fuzz工作。关于inject()
的分析见后。
结束对当前参数的检测后,清理occur_number
和occur_location
,用于存放下一个参数出现的ID和位置。current_param = current_param + 1
,程序进入对下一个参数的检测。
第三步
完成第二步的自动话检测后,这一步是手动检测,通过自动填充payaload,打开浏览器,进行人工确认:
paramfinder() - 查找隐藏参数
paramfinder()定义在第439行,源码如下:
paramfinder()先请求URL,获得HTML页面后,根据正则表达式提取出所有可能的输入点,并将其添加进blind_params
。
之后根据请求方法GET
还是POST
,构造相应的请求。在这两种请求中,参数名为从html页面提取的可能的参数,而参数值则为一开始即初始化过的xsschecker
。
接着paramfinder()根据返回页面中是否包含xsschecker的值来确定是否存在隐藏参数,并将其添加进入paranames
和paravalues
中,作为进一步扫描的对象。
WAF_detector() - WAF检测
WAF_detector() 定义在第171行,它通过发起请求,然后根据页面的response code来确定是否存在waf。源码如下:
该函数在第652行,initiator()
中调用:WAF_detector(url, '?'+paranames[0]+'='+xsschecker, GET, POST)
。
该函数将无害的xsschecker替换为最常见的payload<script>confirm()</script>
,因此当存在waf时,基本能触发waf,从而检测得到。之后根据下表进行了对waf的指纹检索:
status_code | WAF name |
---|---|
406或501 | Mod_Security |
999 | WebKnight |
419 | F5 BIG IP |
403 | Unknown |
fuzzer() - 对WAF的fuzz
fuzzer()定义在 134 行,源码如下:
fuzzes在程序初始化部分已经定义fuzzes = ['<z oNxXx=yyy>', '<z xXx=yyy>'.......]
。fuzzer中遍历fuzzes,通过对当前测试参数替换不同的payload,观察返回的html页面,若payload在页面中被匹配到则为Works
,否则即失败Filtered
或Blocked
。之后用PrettyTable输出fuzz的结果。
filter_checker() - 过滤检查
filter_checker()定义在 207 行:
这里直接使用<svg/onload=(confirm)()>
来进行过滤检查。变量strength用于表明过滤的强度。之后根据页面返回的html进行深入检查。
如果没有过滤,也即返回的html中直接包含了<svg/onload=(confirm)()>
,则直接确定过滤强度为Low or None。并且<svg/onload=(confirm)()>
即可作为payload,根据选择是要进一步的处理,还是直接根据这个payload打开相应的xss页面。下面是对应的代码。
倘若存在过滤,也即返回的页面中找不到<svg/onload=(confirm)()>
,可能直接整个去掉了,可能过滤了某些关键字,或者可能转义了敏感字符。则会更换测试的payload,比如<zz//onxx=yy>
,然后发起请。根据响应html,如果<zz//onxx=yy>
在html中,则过滤程度为medium,如果不在html,则过滤程度为high。相关代码如下:
locater() - 定位输出点
locater() 定义在第 254 行:
这里定位输出点,通过xsschecker的值为d3v,可以检测在html中该值出现了几次,保存为NUM_REFLECTIONS。同时用变量OCCURENCE_NUM来定位每次的输出点,然后通过对每一处进行scan_occurence()
,该函数定义在 273 行,代码如下:
|
|
html_parse(init_resp)
是作者自己实现的html解析函数,通过OCCURENCE_NUM可以定位到具体的输出点,然后确定输出点所在的位置。作者在注释中提到,如果输出点在注释中,则直接成为occur_number
和occur_location
的首元素,因为通常情况下处于注释中的代码时不会执行的。在其他情况下(script/html_data/start_end_tag_attr/attr),按顺序对应添加进入occur_number
和occur_location
。occur_number
是输出点的标号,occur_location
是输出点的位置。
inject() - payload注入
inject() 定义在 第 468 行,这部分是进行xss攻击的核心部分,代码较长,整体的框架如下:
首先会先定义四个变量special
、l_filling
、e_fillings
、fillings
,这些保存着后续payload生成的一些关键字符。接着通过对occur_number
,occur_location
的遍历,对每一个测试点进行测试。
测试主要分为两部分。
在第一部分的测试中,主要通过test_param_check()
来进行特殊字符的检测,查看是否进行了编码:
- 双引号(
'
) - 单引号(
"
) - 尖括号(
<>
)
关于test_param_check()
如何具体工作,见后文
在第二部分的测试中,根据当前测试点所在位置的不同进行不同的测试。
当输出点在注释(comment)中时:
为了闭合注释,则payload的前缀必然是-->
,而对于后缀可以是空,或者选择闭合<!--
。接着选取前面定义的各种payload组成元素,构成payload,进行test_param_check()
测试
当输出点在script
标签中时,同样确定了可能的前缀和后缀,然后在生成payload,最后进行test_param_check()
测试
当输出点在html_data
中时,比如<h1>输出点</h1>
为了能让js解析payload而不仅仅只是文本,通常需要有尖括号,比如<h1><script>alert(1)</h1>
、<h1><svg/onload=(confirm)()><h1>
,因此当检测到尖括号被过滤掉时,会直接跳过此次测试:
倘若没有过滤,则生成payload,并进行test_param_check()
测试。
当输出点在属性中时,比如<img src=输出点>
或者<img src="输出点">
或者<img src='输出点'>
,首先要考虑的时引号的闭合问题,因此会先提取出需要闭合的是单引号还是双引号还是不需要引号,然后生成payload进行test_param_check()
测试:
html_parse() - html解析
html_parse()定义在第 287 行:
而MyHTMLParser()
是作者实现的类,继承自HTMLParser,定义在第 360 行:
以handle_comment
为例,当当前处理的OCCURENCE_PARSED与OCCURENCE_NUM相等时,说明此时MyHTMLParser()解析到此时检查的输出点处,根据情况不同raise异常,比如raise Exception("comment")
。
然后html_parse()
中,通过捕获异常location = str(e)
来获得输出点的位置。
在html解析中,输出点主要分为以下几类:
- comment
- script
- attr
- html_data
- start_end_tag_attr
test_param_check() - 检查返回值
test_param_check()定义在 296 行,用于在注入特殊字符串(包括比如引号测试,payload测试)后,根据页面返回信息来确定是否xss成功。
|
|
check_string是发送的payload,由于要在网络中传输,因此一般会经过url编码。compare_string是页面返回html中期望看到的payload本身。这两个变量头尾都加上了XSSSTART
和XSSEND
,这是为了后续定位检测的方便。
在定位到输出点后,使用了fuzz.partial_ratio()
来计算字符串的相似度,来测试xss是否成功过。
根据官网的信息,里面是这么描述XSStrike的:
所以levensthian algorithm
即为partial_ratio()
。。
总结
XSStrike的运行流程归结如下:
- 程序初始化
- input() 程序入口
- param_parser() 参数解析
- initiator() xss扫描
- paramfinder() 查询隐藏参数
- GET/POST
- WAF_detector() WAF检测
- 有无WAF
- 有
- fuzzer()
- 无
- filter_checker()
- locater()
- scan_occurence()
- inject()
- test_param_check() 特殊字符检测
- test_param_check() payload注入检测1
- 手动检测
- 有