wafw00f源码浅析

本文详细剖析了WAFW00F这款知名WAF识别工具的源码,包括其工作流程、参数解析、请求处理、WAF检测机制等。作者指出其代码质量优秀,但未使用高性能技术,并提出了可能的优化方向,如规则格式转换和并发处理。此外,作者还分享了基于WAFW00F的Go语言重实现项目。
摘要由CSDN通过智能技术生成

公粽号:黒掌
一个专注于分享网络安全、黑客圈热点、黑客工具技术区博主!

简介

https://github.com/EnableSecurity/wafw00f/
Wafw00f是一款知名的WAF识别工具,目前Github有2300的Star,由Python编写。简单测试了几个网站,发现识别效果不是那么好

目录分析

整体

img

  • docs:sphinx自动生成文档的工作
  • wafw00f:代码
  • setup.py:安装脚本

wafw00f

img

  • bin:启动文件,执行这里的脚本即可启动程序
  • lib:asciiarts是logo,evillib是发请求的工具类,后续分析
  • plugins:指纹识别的规则,后续分析
  • main:核心代码
  • manager:管理规则的脚本
  • wafprio:指纹识别优先级规定

代码分析

首先看一下plugins目录下的规则文件,每一个都是py文件,并且都有函数is_waf

def is_waf(self):
    schemes = [
        self.matchHeader(('aeSecure-code', '.+?')),
        self.matchContent(r'aesecure_denied\.png')
    ]
    if any(i for i in schemes):
        return True
    return False

比如以上这个规则,是匹配响应头和响应Body中的关键字

正是分析代码,按照一般的规则,先从main.py分析
一开始是常见的参数解析:
parser = OptionParser(usage='%prog url1 [url2 [url3 ... ]]\r\nexample: %prog http://www.victim.org/')

338行print(randomArt())调用asciiarts.py打印LOGO

如果是-l参数那么就打印所有可用的waf,从wafprio文件中读取:

if options.list:
    ......
    try:
        m = [i.replace(')', '').split(' (') for i in wafdetectionsprio]
        print(R+'  WAF Name'+' '*24+'Manufacturer\n  '+'-'*8+' '*24+'-'*12+'\n')
        max_len = max(len(str(x)) for k in m for x in k)
        ......

如果是-v参数那么就打印版本信息:

print('[+] The version of WAFW00F you have is %sv%s%s' % (B, __version__, E))

如果有自定义请求头,那么就尝试从指定的文件中解析请求头,getheaders函数较简单

extraheaders = getheaders(options.headers)

如果输入是一个完整的文件,尝试JSON解析:

with open(options.input) as f:
    try:
        urls = json.loads(f.read())
    except json.decoder.JSONDecodeError:
        log.critical("JSON file %s did not contain well-formed JSON", options.input)
        sys.exit(1)

另外也支持CSV文件和最基础的txt列表格式:

elif options.input.endswith('.csv'):
    columns = defaultdict(list)
    with open(options.input) as f:
        reader = csv.DictReader(f)
        for row in reader:
            for (k,v) in row.items():
                columns[k].append(v)
    targets = columns['url']
else:
    with open(options.input) as f:
        targets = [x for x in f.read().splitlines()]

然后进行了URL验证,比如是否HTTP开头,代理信息处理等操作,创建一个核心类WAF00F,并发请求:

attacker = WAFW00F(target, debuglevel=options.verbose, path=path,
            followredirect=options.followredirect, extraheaders=extraheaders,
                proxies=proxies)
global rq
rq = attacker.normalRequest()

WAF00F类在开头定义了五个Payload,为了触发WAF而设计:

xsstring = '<script>alert("XSS");</script>'
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 = '<!ENTITY xxe SYSTEM "file:///etc/shadow">]><pwn>&hack;</pwn>'

上面调用的normalRequest函数使用了evillib文件,简单的requests库使用:

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)

拿到响应后,就要进行WAF探测的内容了。options.findall是传入的参数-a,代表你想测试所有的WAF,不因为探测到某一种而停止,这里作为一个布尔参数传入:

waf = attacker.identwaf(options.findall)

identwaf开头又调用了performCheck函数,这个函数作用是执行它的参数(函数传参),所以需要关心的是centralAttack是什么:

try:
    self.attackres = self.performCheck(self.centralAttack)
except RequestBlocked:
    return detected

可以看到是发请求,参数是上面的payload,为了触发WAF:

return self.Request(path=self.path, params={'a': self.xsstring, 'b': self.sqlistring, 'c': self.lfistring})

继续回到上层,如果响应为空,会抛出异常,返回检测失败;如果返回正常,那么会从上文提到的指纹识别优先级规定文件wafprio中读取并逐个检测,如果有findall表示就会一直执行:

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

wafdetections的代码如下,load_plugins是上文提到manager.py文件中的函数,加载所有插件进行检测,每一个规则都有is_waf函数,记录了规则匹配方式;最后求差集确保添加到checklist中,也就是上文的指纹识别优先级规定文件:

wafdetections = dict()

plugin_dict = load_plugins()
result_dict = {}
for plugin_module in plugin_dict.values():
    wafdetections[plugin_module.NAME] = plugin_module.is_waf
# Check for prioritized ones first, then check those added externally
checklist = wafdetectionsprio
checklist += list(set(wafdetections.keys()) - set(checklist))

注意一个写法值得思考,这里的wafdetections不是一个字典吗?使用索引[]得到某一个item后,为什么要在后面加一个(self)呢?

if self.wafdetections[wafvendor](self):

