waf识别在渗透测试中必不可少的一环
识别网站使用的什么WAF,可以去找相应的绕过手段。
以下分析 wafw00f 工具源代码的实现,与其流量的特征,主要包括其通过哪些语句测试是否存在 WAF ,以及其判断 WAF 类型所使用的特征库,通多对这些的了解,将会更熟悉该工具的运作机制,同样可以借鉴其用于识别不同厂商 WAF 的指纹信息
这不是本次的重点,相关介绍及使用方法相信大家已经了解,所以此处就直接引用其开发者对该工具的介绍。
To do its magic, WAFW00F does the following:
Sends a normal HTTP request and analyses the response; this identifies a number of WAF solutions.
通过发送正常的HTTP
请求并且分析其返回包,判断其是否使用 WAF ,若使用确认WAF
类型
If that is not successful, it sends a number of (potentially malicious) HTTP requests and uses simple logic to deduce which WAF it is.
若是无法通过正常的HTTP
请求结果分析出是否使用WAF
以及其类型,则构造恶意的请求通过简单的逻辑再次进行判断
If that is also not successful, it analyses the responses previously returned and uses another simple algorithm to guess if a WAF or security solution is actively responding to our attacks.
如果这也不成功,它会分析之前返回的响应,并使用另一种简单的算法来猜测WAF
或安全解决方案是否正在积极响应我们的攻击
这不是本次的重点,想要具体了解其用法去其
github
主页即可
https://github.com/EnableSecurity/wafw00f
本工具的作用上面已经很详细的描述出来了,概括一下很简单:探测目标是否存在WAF
,也可以说wafw00f
是一个Web
应用防火墙(WAF
)指纹识别的工具。
使用方法不难,此处不做介绍,若想进一步了解见:WEB 防火墙探测工具 – wafw00f 使用教程 - General 的个人博客
所谓源码解析并不会完整分析其源码中的所有功能,比如 日志、输出、展示 等功能不在我们分析的范围内,而其对 WAF 的检测逻辑、所发流量的特征、WAF 识别的指纹等将是重点要分析的地方。
流程:
为了方便大家之后去看源码,此处简单描述一下源码的组成,其中没有描述的说明其用不大,大家自己去看就知道了,其中标注
important
是本工具的主要功能文件,后续将重点说明。
bin
lib
asciiarts.py
evillib.py
用于向目标建立连接发起请求 ( important )plugins
用于判断各个WAF
的指纹 ( important )
aliyundun.py
阿里云盾的特征值匹配文件huaweicloud.py
华为云的特征值匹配文件baidu.py
百度云加速的特征匹配文件还有很多特征文件大家自己去看就好
__init__.py
main.py
该工具主要的检测、判断功能的类与函数都在该文件中实现( important )
manager.py
加载plugins
中的WAF
指纹判断文件识别目标WAF
类型
wafprio.py
上述非important
的文件起到的作用多是一些起到 输出选项、连接 等功能的文件/函数,所以不做特殊介绍
请求的流程并不复杂,和最开始介绍处的流程如出一辙:
HTTP
请求,并判断是否存在WAF
若存在根据指纹判断其类型HTTP
检测不出WAF
,则附加恶意的请求尝试出发WAF
并分析其类型最后所谓的根据算法猜测,流量特征不明显,不在此次分析的范围内,实际用处也不大。
以第一次的正常
HTTP
请求探测为例(其余的都一致)。
由于对 Python 并不是太了解,所以作用域的表示采用了C++
中的::
(若是不对请及时指出会做更改)。
main.py
分析编辑完整的源码就不放在这占地方了,大家随时可到其github
主页获取,此处直接分析其重点部分
由上述流程可以看出,所有的请求均是从class WAFW00F
中发起,所以该类就是我们分析的重中之重!
请求的具体实现(比如请求中携带了哪些内容)将在分析evillib.py
文件是分析,此处主要分析何时要发出何种请求。
main()
函数出发看其逻辑:(解析参数等功能将直接略过)没有指定其余参数
对输入的URL
做相关处理后放到target
中传入WAFW00F
进行处理
attacker = WAFW00F(target, debuglevel=options.verbose, path=path,followredirect=options.followredirect, extraheaders=extraheaders,proxies=proxies)
# 若请求没有结果则说明目标网站或本地网络有问题
if attacker.rq is None:
log.error('Site %s appears to be down' % hostname)
continue
由于第一次做的是常规探测,若此处就匹配到了WAF
的指纹则输出结束即可,具体的输出等逻辑不是重点,略过;
若无法分析出其是否存在WAF
或匹配不出WAF
则通过identwaf()
函数进步拼凑恶意参数进行探测;
waf = attacker.identwaf(options.findall)
log.info('Identified WAF: %s' % waf)
进入identwaf()
函数后,便会尝试匹配各个WAF
的特征;
def identwaf(self, findall=False):
detected = list()
try:
self.attackres = self.performCheck(self.centralAttack)
except RequestBlocked:
return detected
for wafvendor in self.checklist:
self.log.info('Checking for %s' % wafvendor)
if self.wafdetections[wafvendor](self):
detected.append(wafvendor)
if not findall:
break
self.knowledge['wafname'] = detected
return detected
这就是基本的流程。
解析来如果还没有确定出WAF
便会进入其自己提供的一个算法,但这并不是我们对流量特征分析所要关注的地方,所以就不探讨了。
下面到了激动人心的时刻,WAFW00F
类中到底是如何实现的呢?
class WAFW00F
一上来就中了大奖:
class WAFW00F(waftoolsengine):
xsstring = ''
sqlistring = "UNION SELECT ALL FROM information_schema AND ' or SLEEP(5) or '"
lfistring = '../../../../etc/passwd'
rcestring = '/bin/cat /etc/passwd; ping 127.0.0.1; curl google.com'
xxestring = ']>&hack; '
WAFW00F
这个工具所要构造拼接的恶意代码在一开始就全部列出了,而且在之后的使用中绝无变化,要构造恶意的请求就是从上述5
个字符串中选择1
个或多个直接使用。
可以看到改用据用于判断是否存在WAF
的语句就以下五类:
UNION SELECT ALL FROM information_schema AND ' or SLEEP(5) or '
../../../../etc/passwd
/bin/cat /etc/passwd; ping 127.0.0.1; curl google.com
]>&hack;
那么有了这些语句,WAFW00F
又该如何拼接呢?
def normalRequest(self):
return self.Request()
def customRequest(self, headers=None):
return self.Request(headers=headers)
def nonExistent(self):
return self.Request(path=self.path + str(random.randrange(100, 999)) + '.html')
def xssAttack(self):
return self.Request(path=self.path, params= {'s': self.xsstring})
def xxeAttack(self):
return self.Request(path=self.path, params= {'s': self.xxestring})
def lfiAttack(self):
return self.Request(path=self.path + self.lfistring)
def centralAttack(self):
return self.Request(path=self.path, params={'a': self.xsstring, 'b': self.sqlistring, 'c': self.lfistring})
def sqliAttack(self):
return self.Request(path=self.path, params= {'s': self.sqlistring})
def oscAttack(self):
return self.Request(path=self.path, params= {'s': self.rcestring})
具体逻辑并不难理解,拿出几种几个来说一下。
1:def xssAttack(self)
def xssAttack(self):
return self.Request(path=self.path, params= {'s': self.xsstring})
例如给一个简单的例子:我们传给wafw00f
的URL
为:http://127.0.0.1:9000
则经过该参数拼接后的请求就为:
http://127.0.0.1:9000/?s=
(URL
编码前 )。
2:def centralAttack(self):
def centralAttack(self):
return self.Request(path=self.path, params={'a': self.xsstring, 'b': self.sqlistring, 'c': self.lfistring})
例如给一个简单的例子:我们传给wafw00f
的URL
为:http://127.0.0.1:9000
则经过该参数拼接后的请求就为:
http://127.0.0.1:9000/?s=&b=UNION SELECT ALL FROM information_schema AND ' or SLEEP(5) or '&c=../../../../etc/passwd
(URL
编码前 )
从前面的流程可以看出,在def identwaf(self, findall=False):
中调用的拼接的语句的方法就是本方法,拼接进三个语句,
其他的逻辑相同。
evillib.py
分析编辑上面只是在上层对要拼接哪些参数进行构造,实际上组合成完整的 HTTP 报文调用requests.get()
进行请求的是在该文件中。
wafw00f
中若没有通过—headers
指定头部的话,会使用自己默认的—headers
这个默认的 headers 就定义在该文件中。
def_headers = {'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'en-US,en;q=0.9',
'DNT' : '1', # Do Not Track request header
'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3770.100 Safari/537.36',
'Upgrade-Insecure-Requests': '1' #
}
默认情况下,接下来通过requests.get()
请求时便会使用该头部,
def Request(self, headers=None, path=None, params={}, delay=0, timeout=7):
try:
time.sleep(delay)
if not headers:
h = self.headers
else: h = headers
req = requests.get(self.target, proxies=self.proxies, headers=h, timeout=timeout,
allow_redirects=self.allowredir, params=params, verify=False)
self.log.info('Request Succeeded')
self.log.debug('Headers: %s\\n' % req.headers)
self.log.debug('Content: %s\\n' % req.content)
self.requestnumber += 1
return req
except requests.exceptions.RequestException as e:
self.log.error('Something went wrong %s' % (e.__str__()))
拿Huawei Cloud WAF
的指纹来说
#!/usr/bin/env python
'''
Copyright (C) 2022, WAFW00F Developers.
See the LICENSE file for copying permission.
'''
NAME = 'Huawei Cloud Firewall (Huawei)'
def is_waf(self):
schemes = [
# 匹配 cookie
self.matchCookie(r'^HWWAFSESID='),
# 匹配 header 中的 Server
self.matchHeader(('Server', r'HuaweiCloudWAF')),
# 匹配 body
self.matchContent(r'hwclouds\\.com'),
self.matchContent(r'hws_security@')
]
if any(i for i in schemes):
return True
return False
CSDN
使用华为云防护
其余脚本见:wafw00f/wafw00f/plugins at master · EnableSecurity/wafw00f