wafw00f-0.9.4/ 0000775 0001750 0001750 00000000000 12672245714 012426 5 ustar kun kun 0000000 0000000 wafw00f-0.9.4/setup.cfg 0000664 0001750 0001750 00000000073 12672245714 014247 0 ustar kun kun 0000000 0000000 [egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
wafw00f-0.9.4/LICENSE 0000664 0001750 0001750 00000003014 12672245364 013432 0 ustar kun kun 0000000 0000000 Copyright (c) 2016, Sandro Gauci, Enable Security ltd
and Wendel G. Henrique - Trustwave 2009
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the {organization} nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. wafw00f-0.9.4/setup.py 0000664 0001750 0001750 00000001654 12672245364 014147 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
from setuptools import setup, find_packages
setup(
name='wafw00f',
version=__import__('wafw00f').__version__,
description=('WAFW00F identifies and fingerprints '
'Web Application Firewall (WAF) products.'),
author='sandrogauci',
author_email='sandro@enablesecurity.com',
license='BSD License',
url='https://github.com/sandrogauci/wafw00f',
packages=find_packages(),
scripts=['wafw00f/bin/wafw00f'],
install_requires=[
'beautifulsoup4==4.4.1',
'pluginbase==0.3',
],
extras_require={
'dev': [
'prospector==0.10.1',
],
'test': [
'httpretty==0.8.10',
'coverage==3.7.1',
'coveralls==0.5',
'python-coveralls==2.5.0',
'nose==1.3.6',
],
'docs': [
'Sphinx==1.3.1',
],
},
test_suite='nose.collector',
)
wafw00f-0.9.4/MANIFEST.in 0000664 0001750 0001750 00000000202 12672245364 014157 0 ustar kun kun 0000000 0000000 include CREDITS.txt
include LICENSE
include MANIFEST.in
include README.md
include wafw00f/__init__.py
include wafw00f/bin/wafw00f
wafw00f-0.9.4/PKG-INFO 0000664 0001750 0001750 00000000463 12672245714 013526 0 ustar kun kun 0000000 0000000 Metadata-Version: 1.0
Name: wafw00f
Version: 0.9.4
Summary: WAFW00F identifies and fingerprints Web Application Firewall (WAF) products.
Home-page: https://github.com/sandrogauci/wafw00f
Author: sandrogauci
Author-email: sandro@enablesecurity.com
License: BSD License
Description: UNKNOWN
Platform: UNKNOWN
wafw00f-0.9.4/wafw00f.egg-info/ 0000775 0001750 0001750 00000000000 12672245714 015372 5 ustar kun kun 0000000 0000000 wafw00f-0.9.4/wafw00f.egg-info/top_level.txt 0000664 0001750 0001750 00000000010 12672245714 020113 0 ustar kun kun 0000000 0000000 wafw00f
wafw00f-0.9.4/wafw00f.egg-info/SOURCES.txt 0000664 0001750 0001750 00000002702 12672245714 017257 0 ustar kun kun 0000000 0000000 CREDITS.txt
LICENSE
MANIFEST.in
README.md
setup.py
wafw00f/__init__.py
wafw00f/main.py
wafw00f/manager.py
wafw00f.egg-info/PKG-INFO
wafw00f.egg-info/SOURCES.txt
wafw00f.egg-info/dependency_links.txt
wafw00f.egg-info/requires.txt
wafw00f.egg-info/top_level.txt
wafw00f/bin/wafw00f
wafw00f/lib/__init__.py
wafw00f/lib/evillib.py
wafw00f/lib/proxy.py
wafw00f/plugins/__init__.py
wafw00f/plugins/airlock.py
wafw00f/plugins/anquanbao.py
wafw00f/plugins/barracuda.py
wafw00f/plugins/binarysec.py
wafw00f/plugins/chinacache.py
wafw00f/plugins/ciscoacexml.py
wafw00f/plugins/cloudflare.py
wafw00f/plugins/denyall.py
wafw00f/plugins/dotdefender.py
wafw00f/plugins/f5bigipapm.py
wafw00f/plugins/f5bigipasm.py
wafw00f/plugins/f5bigipltm.py
wafw00f/plugins/f5firepass.py
wafw00f/plugins/f5trafficshield.py
wafw00f/plugins/hyperguard.py
wafw00f/plugins/ibm.py
wafw00f/plugins/ibmdatapower.py
wafw00f/plugins/imperva.py
wafw00f/plugins/incapsula.py
wafw00f/plugins/isaserver.py
wafw00f/plugins/missioncontrol.py
wafw00f/plugins/modsecurity.py
wafw00f/plugins/netcontinuum.py
wafw00f/plugins/netscaler.py
wafw00f/plugins/nsfocus.py
wafw00f/plugins/powercdn.py
wafw00f/plugins/profense.py
wafw00f/plugins/safedog.py
wafw00f/plugins/secureiis.py
wafw00f/plugins/teros.py
wafw00f/plugins/urlscan.py
wafw00f/plugins/uspses.py
wafw00f/plugins/webknight.py
wafw00f/plugins/webscurity.py
wafw00f/plugins/west263cdn.py
wafw00f/plugins/wzb360.py
wafw00f/tests/__init__.py
wafw00f/tests/test_main.py wafw00f-0.9.4/wafw00f.egg-info/dependency_links.txt 0000664 0001750 0001750 00000000001 12672245714 021440 0 ustar kun kun 0000000 0000000
wafw00f-0.9.4/wafw00f.egg-info/PKG-INFO 0000664 0001750 0001750 00000000463 12672245714 016472 0 ustar kun kun 0000000 0000000 Metadata-Version: 1.0
Name: wafw00f
Version: 0.9.4
Summary: WAFW00F identifies and fingerprints Web Application Firewall (WAF) products.
Home-page: https://github.com/sandrogauci/wafw00f
Author: sandrogauci
Author-email: sandro@enablesecurity.com
License: BSD License
Description: UNKNOWN
Platform: UNKNOWN
wafw00f-0.9.4/wafw00f.egg-info/requires.txt 0000664 0001750 0001750 00000000262 12672245714 017772 0 ustar kun kun 0000000 0000000 beautifulsoup4==4.4.1
pluginbase==0.3
[test]
httpretty==0.8.10
coverage==3.7.1
coveralls==0.5
python-coveralls==2.5.0
nose==1.3.6
[docs]
Sphinx==1.3.1
[dev]
prospector==0.10.1 wafw00f-0.9.4/README.md 0000664 0001750 0001750 00000005767 12672245364 013725 0 ustar kun kun 0000000 0000000 # WAFW00F
WAFW00F identifies and fingerprints Web Application Firewall (WAF) products.
## How does it work?
To do its magic, WAFW00F does the following:
- Sends a _normal_ HTTP request and analyses the response; this identifies a
number of WAF solutions
- If that is not successful, it sends a number of (potentially malicious) HTTP
requests and uses simple logic to deduce which WAF it is
- 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
For further details, check out the source code on the main site,
[github.com/sandrogauci/wafw00f](https://github.com/sandrogauci/wafw00f).
## What does it detect?
It detects a number of WAFs. To view which WAFs it is able to detect run
WAFW00F with the `-l` option. At the time of writing the output is as follows:
$ ./wafw00f -l
^ ^
_ __ _ ____ _ __ _ _ ____
///7/ /.' \ / __////7/ /,' \ ,' \ / __/
| V V // o // _/ | V V // 0 // 0 // _/
|_n_,'/_n_//_/ |_n_,' \_,' \_,'/_/
<
...'
WAFW00F - Web Application Firewall Detection Tool
By Sandro Gauci && Wendel G. Henrique
Can test for these WAFs:
Profense
NetContinuum
Incapsula WAF
CloudFlare
NSFocus
USP Secure Entry Server
Cisco ACE XML Gateway
Barracuda Application Firewall
Art of Defence HyperGuard
BinarySec
Teros WAF
F5 BIG-IP LTM
F5 BIG-IP APM
F5 BIG-IP ASM
F5 FirePass
F5 Trafficshield
InfoGuard Airlock
Citrix NetScaler
Trustwave ModSecurity
IBM Web Application Security
IBM DataPower
DenyALL WAF
Applicure dotDefender
Juniper WebApp Secure
Microsoft URLScan
Aqtronix WebKnight
eEye Digital Security SecureIIS
Imperva SecureSphere
Microsoft ISA Server
## How do I use it?
For help please make use of the `--help` option. The basic usage is to pass it
a URL as an argument. Example:
$./wafw00f https://www.ibm.com/
^ ^
_ __ _ ____ _ __ _ _ ____
///7/ /.' \ / __////7/ /,' \ ,' \ / __/
| V V // o // _/ | V V // 0 // 0 // _/
|_n_,'/_n_//_/ |_n_,' \_,' \_,'/_/
<
...'
WAFW00F - Web Application Firewall Detection Tool
By Sandro Gauci && Wendel G. Henrique
Checking https://www.ibm.com/
The site https://www.ibm.com/ is behind a Citrix NetScaler
Number of requests: 6
## How do I install it?
The following should do the trick:
python setup.py install
or
pip install wafw00f
## Need a freelance pentester?
More information about the services that I offer at [Enable Security](http://enablesecurity.com/)
## Questions?
Contact [me](mailto:sandro@enablesecurity.com)
wafw00f-0.9.4/wafw00f/ 0000775 0001750 0001750 00000000000 12672245714 013700 5 ustar kun kun 0000000 0000000 wafw00f-0.9.4/wafw00f/bin/ 0000775 0001750 0001750 00000000000 12672245714 014450 5 ustar kun kun 0000000 0000000 wafw00f-0.9.4/wafw00f/bin/wafw00f 0000775 0001750 0001750 00000000136 12672245364 015651 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
from wafw00f import main
if __name__ == '__main__':
main.main()
wafw00f-0.9.4/wafw00f/main.py 0000775 0001750 0001750 00000047574 12672245364 015223 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
# wafw00f - Web Application Firewall Detection Tool
# by Sandro Gauci - enablesecurity.com (c) 2016
# and Wendel G. Henrique - Trustwave 2009
__license__ = """
Copyright (c) 2016, {Sandro Gauci|Wendel G. Henrique}
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of EnableSecurity or Trustwave nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
"""
import os
try:
import httplib
except ImportError:
import http.client as httplib
try:
from urllib import quote, unquote
except ImportError:
from urllib.parse import quote, unquote
from optparse import OptionParser
import logging
import sys
import random
currentDir = os.getcwd()
scriptDir = os.path.dirname(sys.argv[0]) or '.'
os.chdir(scriptDir)
from wafw00f import __version__
from wafw00f.lib.evillib import oururlparse, scrambledheader, waftoolsengine
from wafw00f.manager import load_plugins
lackofart = """
^ ^
_ __ _ ____ _ __ _ _ ____
///7/ /.' \ / __////7/ /,' \ ,' \ / __/
| V V // o // _/ | V V // 0 // 0 // _/
|_n_,'/_n_//_/ |_n_,' \_,' \_,'/_/
<
...'
WAFW00F - Web Application Firewall Detection Tool
By Sandro Gauci && Wendel G. Henrique
"""
class WafW00F(waftoolsengine):
"""
WAF detection tool
"""
AdminFolder = '/Admin_Files/'
xssstring = ''
dirtravstring = '../../../../etc/passwd'
cleanhtmlstring = 'hello'
isaservermatch = [
'Forbidden ( The server denied the specified Uniform Resource Locator (URL). Contact the server administrator. )',
'Forbidden ( The ISA Server denied the specified Uniform Resource Locator (URL)']
def __init__(self, target='www.microsoft.com', port=80, ssl=False,
debuglevel=0, path='/', followredirect=True, extraheaders={}, proxy=False):
"""
target: the hostname or ip of the target server
port: defaults to 80
ssl: defaults to false
"""
self.log = logging.getLogger('wafw00f')
waftoolsengine.__init__(self, target, port, ssl, debuglevel, path, followredirect, extraheaders, proxy)
self.knowledge = dict(generic=dict(found=False, reason=''), wafname=list())
def normalrequest(self, usecache=True, cacheresponse=True, headers=None):
return self.request(usecache=usecache, cacheresponse=cacheresponse, headers=headers)
def normalnonexistentfile(self, usecache=True, cacheresponse=True):
path = self.path + str(random.randrange(1000, 9999)) + '.html'
return self.request(path=path, usecache=usecache, cacheresponse=cacheresponse)
def unknownmethod(self, usecache=True, cacheresponse=True):
return self.request(method='OHYEA', usecache=usecache, cacheresponse=cacheresponse)
def directorytraversal(self, usecache=True, cacheresponse=True):
return self.request(path=self.path + self.dirtravstring, usecache=usecache, cacheresponse=cacheresponse)
def invalidhost(self, usecache=True, cacheresponse=True):
randomnumber = random.randrange(100000, 999999)
return self.request(headers={'Host': str(randomnumber)})
def cleanhtmlencoded(self, usecache=True, cacheresponse=True):
string = self.path + quote(self.cleanhtmlstring) + '.html'
return self.request(path=string, usecache=usecache, cacheresponse=cacheresponse)
def cleanhtml(self, usecache=True, cacheresponse=True):
string = self.path + self.cleanhtmlstring + '.html'
return self.request(path=string, usecache=usecache, cacheresponse=cacheresponse)
def xssstandard(self, usecache=True, cacheresponse=True):
xssstringa = self.path + self.xssstring + '.html'
return self.request(path=xssstringa, usecache=usecache, cacheresponse=cacheresponse)
def protectedfolder(self, usecache=True, cacheresponse=True):
pfstring = self.path + self.AdminFolder
return self.request(path=pfstring, usecache=usecache, cacheresponse=cacheresponse)
def xssstandardencoded(self, usecache=True, cacheresponse=True):
xssstringa = self.path + quote(self.xssstring) + '.html'
return self.request(path=xssstringa, usecache=usecache, cacheresponse=cacheresponse)
def cmddotexe(self, usecache=True, cacheresponse=True):
# thanks j0e
string = self.path + 'cmd.exe'
return self.request(path=string, usecache=usecache, cacheresponse=cacheresponse)
attacks = [cmddotexe, directorytraversal, xssstandard, protectedfolder, xssstandardencoded]
def genericdetect(self, usecache=True, cacheresponse=True):
knownflops = [
('Microsoft-IIS/7.0','Microsoft-HTTPAPI/2.0'),
]
reason = ''
reasons = ['Blocking is being done at connection/packet level.',
'The server header is different when an attack is detected.',
'The server returned a different response code when a string trigged the blacklist.',
'It closed the connection for a normal request.',
'The connection header was scrambled.'
]
# test if response for a path containing html tags with known evil strings
# gives a different response from another containing invalid html tags
r = self.cleanhtml()
if r is None:
self.knowledge['generic']['reason'] = reasons[0]
self.knowledge['generic']['found'] = True
return True
cleanresponse, _tmp = r
r = self.xssstandard()
if r is None:
self.knowledge['generic']['reason'] = reasons[0]
self.knowledge['generic']['found'] = True
return True
xssresponse, _tmp = r
if xssresponse.status != cleanresponse.status:
self.log.info('Server returned a different response when a script tag was tried')
reason = reasons[2]
reason += '\r\n'
reason += 'Normal response code is "%s",' % cleanresponse.status
reason += ' while the response code to an attack is "%s"' % xssresponse.status
self.knowledge['generic']['reason'] = reason
self.knowledge['generic']['found'] = True
return True
r = self.cleanhtmlencoded()
cleanresponse, _tmp = r
r = self.xssstandardencoded()
if r is None:
self.knowledge['generic']['reason'] = reasons[0]
self.knowledge['generic']['found'] = True
return True
xssresponse, _tmp = r
if xssresponse.status != cleanresponse.status:
self.log.info('Server returned a different response when a script tag was tried')
reason = reasons[2]
reason += '\r\n'
reason += 'Normal response code is "%s",' % cleanresponse.status
reason += ' while the response code to an attack is "%s"' % xssresponse.status
self.knowledge['generic']['reason'] = reason
self.knowledge['generic']['found'] = True
return True
response, responsebody = self.normalrequest()
normalserver = response.getheader('Server')
for attack in self.attacks:
r = attack(self)
if r is None:
self.knowledge['generic']['reason'] = reasons[0]
self.knowledge['generic']['found'] = True
return True
response, responsebody = r
attackresponse_server = response.getheader('Server')
if attackresponse_server:
if attackresponse_server != normalserver:
if (normalserver, attackresponse_server) in knownflops:
return False
self.log.info('Server header changed, WAF possibly detected')
self.log.debug('attack response: %s' % attackresponse_server)
self.log.debug('normal response: %s' % normalserver)
reason = reasons[1]
reason += '\r\nThe server header for a normal response is "%s",' % normalserver
reason += ' while the server header a response to an attack is "%s.",' % attackresponse_server
self.knowledge['generic']['reason'] = reason
self.knowledge['generic']['found'] = True
return True
for attack in self.wafdetectionsprio:
if self.wafdetections[attack](self) is None:
self.knowledge['generic']['reason'] = reasons[0]
self.knowledge['generic']['found'] = True
return True
for attack in self.attacks:
r = attack(self)
if r is None:
self.knowledge['generic']['reason'] = reasons[0]
self.knowledge['generic']['found'] = True
return True
response, responsebody = r
for h, v in response.getheaders():
if scrambledheader(h):
self.knowledge['generic']['reason'] = reasons[4]
self.knowledge['generic']['found'] = True
return True
return False
def matchheader(self, headermatch, attack=False, ignorecase=True):
import re
detected = False
header, match = headermatch
if attack:
requests = self.attacks
else:
requests = [self.normalrequest]
for request in requests:
r = request(self)
if r is None:
return
response, responsebody = r
headerval = response.getheader(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 ignorecase:
if re.match(match, headerval, re.IGNORECASE):
detected = True
break
else:
if re.match(match, headerval):
detected = True
break
if detected:
break
return detected
def matchcookie(self, match):
"""
a convenience function which calls matchheader
"""
return self.matchheader(('set-cookie', match))
def isbeeware(self):
# disabled cause it was giving way too many false positives
# credit goes to Sebastien Gioria
detected = False
r = self.xssstandard()
if r is None:
return
response, responsebody = r
if (response.status != 200) or (response.reason == 'Forbidden'):
r = self.directorytraversal()
if r is None:
return
response, responsebody = r
if response.status == 403:
if response.reason == 'Forbidden':
detected = True
return detected
def ismodsecuritypositive(self):
detected = False
self.normalrequest(usecache=False, cacheresponse=False)
randomfn = self.path + str(random.randrange(1000, 9999)) + '.html'
r = self.request(path=randomfn)
if r is None:
return
response, responsebody = r
if response.status != 302:
return False
randomfnnull = randomfn + '%00'
r = self.request(path=randomfnnull)
if r is None:
return
response, responsebody = r
if response.status == 404:
detected = True
return detected
wafdetections = dict()
# easy ones
# lil bit more complex
#wafdetections['BeeWare'] = isbeeware
#wafdetections['ModSecurity (positive model)'] = ismodsecuritypositive removed for now
wafdetectionsprio = ['Profense', 'NetContinuum', 'Incapsula WAF', 'CloudFlare', 'NSFocus', 'Safedog',
'Mission Control Application Shield', 'USP Secure Entry Server', 'Cisco ACE XML Gateway',
'Barracuda Application Firewall', 'Art of Defence HyperGuard', 'BinarySec', 'Teros WAF',
'F5 BIG-IP LTM', 'F5 BIG-IP APM', 'F5 BIG-IP ASM', 'F5 FirePass', 'F5 Trafficshield',
'InfoGuard Airlock', 'Citrix NetScaler',
'Trustwave ModSecurity', 'IBM Web Application Security', 'IBM DataPower', 'DenyALL WAF',
'Applicure dotDefender', 'Juniper WebApp Secure', # removed for now 'ModSecurity (positive model)',
'Microsoft URLScan', 'Aqtronix WebKnight',
'eEye Digital Security SecureIIS', 'Imperva SecureSphere', 'Microsoft ISA Server']
plugin_dict = load_plugins()
result_dict = {}
for plugin_module in plugin_dict.values():
wafdetections[plugin_module.NAME] = plugin_module.is_waf
def identwaf(self, findall=False):
detected = list()
for wafvendor in self.wafdetectionsprio:
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
def calclogginglevel(verbosity):
default = 40 # errors are printed out
level = default - (verbosity * 10)
if level < 0:
level = 0
return level
def getheaders(fn):
headers = {}
fullfn = os.path.abspath(os.path.join(os.getcwd(),fn))
if not os.path.exists(fullfn):
logging.getLogger('wafw00f').critical('Headers file "%s" does not exist!'%fullfn)
return
with open(fn,'r') as f:
for line in f.readlines():
_t = line.split(':',2)
if len(_t) == 2:
h,v = map(lambda x: x.strip(),_t)
headers[h] = v
return headers
def main():
print(lackofart)
parser = OptionParser(usage='%prog url1 [url2 [url3 ... ]]\r\nexample: %prog http://www.victim.org/')
parser.add_option('-v', '--verbose', action='count', dest='verbose', default=0,
help='enable verbosity - multiple -v options increase verbosity')
parser.add_option('-a', '--findall', action='store_true', dest='findall', default=False,
help='Find all WAFs, do not stop testing on the first one')
parser.add_option('-r', '--disableredirect', action='store_false', dest='followredirect',
default=True, help='Do not follow redirections given by 3xx responses')
parser.add_option('-t', '--test', dest='test',
help='Test for one specific WAF')
parser.add_option('-l', '--list', dest='list', action='store_true',
default=False, help='List all WAFs that we are able to detect')
parser.add_option('-p', '--proxy', dest='proxy',
default=False, help='Use an HTTP proxy to perform requests, example: http://hostname:8080, socks5://hostname:1080')
parser.add_option('--version', '-V', dest='version', action='store_true',
default=False, help='Print out the version')
parser.add_option('--headersfile', '-H', dest='headersfile', action='store',
default=None, help='Pass custom headers, for example to overwrite the default User-Agent string')
options, args = parser.parse_args()
logging.basicConfig(level=calclogginglevel(options.verbose))
log = logging.getLogger()
if options.list:
print('Can test for these WAFs:\r\n')
attacker = WafW00F(None)
print('\r\n'.join(attacker.wafdetectionsprio))
return
if options.version:
print('WAFW00F version %s' % __version__)
return
extraheaders = {}
if options.headersfile:
log.info('Getting extra headers from %s' % options.headersfile)
extraheaders = getheaders(options.headersfile)
if extraheaders is None:
parser.error('Please provide a headers file with colon delimited header names and values')
if len(args) == 0:
parser.error('we need a target site')
targets = args
for target in targets:
if not (target.startswith('http://') or target.startswith('https://')):
log.info('The url %s should start with http:// or https:// .. fixing (might make this unusable)' % target)
target = 'http://' + target
print('Checking %s' % target)
pret = oururlparse(target)
if pret is None:
log.critical('The url %s is not well formed' % target)
sys.exit(1)
(hostname, port, path, query, ssl) = pret
log.info('starting wafw00f on %s' % target)
attacker = WafW00F(hostname, port=port, ssl=ssl,
debuglevel=options.verbose, path=path,
followredirect=options.followredirect,
extraheaders=extraheaders,
proxy=options.proxy)
if attacker.normalrequest() is None:
log.error('Site %s appears to be down' % target)
sys.exit(1)
if options.test:
if options.test in attacker.wafdetections:
waf = attacker.wafdetections[options.test](attacker)
if waf:
print('The site %s is behind a %s' % (target, options.test))
else:
print('WAF %s was not detected on %s' % (options.test, target))
else:
print(
'WAF %s was not found in our list\r\nUse the --list option to see what is available' % options.test)
return
waf = attacker.identwaf(options.findall)
log.info('Ident WAF: %s' % waf)
if len(waf) > 0:
print('The site %s is behind a %s' % (target, ' and/or '.join(waf)))
if (options.findall) or len(waf) == 0:
print('Generic Detection results:')
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'])
else:
print('No WAF detected by the generic detection')
print('Number of requests: %s' % attacker.requestnumber)
if __name__ == '__main__':
if sys.hexversion < 0x2060000:
sys.stderr.write('Your version of python is way too old .. please update to 2.6 or later\r\n')
main()
wafw00f-0.9.4/wafw00f/plugins/ 0000775 0001750 0001750 00000000000 12672245714 015361 5 ustar kun kun 0000000 0000000 wafw00f-0.9.4/wafw00f/plugins/west263cdn.py 0000664 0001750 0001750 00000000172 12672245364 017636 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'West263CDN'
def is_waf(self):
return self.matchheader(('X-Cache', '.+WT263CDN-.+'))
wafw00f-0.9.4/wafw00f/plugins/webscurity.py 0000664 0001750 0001750 00000000705 12672245364 020136 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'Juniper WebApp Secure'
def is_waf(self):
detected = False
r = self.normalrequest()
if r is None:
return
response, responsebody = r
if response.status == 403:
return detected
newpath = self.path + '?nx=@@'
r = self.request(path=newpath)
if r is None:
return
response, responsebody = r
if response.status == 403:
detected = True
return detected
wafw00f-0.9.4/wafw00f/plugins/imperva.py 0000664 0001750 0001750 00000000701 12672245364 017375 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'Imperva SecureSphere'
def is_waf(self):
# thanks to Mathieu Dessus for this
# might lead to false positives so please report back to sandro@enablesecurity.com
for attack in self.attacks:
r = attack(self)
if r is None:
return
response, responsebody = r
if response.version == 10:
return True
return False
wafw00f-0.9.4/wafw00f/plugins/teros.py 0000664 0001750 0001750 00000000200 12672245364 017060 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'Teros WAF'
def is_waf(self):
# credit goes to W3AF
return self.matchcookie('^st8id=')
wafw00f-0.9.4/wafw00f/plugins/incapsula.py 0000664 0001750 0001750 00000000360 12672245364 017712 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'Incapsula WAF'
def is_waf(self):
# credit goes to Charlie Campbell
if self.matchcookie('^.incap_ses'):
return True
if self.matchcookie('^visid.*='):
return True
return False
wafw00f-0.9.4/wafw00f/plugins/anquanbao.py 0000664 0001750 0001750 00000000175 12672245364 017704 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'Anquanbao'
def is_waf(self):
return self.matchheader(('X-Powered-By-Anquanbao', '.+'))
wafw00f-0.9.4/wafw00f/plugins/ibm.py 0000664 0001750 0001750 00000000304 12672245364 016500 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'IBM Web Application Security'
def is_waf(self):
detected = False
r = self.protectedfolder()
if r is None:
detected = True
return detected
wafw00f-0.9.4/wafw00f/plugins/ibmdatapower.py 0000664 0001750 0001750 00000000412 12672245364 020407 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'IBM DataPower'
def is_waf(self):
# Added by Mathieu Dessus
detected = False
if self.matchheader(('X-Backside-Transport', '^(OK|FAIL)')):
detected = True
return detected
wafw00f-0.9.4/wafw00f/plugins/wzb360.py 0000664 0001750 0001750 00000000177 12672245364 016774 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = '360WangZhanBao'
def is_waf(self):
return self.matchheader(('X-Powered-By-360WZB', '.+'))
wafw00f-0.9.4/wafw00f/plugins/modsecurity.py 0000664 0001750 0001750 00000000744 12672245364 020310 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'Trustwave ModSecurity'
def is_waf(self):
detected = False
for attack in self.attacks:
r = attack(self)
if r is None:
return
response, responsebody = r
if response.status == 501:
detected = True
break
# the following based on nmap's http-waf-fingerprint.nse
if self.matchheader(('server', '(mod_security|Mod_Security|NOYB)')):
return True
return detected
wafw00f-0.9.4/wafw00f/plugins/cloudflare.py 0000664 0001750 0001750 00000000420 12672245364 020050 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'CloudFlare'
# the following based on nmap's http-waf-fingerprint.nse
def is_waf(self):
if self.matchheader(('server', 'cloudflare-nginx')):
return True
if self.matchcookie('__cfduid'):
return True
return False
wafw00f-0.9.4/wafw00f/plugins/netcontinuum.py 0000664 0001750 0001750 00000000214 12672245364 020461 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'NetContinuum'
def is_waf(self):
# credit goes to W3AF
return self.matchcookie('^NCI__SessionId=')
wafw00f-0.9.4/wafw00f/plugins/binarysec.py 0000664 0001750 0001750 00000000716 12672245364 017717 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'BinarySec'
def is_waf(self):
# credit goes to W3AF
if self.matchheader(('server', 'BinarySec')):
return True
# the following based on nmap's http-waf-fingerprint.nse
elif self.matchheader(('x-binarysec-via', '.')):
return True
# the following based on nmap's http-waf-fingerprint.nse
elif self.matchheader(('x-binarysec-nocache', '.')):
return True
else:
return False
wafw00f-0.9.4/wafw00f/plugins/chinacache.py 0000664 0001750 0001750 00000000201 12672245364 017773 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'ChinaCache-CDN'
def is_waf(self):
return self.matchheader(('Powered-By-ChinaCache', '.+'))
wafw00f-0.9.4/wafw00f/plugins/isaserver.py 0000664 0001750 0001750 00000000425 12672245364 017740 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'Microsoft ISA Server'
def is_waf(self):
detected = False
r = self.invalidhost()
if r is None:
return
response, responsebody = r
if response.reason in self.isaservermatch:
detected = True
return detected
wafw00f-0.9.4/wafw00f/plugins/hyperguard.py 0000664 0001750 0001750 00000000225 12672245364 020105 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'Art of Defence HyperGuard'
def is_waf(self):
# credit goes to W3AF
return self.matchcookie('^WODSESSION=')
wafw00f-0.9.4/wafw00f/plugins/barracuda.py 0000664 0001750 0001750 00000000731 12672245364 017661 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'Barracuda Application Firewall'
def is_waf(self):
# credit goes to W3AF
if self.matchcookie('^barra_counter_session='):
return True
# credit goes to Charlie Campbell
if self.matchcookie('^BNI__BARRACUDA_LB_COOKIE='):
return True
# credit goes to yours truly
if self.matchcookie('^BNI_persistence='):
return True
if self.matchcookie('^BN[IE]S_.*?='):
return True
return False
wafw00f-0.9.4/wafw00f/plugins/airlock.py 0000664 0001750 0001750 00000000223 12672245364 017355 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'InfoGuard Airlock'
def is_waf(self):
# credit goes to W3AF
return self.matchcookie('^AL[_-]?(SESS|LB)=')
wafw00f-0.9.4/wafw00f/plugins/denyall.py 0000664 0001750 0001750 00000001047 12672245364 017366 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'DenyALL WAF'
def is_waf(self):
# credit goes to W3AF
if self.matchcookie('^sessioncookie='):
return True
# credit goes to Sebastien Gioria
# Tested against a Rweb 3.8
# and modified by sandro gauci and someone else
for attack in self.attacks:
r = attack(self)
if r is None:
return
response, responsebody = r
if response.status == 200:
if response.reason == 'Condition Intercepted':
return True
return False
wafw00f-0.9.4/wafw00f/plugins/ciscoacexml.py 0000664 0001750 0001750 00000000250 12672245364 020223 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'Cisco ACE XML Gateway'
def is_waf(self):
if self.matchheader(('server', 'ACE XML Gateway')):
return True
return False
wafw00f-0.9.4/wafw00f/plugins/safedog.py 0000664 0001750 0001750 00000000445 12672245364 017347 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'Safedog'
def is_waf(self):
if self.matchcookie('^safedog-flow-item='):
return True
if self.matchheader(('server', '^Safedog')):
return True
if self.matchheader(('x-powered-by', '^WAF/\d\.\d')):
return True
return False
wafw00f-0.9.4/wafw00f/plugins/f5bigipasm.py 0000664 0001750 0001750 00000000221 12672245364 017755 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'F5 BIG-IP ASM'
def is_waf(self):
# credit goes to W3AF
return self.matchcookie('^TS[a-zA-Z0-9]{3,8}=')
wafw00f-0.9.4/wafw00f/plugins/dotdefender.py 0000664 0001750 0001750 00000000251 12672245364 020215 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'Applicure dotDefender'
def is_waf(self):
# thanks to j0e
return self.matchheader(['X-dotDefender-denied', '^1$'], attack=True)
wafw00f-0.9.4/wafw00f/plugins/missioncontrol.py 0000664 0001750 0001750 00000000310 12672245364 021010 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'Mission Control Application Shield'
def is_waf(self):
if self.matchheader(('server', 'Mission Control Application Shield')):
return True
return False
wafw00f-0.9.4/wafw00f/plugins/__init__.py 0000664 0001750 0001750 00000000000 12672245364 017461 0 ustar kun kun 0000000 0000000 wafw00f-0.9.4/wafw00f/plugins/f5bigipltm.py 0000664 0001750 0001750 00000000413 12672245364 017774 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'F5 BIG-IP LTM'
def is_waf(self):
detected = False
if self.matchcookie('^BIGipServer'):
return True
elif self.matchheader(('X-Cnection', '^close$'), attack=True):
return True
else:
return False
wafw00f-0.9.4/wafw00f/plugins/uspses.py 0000664 0001750 0001750 00000000256 12672245364 017261 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'USP Secure Entry Server'
def is_waf(self):
if self.matchheader(('server', 'Secure Entry Server')):
return True
return False
wafw00f-0.9.4/wafw00f/plugins/nsfocus.py 0000664 0001750 0001750 00000000222 12672245364 017410 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'NSFocus'
def is_waf(self):
if self.matchheader(('server', 'NSFocus')):
return True
return False
wafw00f-0.9.4/wafw00f/plugins/urlscan.py 0000664 0001750 0001750 00000001125 12672245364 017402 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'Microsoft URLScan'
def is_waf(self):
detected = False
testheaders = dict()
testheaders['Translate'] = 'z' * 10
testheaders['If'] = 'z' * 10
testheaders['Lock-Token'] = 'z' * 10
testheaders['Transfer-Encoding'] = 'z' * 10
r = self.normalrequest()
if r is None:
return
response, _tmp = r
r = self.normalrequest(headers=testheaders)
if r is None:
return
response2, _tmp = r
if response.status != response2.status:
if response2.status == 404:
detected = True
return detected
wafw00f-0.9.4/wafw00f/plugins/profense.py 0000664 0001750 0001750 00000000266 12672245364 017561 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'Profense'
def is_waf(self):
"""
Checks for server headers containing "profense"
"""
return self.matchheader(('server', 'profense'))
wafw00f-0.9.4/wafw00f/plugins/webknight.py 0000664 0001750 0001750 00000000507 12672245364 017720 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'Aqtronix WebKnight'
def is_waf(self):
detected = False
for attack in self.attacks:
r = attack(self)
if r is None:
return
response, responsebody = r
if response.status == 999:
detected = True
break
return detected
wafw00f-0.9.4/wafw00f/plugins/f5trafficshield.py 0000664 0001750 0001750 00000000630 12672245364 020775 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'F5 Trafficshield'
def is_waf(self):
for hv in [['cookie', '^ASINFO='], ['server', 'F5-TrafficShield']]:
r = self.matchheader(hv)
if r is None:
return
elif r:
return r
# the following based on nmap's http-waf-fingerprint.nse
if self.matchheader(('server', 'F5-TrafficShield')):
return True
return False
wafw00f-0.9.4/wafw00f/plugins/powercdn.py 0000664 0001750 0001750 00000000156 12672245364 017557 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'PowerCDN'
def is_waf(self):
return self.matchheader(('PowerCDN', '.+'))
wafw00f-0.9.4/wafw00f/plugins/f5bigipapm.py 0000664 0001750 0001750 00000002201 12672245364 017752 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'F5 BIG-IP APM'
def is_waf(self):
detected = False
# the following based on nmap's http-waf-fingerprint.nse
if self.matchcookie('^LastMRH_Session') and self.matchcookie('^MRHSession'):
return True
elif self.matchheader(('server', 'BigIP|BIG-IP|BIGIP')) and self.matchcookie('^MRHSession'):
return True
if self.matchheader(('Location', '\/my.policy')) and self.matchheader(('server', 'BigIP|BIG-IP|BIGIP')):
return True
elif self.matchheader(('Location', '\/my\.logout\.php3')) and self.matchheader(('server', 'BigIP|BIG-IP|BIGIP')):
return True
elif self.matchheader(('Location', '.+\/f5\-w\-68747470.+')) and self.matchheader(('server', 'BigIP|BIG-IP|BIGIP')):
return True
elif self.matchheader(('server', 'BigIP|BIG-IP|BIGIP')):
return True
elif self.matchcookie('^F5_fullWT') or self.matchcookie('^F5_ST') or self.matchcookie('^F5_HT_shrinked'):
return True
elif self.matchcookie('^MRHSequence') or self.matchcookie('^MRHSHint') or self.matchcookie('^LastMRH_Session'):
return True
else:
return False
wafw00f-0.9.4/wafw00f/plugins/secureiis.py 0000664 0001750 0001750 00000001011 12672245364 017720 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'eEye Digital Security SecureIIS'
def is_waf(self):
# credit goes to W3AF
detected = False
r = self.normalrequest()
if r is None:
return
response, responsebody = r
if response.status == 404:
return
headers = dict()
headers['Transfer-Encoding'] = 'z' * 1025
r = self.normalrequest(headers=headers)
if r is None:
return
response, responsebody = r
if response.status == 404:
detected = True
return detected
wafw00f-0.9.4/wafw00f/plugins/netscaler.py 0000664 0001750 0001750 00000001603 12672245364 017714 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'Citrix NetScaler'
def is_waf(self):
"""
First checks if a cookie associated with Netscaler is present,
if not it will try to find if a "Cneonction" or "nnCoection" is returned
for any of the attacks sent
"""
# NSC_ and citrix_ns_id come from David S. Langlands
if self.matchcookie('^(ns_af=|citrix_ns_id|NSC_)'):
return True
if self.matchheader(('Cneonction', 'close'), attack=True):
return True
if self.matchheader(('nnCoection', 'close'), attack=True):
return True
if self.matchheader(('Via', 'NS-CACHE'), attack=True):
return True
if self.matchheader(('x-client-ip', '.'), attack=True):
return True
if self.matchheader(('Location', '\/vpn\/index\.html')):
return True
if self.matchcookie('^pwcount'):
return True
return False
wafw00f-0.9.4/wafw00f/plugins/f5firepass.py 0000664 0001750 0001750 00000001127 12672245364 020004 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
NAME = 'F5 FirePass'
def is_waf(self):
detected = False
if self.matchheader(('Location', '\/my\.logon\.php3')) and self.matchcookie('^VHOST'):
return True
elif self.matchcookie('^MRHSession') and (self.matchcookie('^VHOST') or self.matchcookie('^uRoamTestCookie')):
return True
elif self.matchcookie('^MRHSession') and (self.matchcookie('^MRHCId') or self.matchcookie('^MRHIntranetSession')):
return True
elif self.matchcookie('^uRoamTestCookie') or self.matchcookie('^VHOST'):
return True
else:
return False
wafw00f-0.9.4/wafw00f/lib/ 0000775 0001750 0001750 00000000000 12672245714 014446 5 ustar kun kun 0000000 0000000 wafw00f-0.9.4/wafw00f/lib/evillib.py 0000775 0001750 0001750 00000040130 12672245364 016450 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
import re
import sys
import socket
try:
from urlparse import urlparse, urlunparse
except ImportError:
from urllib.parse import urlparse, urlunparse
import logging
try:
from bs4 import BeautifulSoup
except ImportError:
sys.stderr.write('You need to get BeautifulSoup installed\n')
sys.stderr.write('Do it now, as privileged user/root run: pip install beautifulsoup4\now')
sys.exit(1)
from .proxy import NullProxy, HttpProxy, Socks5Proxy, httplib, socks
__license__ = """
Copyright (c) 2016, {Sandro Gauci|Wendel G. Henrique}
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of EnableSecurity or Trustwave nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
"""
# unicode mapping borrowed from http://packetstormsecurity.org/web/unicode-fun.txt
# by Gary O'leary-Steele of Sec-1 Ltd
try:
from urllib import quote, unquote
except ImportError:
from urllib.parse import quote, unquote
unicodemapping = {' ': '%u0020',
'/': '%u2215',
'\\': '%u2215',
"'": '%u02b9',
'"': '%u0022',
'>': '%u003e',
'<': '%u003c',
'#': '%uff03',
'!': '%uff01',
'$': '%uff04',
'*': '%uff0a',
'@': '%u0040',
'.': '%uff0e',
'_': '%uff3f',
'(': '%uff08',
')': '%uff09',
',': '%uff0c',
'%': '%u0025',
'-': '%uff0d',
';': '%uff1b',
':': '%uff1a',
'|': '%uff5c',
'&': '%uff06',
'+': '%uff0b',
'=': '%uff1d',
'a': '%uff41',
'A': '%uff21',
'b': '%uff42',
'B': '%uff22',
'c': '%uff43',
'C': '%uff23',
'd': '%uff44',
'D': '%uff24',
'e': '%uff45',
'E': '%uff25',
'f': '%uff46',
'F': '%uff26',
'g': '%uff47',
'G': '%uff27',
'h': '%uff48',
'H': '%uff28',
'i': '%uff49',
'I': '%uff29',
'j': '%uff4a',
'J': '%uff2a',
'k': '%uff4b',
'K': '%uff2b',
'l': '%uff4c',
'L': '%uff2c',
'm': '%uff4d',
'M': '%uff2d',
'n': '%uff4e',
'N': '%uff2e',
'o': '%uff4f',
'O': '%uff2f',
'p': '%uff50',
'P': '%uff30',
'q': '%uff51',
'Q': '%uff31',
'r': '%uff52',
'R': '%uff32',
's': '%uff53',
'S': '%uff33',
't': '%uff54',
'T': '%uff34',
'u': '%uff55',
'U': '%uff35',
'v': '%uff56',
'V': '%uff36',
'w': '%uff57',
'W': '%uff37',
'x': '%uff58',
'X': '%uff38',
'y': '%uff59',
'Y': '%uff39',
'z': '%uff5a',
'Z': '%uff3a',
'0': '%uff10',
'1': '%uff11',
'2': '%uff12',
'3': '%uff13',
'4': '%uff14',
'5': '%uff15',
'6': '%uff16',
'7': '%uff17',
'8': '%uff18',
'9': '%uff19'}
homoglyphicmapping = {"'": '%ca%bc'}
def oururlparse(target):
log = logging.getLogger('urlparser')
ssl = False
o = urlparse(target)
if o[0] not in ['http', 'https', '']:
log.error('scheme %s not supported' % o[0])
return
if o[0] == 'https':
ssl = True
if len(o[2]) > 0:
path = o[2]
else:
path = '/'
tmp = o[1].split(':')
if len(tmp) > 1:
port = tmp[1]
else:
port = None
hostname = tmp[0]
query = o[4]
return (hostname, port, path, query, ssl)
def modifyurl(path, modfunc, log):
path = path
log.debug('path is currently %s' % path)
#s = re.search('(\[.*?\])',path)
for m in re.findall('(\[.*?\])', path):
ourstr = m[1:-1]
newstr = modfunc(ourstr)
log.debug('String was %s' % ourstr)
log.debug('String became %s' % newstr)
path = path.replace(m, newstr)
log.debug('the path is now %s' % path)
return path
def modifypath(path, newstrs, log, encode=True):
log.debug('path is currently %s' % path)
for m in re.findall('(\[.*?\])', path):
ourstr = m[1:-1]
for newstr in newstrs:
if encode:
newstr = quote(newstr)
log.debug('String was %s' % ourstr)
log.debug('String became %s' % newstr)
newpath = path.replace(m, newstr).replace(']', '').replace('[', '')
yield (newpath)
def bruteforceascii(ourstr):
listourstr = list(ourstr)
for pos in range(len(ourstr)):
for i in range(256):
newlistourstr = listourstr[:]
newlistourstr[pos] = chr(i)
yield (quote(''.join(newlistourstr)))
def unicodeurlencode(ourstr):
newstr = str()
for character in ourstr:
if unicodemapping.has_key(character):
newstr += unicodemapping[character]
else:
newstr += character
return newstr
def nullify(ourstr):
newstr = str()
for character in ourstr:
newstr += character + "\x00"
return quote(newstr)
def replacechars(ourstr, origchar, newchar):
newstr = ourstr.replace(origchar, newchar)
return newstr
def nullifyspaces(ourstr):
return quote(replacechars(ourstr, ' ', '\x00'))
def slashspaces(ourstr):
return replacechars(ourstr, ' ', '/')
def tabifyspaces(ourstr):
return replacechars(ourstr, ' ', '\t')
def crlfspaces(ourstr):
return replacechars(ourstr, ' ', '\n')
def backslashquotes(ourstr):
return replacechars(ourstr, "'", "\''")
class waftoolsengine:
def __init__(self, target='www.microsoft.com', port=80, ssl=False,
debuglevel=0, path='/', followredirect=True, extraheaders={},
proxy=False):
"""
target: the hostname or ip of the target server
port: defaults to 80
ssl: defaults to false
"""
self.target = target
if port is None:
if ssl:
port = 443
else:
port = 80
self.port = int(port)
self.ssl = ssl
self.debuglevel = debuglevel
self.cachedresponses = dict()
self.requestnumber = 0
self.path = path
self.redirectno = 0
self.followredirect = followredirect
self.crawlpaths = list()
self.extraheaders = extraheaders
try:
self.proxy = self._parse_proxy(proxy) if proxy else NullProxy()
except Exception as e:
self.log.critical("Proxy disabled: %s" % e)
self.proxy = NullProxy()
def request(self, method='GET', path=None, usecache=True,
cacheresponse=True, headers=None,
comingfromredir=False):
followredirect = self.followredirect
if comingfromredir:
self.redirectno += 1
if self.redirectno >= 5:
self.log.error('We received way too many redirects.. stopping that')
followredirect = False
else:
self.redirectno = 0
if path is None:
path = self.path
for hdr in self.extraheaders.keys():
if headers is None:
headers = {}
headers[hdr] = self.extraheaders[hdr]
if headers is not None:
knownheaders = map(lambda x: x.lower(), headers.keys())
else:
knownheaders = {}
headers = {}
if not 'user-agent' in knownheaders:
headers[
'User-Agent'] = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b1) Gecko/20081007 Firefox/3.0'
if not 'accept-charset' in knownheaders:
headers['Accept-Charset'] = 'ISO-8859-1,utf-8;q=0.7,*;q=0.7'
if not 'accept' in knownheaders:
headers['Accept'] = '*/*'
k = str([method, path, headers])
if usecache:
if k in self.cachedresponses.keys():
self.log.debug('Using cached version of %s, %s' % (method, path))
return self.cachedresponses[k]
else:
self.log.debug('%s not found in %s' % (k, self.cachedresponses.keys()))
r = self._request(method, path, headers)
if cacheresponse:
self.cachedresponses[k] = r
if r:
response, responsebody = r
if response.status in [301, 302, 307]:
if followredirect:
if response.getheader('location'):
newloc = response.getheader('location')
self.log.info('Redirected to %s' % newloc)
pret = oururlparse(newloc)
if pret is not None:
(target, port, path, query, ssl) = pret
if not port: port = 80
if target == '':
target = self.target
if port is None:
port = self.port
if not path.startswith('/'):
path = '/' + path
if (target, port, ssl) == (self.target, self.port, ssl):
r = self.request(method, path, usecache, cacheresponse,
headers, comingfromredir=True)
else:
self.log.warn('Tried to redirect to a different server %s' % newloc)
else:
self.log.warn('%s is not a well formatted url' % response.getheader('location'))
return r
def _request(self, method, path, headers):
original_socket = socket.socket
try:
conn_factory, connect_host, connect_port, query_path = \
self.proxy.prepare(self.target, self.port, path, self.ssl)
params = dict()
if sys.hexversion > 0x2060000:
params['timeout'] = 4
if (sys.hexversion >= 0x2070900) and self.ssl:
import ssl as ssllib
params['context'] = ssllib._create_unverified_context()
h = conn_factory(connect_host, connect_port,**params)
if self.debuglevel <= 10:
if self.debuglevel > 1:
h.set_debuglevel(self.debuglevel)
try:
self.log.info('Sending %s %s' % (method, path))
h.request(method, query_path, headers=headers)
except socket.error:
self.log.warn('Could not initialize connection to %s' % self.target)
return
self.requestnumber += 1
response = h.getresponse()
responsebody = response.read()
h.close()
r = response, responsebody
except (socket.error, socket.timeout, httplib.BadStatusLine):
self.log.warn('Hey.. they closed our connection!')
r = None
finally:
self.proxy.terminate()
return r
def querycrawler(self, path=None, curdepth=0, maxdepth=1):
self.log.debug('Crawler is visiting %s' % path)
localcrawlpaths = list()
if curdepth > maxdepth:
self.log.info('maximum depth %s reached' % maxdepth)
return
r = self.request(path=path)
if r is None:
return
response, responsebody = r
try:
soup = BeautifulSoup(responsebody)
except:
self.log.warn('could not parse the response body')
return
tags = soup('a')
for tag in tags:
try:
href = tag["href"]
if href is not None:
tmpu = urlparse(href)
if (tmpu[1] != '') and (self.target != tmpu[1]):
# not on the same domain name .. ignore
self.log.debug('Ignoring link because it is not on the same site %s' % href)
continue
if tmpu[0] not in ['http', 'https', '']:
self.log.debug('Ignoring link because it is not an http uri %s' % href)
continue
path = tmpu[2]
if not path.startswith('/'):
path = '/' + path
if len(tmpu[4]) > 0:
# found a query .. thats all we need
location = urlunparse(('', '', path, tmpu[3], tmpu[4], ''))
self.log.info('Found query %s' % location)
return href
if path not in self.crawlpaths:
href = unquote(path)
self.log.debug('adding %s for crawling' % href)
self.crawlpaths.append(href)
localcrawlpaths.append(href)
except KeyError:
pass
for nextpath in localcrawlpaths:
r = self.querycrawler(path=nextpath, curdepth=curdepth + 1, maxdepth=maxdepth)
if r:
return r
def _parse_proxy(self, proxy):
parts = urlparse(proxy)
if not parts.scheme or not parts.netloc:
raise Exception("Invalid proxy specified, scheme required")
netloc = parts.netloc.split(":")
if len(netloc) != 2:
raise Exception("Proxy port unspecified")
try:
if parts.scheme == "socks5":
if socks is None:
raise Exception("socks5 proxy requires PySocks")
return Socks5Proxy(netloc[0], int(netloc[1]))
elif parts.scheme == "http":
return HttpProxy(netloc[0], int(netloc[1]))
else:
raise Exception("Unsupported proxy scheme")
except ValueError:
raise Exception("Invalid port number")
def scrambledheader(header):
c = 'connection'
if len(header) != len(c):
return False
if header == c:
return False
for character in c:
if c.count(character) != header.count(character):
return False
return True
wafw00f-0.9.4/wafw00f/lib/proxy.py 0000664 0001750 0001750 00000003462 12672245364 016207 0 ustar kun kun 0000000 0000000 import socket
try:
import httplib
except ImportError:
import http.client as httplib
try:
import socks
except ImportError:
socks = None
class NullProxy:
def prepare(self, target, port, path, ssl):
conn_factory = httplib.HTTPSConnection if ssl else httplib.HTTPConnection
return conn_factory, target, port, path
def terminate(self):
pass
class HttpProxy:
def __init__(self, host, port):
self.host = host
self.port = port
def prepare(self, target, port, path, ssl):
conn_factory = httplib.HTTPConnection
query_path = "%(scheme)s://%(host)s%(path)s" % dict(
scheme="https" if ssl else "http",
host=target,
path=path)
return conn_factory, self.host, self.port, query_path
def terminate(self):
pass
class Socks5Proxy:
def __init__(self, host, port):
self.host = host
self.port = port
self.original = socket.socket
self.original_create = socket.create_connection
def prepare(self, target, port, path, ssl):
def proxy_create_connection(address, timeout=4, source_address=None):
return socks.create_connection(address, proxy_type=socks.PROXY_TYPE_SOCKS5, proxy_addr=self.host, proxy_port=self.port, source_address=source_address, timeout=timeout)
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, self.host, self.port)
httplib.socket.socket = socks.socksocket
httplib.socket.create_connection = proxy_create_connection
conn_factory = httplib.HTTPSConnection if ssl else httplib.HTTPConnection
return conn_factory, target, port, path
def terminate(self):
httplib.socket.socket = self.original
httplib.socket.create_connection = self.original_create
wafw00f-0.9.4/wafw00f/lib/__init__.py 0000664 0001750 0001750 00000000000 12672245364 016546 0 ustar kun kun 0000000 0000000 wafw00f-0.9.4/wafw00f/manager.py 0000664 0001750 0001750 00000001164 12672245364 015667 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
import os
from functools import partial
from pluginbase import PluginBase
def load_plugins():
here = os.path.abspath(os.path.dirname(__file__))
get_path = partial(os.path.join, here)
plugin_dir = get_path('plugins')
plugin_base = PluginBase(
package='wafw00f.plugins', searchpath=[plugin_dir]
)
plugin_source = plugin_base.make_plugin_source(
searchpath=[plugin_dir], persist=True
)
plugin_dict = {}
for plugin_name in plugin_source.list_plugins():
plugin_dict[plugin_name] = plugin_source.load_plugin(plugin_name)
return plugin_dict
wafw00f-0.9.4/wafw00f/__init__.py 0000664 0001750 0001750 00000000056 12672245364 016013 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
__version__ = '0.9.4'
wafw00f-0.9.4/wafw00f/tests/ 0000775 0001750 0001750 00000000000 12672245714 015042 5 ustar kun kun 0000000 0000000 wafw00f-0.9.4/wafw00f/tests/test_main.py 0000664 0001750 0001750 00000007446 12672245364 017413 0 ustar kun kun 0000000 0000000 #!/usr/bin/env python
from unittest import TestCase
import httpretty
from wafw00f.main import WafW00F
class WafW00FTestCase(TestCase):
def test_is360wzb(self):
"""
::
$ curl -I http://www.58dm.com/
HTTP/1.1 200 OK
Server: nginx/1.4.3.6
Date: Tue, 10 Jun 2014 12:42:21 GMT
Content-Type: text/html
Connection: keep-alive
X-Powered-By-360WZB: wangzhan.360.cn
Content-Location: http://www.58dm.com/index.html
Last-Modified: Tue, 10 Jun 2014 04:10:42 GMT
ETag: "6a8d27f26184cf1:daa"
VAR-Cache: HIT
Accept-Ranges: bytes
cache-control: max-age=7200
age: 0
"""
self.__assert_waf('www.58dm.com', '360WangZhanBao', {'X-Powered-By-360WZB': 'wangzhan.360.cn'})
def test_isanquanbao(self):
"""
::
$ curl -I http://www.51cdz.com/
HTTP/1.1 200 OK
Server: ASERVER/1.2.9-3
Date: Tue, 10 Jun 2014 12:41:42 GMT
Content-Type: text/html
Content-Length: 76789
Connection: keep-alive
Content-Location: http://www.51cdz.com/index.html
Last-Modified: Sun, 01 Jun 2014 15:39:34 GMT
Accept-Ranges: bytes
ETag: "766fe9afaf7dcf1:41fc"
X-Powered-By-Anquanbao: MISS from chn-tj-ht-se2
"""
self.__assert_waf('www.51cdz.com', 'Anquanbao', {'X-Powered-By-Anquanbao': 'MISS from chn-tj-ht-se2'})
def test_ischinacache(self):
"""
::
$ curl -I http://s1.meituan.net/
HTTP/1.0 404 Not Found
Server: Tengine
Date: Tue, 10 Jun 2014 12:40:19 GMT
Content-Type: text/html; charset=iso-8859-1
Timing-Allow-Origin: *
Powered-By-ChinaCache: MISS from 0100102396
X-Cache: MISS from DXT-BJ-104
X-Cache-Lookup: MISS from DXT-BJ-104:80
X-Cache: MISS from DXT-BJ-219
X-Cache-Lookup: MISS from DXT-BJ-219:80
Connection: close
"""
self.__assert_waf('s1.meituan.net', 'ChinaCache-CDN', {'Powered-By-ChinaCache': 'fake'})
def test_ispowercdn(self):
"""
::
$ curl -I http://www.jjwxc.net/
HTTP/1.1 200 OK
Date: Tue, 10 Jun 2014 01:17:09 GMT
Content-Type: text/html
Last-Modified: Mon, 09 Jun 2014 17:30:04 GMT
Content-Encoding: gzip
X-Cache: HIT from BGP-1-233-ZZ-JJCDN
X-Cache-Lookup: HIT from BGP-1-233-ZZ-JJCDN:80
Age: 6052
PowerCDN: HIT from ak244.powercdn.com
Via: 1.1 ak244.powercdn.com (PowerCDN/2.4)
Connection: keep-alive
"""
self.__assert_waf('www.jjwxc.net', 'PowerCDN', {'PowerCDN': 'HIT from ak244.powercdn.com'})
def test_iswest263cdn(self):
"""
::
$ curl -I http://hsht.hs3w.com/
HTTP/1.0 400 Bad Request
Content-Length: 39
Content-Type: text/html
Date: Tue, 10 Jun 2014 12:33:50 GMT
X-Cache: MISS from WT263CDN-1231786
X-Cache-Lookup: MISS from WT263CDN-1231786:80
X-Cache: MISS from test.abc.com
X-Cache-Lookup: MISS from test.abc.com:80
Via: 1.0 WT263CDN-1231786 (squid/3.0.STABLE20), 1.0 test.abc.com (squid/3.0.STABLE20)
Connection: close
"""
self.__assert_waf('hsht.hs3w.com', 'West263CDN', {'X-Cache': 'MISS from WT263CDN-1231786'})
@httpretty.activate
def __assert_waf(self, host, vendor, fake_headers):
httpretty.register_uri(httpretty.GET, 'http://%s/' % host, body='fake text', adding_headers=fake_headers)
attacker = WafW00F(host)
waf = attacker.wafdetections[vendor](attacker)
self.assertTrue(waf)
wafw00f-0.9.4/wafw00f/tests/__init__.py 0000664 0001750 0001750 00000000000 12672245364 017142 0 ustar kun kun 0000000 0000000 wafw00f-0.9.4/CREDITS.txt 0000664 0001750 0001750 00000001520 12672245364 014263 0 ustar kun kun 0000000 0000000 Original code by Sandro Gauci && Wendel G. Henrique
However, a number of people contributed (in no particular order):
- Sebastien Gioria
- W3AF (or Andres Riancho)
- Charlie Campbell
- @j0eMcCray
- Mathieu Dessus
- David S. Langlands
- Nmap's http-waf-fingerprint.nse / Hani Benhabiles
- Denis Kolegov
- kun a
- Louis-Philippe Huberdeau
- Brendan Coles
- Matt Foster
- g0tmi1k (?)
- MyKings
If you did contributed and somehow I didn't put your name in there, please do
let me know: .