要回答这个问题其实很简单,因为这个字典中保存的是key=string;value=function这样的数据,item的值就是函数,这时候可以使用wafdetections[key](param)的方式调用函数,而这个函数是什么呢?就是规则文件的is_waf函数:

def is_waf(self):
    schemes = [
        self.matchHeader(('aeSecure-code', '.+?')),
        self.matchContent(r'aesecure_denied\.png')
    ]
    if any(i for i in schemes):
        return True
    return False

而规则文件调用了self.matchHeaderself.matchContent函数,所以我们应该查看WAFW00F类的这两个函数,因为参数self就是WAFW00F类的this指针

matchHeader函数如下:解析响应头,处理Cookie,然后正则匹配,比较简单

def matchHeader(self, headermatch, attack=False):
    if attack:
        r = self.attackres
    else: r = rq
    if r is None:
        return
    header, match = headermatch
    headerval = r.headers.get(header)
    if headerval:
        # set-cookie can have multiple headers, python gives it to us
        # concatinated with a comma
        if header == 'Set-Cookie':
            headervals = headerval.split(', ')
        else:
            headervals = [headerval]
        for headerval in headervals:
            if re.search(match, headerval, re.I):
                return True
    return False

matchContent函数也是这样的原理,正则匹配响应Body:

def matchContent(self, regex, attack=True):
    if attack:
        r = self.attackres
    else: r = rq
    if r is None:
        return
    # We may need to match multiline context in response body
    if re.search(regex, r.text, re.I):
        return True
    return False

层层跳出,回到最开始的地方

waf = attacker.identwaf(options.findall)

当没有匹配到任何一个WAF的时候,会执行这样的代码:

if attacker.genericdetect():
    log.info('Generic Detection: %s' % attacker.knowledge['generic']['reason'])
    print('[*] The site %s seems to be behind a WAF or some sort of security solution' % target)
    print('[~] Reason: %s' % attacker.knowledge['generic']['reason'])
    results.append(buildResultRecord(target, 'generic'))
else:
    print('[-] No WAF detected by the generic detection')
    results.append(buildResultRecord(target, None))

观察genericdetect函数,先发一个常见的请求,然后发带有payload的请求,然后对比响应码,如果不相等,证明检测到了WAF。也就是说这个函数为了验证某个站点是否是具有WAF的(但是WAFW00F本身并没有识别出来)

resp1 = self.performCheck(self.normalRequest)
......
resp2 = self.performCheck(self.xssAttack)
if resp1.status_code != resp2.status_code:
    return True
......
resp2 = self.performCheck(self.lfiAttack)
......
resp2 = self.performCheck(self.sqliAttack)
......

后续的代码没有什么值得分析之处,将识别结果打印到命令行或者输出文件这样的功能

总结

  • wafw00f有比较优秀的代码质量,丰富的输入输出(JSON、CSV、TXT)
  • 规则用py直接编写,能否改进为JSON,YML等文件
  • 作为一个拥有2300星的python项目,却没有使用任何高性能技术(多线程、多进程、协程)
  • 可以参考它的payload触发waf识别的原理,编写更完善的工具

后续

参考wafw00f的源码,我打算用golang做一下更完善的版本
目前做了个开头:https://github.com/EmYiQing/go-wafw00f

来源:https://xz.aliyun.com/t/9497

作者:4ra1n

活码3D(缩小)

今天的分享就到这里,喜欢的小伙伴记得一键三连,我有一个公粽号【黒掌】, 可以免费获取更多黑客秘籍,欢迎来耍!

WPF应用程序框架(WAF)v2.5.0.7源码 源码描述: WPF应用程序框架(WAF)是一个轻量级的框架,可以帮助您创建结构良好的WPF应用程序。 它支持你在申请一个分层的架构和模型-视图-ViewModel(又名MVVM, M-V-VM, PresentationModel)模式。 特点 WPF应用程序框架(WAF) ViewModel的:包含类型,帮助你实现的Model-View-ViewModel模式。 DataModel的:基类的应用,支持你的DataModel-View - ViewModel模式。 DelegateCommand:DelegateCommand允许你来处理视图比其他类别的WPF命令。 INotifyPropertyChanged的:基类实现INotifyPropertyChanged接口。实施检查中的属性名称的DEBUG模式。 WeakEvent:第一类支持的WPF WeakEvent模式,它可以帮助你避免内存泄漏。 验证:DataErrorInfoSupport类带来的IDataErrorInfo接口与DataAnnotations的验证框架。 ConverterCollection:这个集合是能够保持同步模型的ObservableCollection DataModels。 服务:显示一条消息或打开/保存文件对话框,向用户提供服务。 最近的文件:RecentFileList类提供了最近的文件列表,可以装载和存储在应用程序设置的逻辑。 单元测试扩展 例如:如果一个action结果在一个特殊的exception,可以用ExpectedException方法来测试。 PropertyChanged:提供了一个辅助方法来测试如果一个属性改变事件是当一个特定的行动提出被执行。 CanExecuteChangedEvent:一个helper方法来测试一个CanExecute改变事件是当一个特定的行动提出被执行。 v2507更新信息 图例: [b]打破变化; [O]标记为过时成员 WAF的:添加CollectionHelper.GetNextElementOrDefault方法。 InfoMan:支持创建一个新的电子邮件,并保存在发送框中。 InfoMan:新的电子邮件:选择从地址簿中的电子邮件地址。 InfoMan:显示在导航窗格中的项目数。 InfoMan:支持删除的电子邮件。 InfoMan:加入在Common.Presentation搜索盒的控制和使用,在EmailCli??ent通讯录模块。 InfoMan:设计数据添加到通讯录意见。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值