mirror of
				https://github.com/sqlmapproject/sqlmap.git
				synced 2025-10-25 05:01:32 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			586 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			586 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python
 | |
| 
 | |
| """
 | |
| Copyright (c) 2019-2020 Miroslav Stampar (@stamparm), MIT
 | |
| See the file 'LICENSE' for copying permission
 | |
| 
 | |
| The above copyright notice and this permission notice shall be included in
 | |
| all copies or substantial portions of the Software.
 | |
| """
 | |
| 
 | |
| from __future__ import print_function
 | |
| 
 | |
| import base64
 | |
| import codecs
 | |
| import difflib
 | |
| import json
 | |
| import locale
 | |
| import optparse
 | |
| import os
 | |
| import random
 | |
| import re
 | |
| import ssl
 | |
| import socket
 | |
| import string
 | |
| import struct
 | |
| import sys
 | |
| import time
 | |
| import zlib
 | |
| 
 | |
| PY3 = sys.version_info >= (3, 0)
 | |
| 
 | |
| if PY3:
 | |
|     import http.cookiejar
 | |
|     import http.client as httplib
 | |
|     import urllib.request
 | |
| 
 | |
|     build_opener = urllib.request.build_opener
 | |
|     install_opener = urllib.request.install_opener
 | |
|     quote = urllib.parse.quote
 | |
|     urlopen = urllib.request.urlopen
 | |
|     CookieJar = http.cookiejar.CookieJar
 | |
|     ProxyHandler = urllib.request.ProxyHandler
 | |
|     Request = urllib.request.Request
 | |
|     HTTPCookieProcessor = urllib.request.HTTPCookieProcessor
 | |
| 
 | |
|     xrange = range
 | |
| else:
 | |
|     import cookielib
 | |
|     import httplib
 | |
|     import urllib
 | |
|     import urllib2
 | |
| 
 | |
|     build_opener = urllib2.build_opener
 | |
|     install_opener = urllib2.install_opener
 | |
|     quote = urllib.quote
 | |
|     urlopen = urllib2.urlopen
 | |
|     CookieJar = cookielib.CookieJar
 | |
|     ProxyHandler = urllib2.ProxyHandler
 | |
|     Request = urllib2.Request
 | |
|     HTTPCookieProcessor = urllib2.HTTPCookieProcessor
 | |
| 
 | |
| NAME = "identYwaf"
 | |
| VERSION = "1.0.124"
 | |
| BANNER = r"""
 | |
|                                    ` __ __ `
 | |
|  ____  ___      ___  ____   ______ `|  T  T` __    __   ____  _____ 
 | |
| l    j|   \    /  _]|    \ |      T`|  |  |`|  T__T  T /    T|   __|
 | |
|  |  T |    \  /  [_ |  _  Yl_j  l_j`|  ~  |`|  |  |  |Y  o  ||  l_
 | |
|  |  | |  D  YY    _]|  |  |  |  |  `|___  |`|  |  |  ||     ||   _|
 | |
|  j  l |     ||   [_ |  |  |  |  |  `|     !` \      / |  |  ||  ] 
 | |
| |____jl_____jl_____jl__j__j  l__j  `l____/ `  \_/\_/  l__j__jl__j  (%s)%s""".strip("\n") % (VERSION, "\n")
 | |
| 
 | |
| RAW, TEXT, HTTPCODE, SERVER, TITLE, HTML, URL = xrange(7)
 | |
| COOKIE, UA, REFERER = "Cookie", "User-Agent", "Referer"
 | |
| GET, POST = "GET", "POST"
 | |
| GENERIC_PROTECTION_KEYWORDS = ("rejected", "forbidden", "suspicious", "malicious", "captcha", "invalid", "your ip", "please contact", "terminated", "protected", "unauthorized", "blocked", "protection", "incident", "denied", "detected", "dangerous", "firewall", "fw_block", "unusual activity", "bad request", "request id", "injection", "permission", "not acceptable", "security policy", "security reasons")
 | |
| GENERIC_PROTECTION_REGEX = r"(?i)\b(%s)\b"
 | |
| GENERIC_ERROR_MESSAGE_REGEX = r"\b[A-Z][\w, '-]*(protected by|security|unauthorized|detected|attack|error|rejected|allowed|suspicious|automated|blocked|invalid|denied|permission)[\w, '!-]*"
 | |
| WAF_RECOGNITION_REGEX = None
 | |
| HEURISTIC_PAYLOAD = "1 AND 1=1 UNION ALL SELECT 1,NULL,'<script>alert(\"XSS\")</script>',table_name FROM information_schema.tables WHERE 2>1--/**/; EXEC xp_cmdshell('cat ../../../etc/passwd')#"  # Reference: https://github.com/sqlmapproject/sqlmap/blob/master/lib/core/settings.py
 | |
| PAYLOADS = []
 | |
| SIGNATURES = {}
 | |
| DATA_JSON = {}
 | |
| DATA_JSON_FILE = os.path.join(os.path.dirname(__file__), "data.json")
 | |
| MAX_HELP_OPTION_LENGTH = 18
 | |
| IS_TTY = sys.stdout.isatty()
 | |
| IS_WIN = os.name == "nt"
 | |
| COLORIZE = not IS_WIN and IS_TTY
 | |
| LEVEL_COLORS = {"o": "\033[00;94m", "x": "\033[00;91m", "!": "\033[00;93m", "i": "\033[00;95m", "=": "\033[00;93m", "+": "\033[00;92m", "-": "\033[00;91m"}
 | |
| VERIFY_OK_INTERVAL = 5
 | |
| VERIFY_RETRY_TIMES = 3
 | |
| MIN_MATCH_PARTIAL = 5
 | |
| DEFAULTS = {"timeout": 10}
 | |
| MAX_MATCHES = 5
 | |
| QUICK_RATIO_THRESHOLD = 0.2
 | |
| MAX_JS_CHALLENGE_SNAPLEN = 120
 | |
| ENCODING_TRANSLATIONS = {"windows-874": "iso-8859-11", "utf-8859-1": "utf8", "en_us": "utf8", "macintosh": "iso-8859-1", "euc_tw": "big5_tw", "th": "tis-620", "unicode": "utf8", "utc8": "utf8", "ebcdic": "ebcdic-cp-be", "iso-8859": "iso8859-1", "iso-8859-0": "iso8859-1", "ansi": "ascii", "gbk2312": "gbk", "windows-31j": "cp932", "en": "us"}  # Reference: https://github.com/sqlmapproject/sqlmap/blob/master/lib/request/basic.py
 | |
| PROXY_TESTING_PAGE = "https://myexternalip.com/raw"
 | |
| 
 | |
| if COLORIZE:
 | |
|     for _ in re.findall(r"`.+?`", BANNER):
 | |
|         BANNER = BANNER.replace(_, "\033[01;92m%s\033[00;49m" % _.strip('`'))
 | |
|     for _ in re.findall(r" [Do] ", BANNER):
 | |
|         BANNER = BANNER.replace(_, "\033[01;93m%s\033[00;49m" % _.strip('`'))
 | |
|     BANNER = re.sub(VERSION, r"\033[01;91m%s\033[00;49m" % VERSION, BANNER)
 | |
| else:
 | |
|     BANNER = BANNER.replace('`', "")
 | |
| 
 | |
| _ = random.randint(20, 64)
 | |
| DEFAULT_USER_AGENT = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; %s; rv:%d.0) Gecko/20100101 Firefox/%d.0" % (NAME, _, _)
 | |
| HEADERS = {"User-Agent": DEFAULT_USER_AGENT, "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "identity", "Cache-Control": "max-age=0"}
 | |
| 
 | |
| original = None
 | |
| options = None
 | |
| intrusive = None
 | |
| heuristic = None
 | |
| chained = False
 | |
| locked_code = None
 | |
| locked_regex = None
 | |
| non_blind = set()
 | |
| seen = set()
 | |
| blocked = []
 | |
| servers = set()
 | |
| codes = set()
 | |
| proxies = list()
 | |
| proxies_index = 0
 | |
| 
 | |
| _exit = exit
 | |
| 
 | |
| def exit(message=None):
 | |
|     if message:
 | |
|         print("%s%s" % (message, ' ' * 20))
 | |
|     _exit(1)
 | |
| 
 | |
| def retrieve(url, data=None):
 | |
|     global proxies_index
 | |
| 
 | |
|     retval = {}
 | |
| 
 | |
|     if proxies:
 | |
|         while True:
 | |
|             try:
 | |
|                 opener = build_opener(ProxyHandler({"http": proxies[proxies_index], "https": proxies[proxies_index]}))
 | |
|                 install_opener(opener)
 | |
|                 proxies_index = (proxies_index + 1) % len(proxies)
 | |
|                 urlopen(PROXY_TESTING_PAGE).read()
 | |
|             except KeyboardInterrupt:
 | |
|                 raise
 | |
|             except:
 | |
|                 pass
 | |
|             else:
 | |
|                 break
 | |
| 
 | |
|     try:
 | |
|         req = Request("".join(url[_].replace(' ', "%20") if _ > url.find('?') else url[_] for _ in xrange(len(url))), data, HEADERS)
 | |
|         resp = urlopen(req, timeout=options.timeout)
 | |
|         retval[URL] = resp.url
 | |
|         retval[HTML] = resp.read()
 | |
|         retval[HTTPCODE] = resp.code
 | |
|         retval[RAW] = "%s %d %s\n%s\n%s" % (httplib.HTTPConnection._http_vsn_str, retval[HTTPCODE], resp.msg, str(resp.headers), retval[HTML])
 | |
|     except Exception as ex:
 | |
|         retval[URL] = getattr(ex, "url", url)
 | |
|         retval[HTTPCODE] = getattr(ex, "code", None)
 | |
|         try:
 | |
|             retval[HTML] = ex.read() if hasattr(ex, "read") else getattr(ex, "msg", str(ex))
 | |
|         except:
 | |
|             retval[HTML] = ""
 | |
|         retval[RAW] = "%s %s %s\n%s\n%s" % (httplib.HTTPConnection._http_vsn_str, retval[HTTPCODE] or "", getattr(ex, "msg", ""), str(ex.headers) if hasattr(ex, "headers") else "", retval[HTML])
 | |
| 
 | |
|     for encoding in re.findall(r"charset=[\s\"']?([\w-]+)", retval[RAW])[::-1] + ["utf8"]:
 | |
|         encoding = ENCODING_TRANSLATIONS.get(encoding, encoding)
 | |
|         try:
 | |
|             retval[HTML] = retval[HTML].decode(encoding, errors="replace")
 | |
|             break
 | |
|         except:
 | |
|             pass
 | |
| 
 | |
|     match = re.search(r"<title>\s*(?P<result>[^<]+?)\s*</title>", retval[HTML], re.I)
 | |
|     retval[TITLE] = match.group("result") if match and "result" in match.groupdict() else None
 | |
|     retval[TEXT] = re.sub(r"(?si)<script.+?</script>|<!--.+?-->|<style.+?</style>|<[^>]+>|\s+", " ", retval[HTML])
 | |
|     match = re.search(r"(?im)^Server: (.+)", retval[RAW])
 | |
|     retval[SERVER] = match.group(1).strip() if match else ""
 | |
|     return retval
 | |
| 
 | |
| def calc_hash(value, binary=True):
 | |
|     value = value.encode("utf8") if not isinstance(value, bytes) else value
 | |
|     result = zlib.crc32(value) & 0xffff
 | |
|     if binary:
 | |
|         result = struct.pack(">H", result)
 | |
|     return result
 | |
| 
 | |
| def single_print(message):
 | |
|     if message not in seen:
 | |
|         print(message)
 | |
|         seen.add(message)
 | |
| 
 | |
| def check_payload(payload, protection_regex=GENERIC_PROTECTION_REGEX % '|'.join(GENERIC_PROTECTION_KEYWORDS)):
 | |
|     global chained
 | |
|     global heuristic
 | |
|     global intrusive
 | |
|     global locked_code
 | |
|     global locked_regex
 | |
| 
 | |
|     time.sleep(options.delay or 0)
 | |
|     if options.post:
 | |
|         _ = "%s=%s" % ("".join(random.sample(string.ascii_letters, 3)), quote(payload))
 | |
|         intrusive = retrieve(options.url, _)
 | |
|     else:
 | |
|         _ = "%s%s%s=%s" % (options.url, '?' if '?' not in options.url else '&', "".join(random.sample(string.ascii_letters, 3)), quote(payload))
 | |
|         intrusive = retrieve(_)
 | |
| 
 | |
|     if options.lock and not payload.isdigit():
 | |
|         if payload == HEURISTIC_PAYLOAD:
 | |
|             match = re.search(re.sub(r"Server:|Protected by", "".join(random.sample(string.ascii_letters, 6)), WAF_RECOGNITION_REGEX, flags=re.I), intrusive[RAW] or "")
 | |
|             if match:
 | |
|                 result = True
 | |
| 
 | |
|                 for _ in match.groupdict():
 | |
|                     if match.group(_):
 | |
|                         waf = re.sub(r"\Awaf_", "", _)
 | |
|                         locked_regex = DATA_JSON["wafs"][waf]["regex"]
 | |
|                         locked_code = intrusive[HTTPCODE]
 | |
|                         break
 | |
|             else:
 | |
|                 result = False
 | |
| 
 | |
|             if not result:
 | |
|                 exit(colorize("[x] can't lock results to a non-blind match"))
 | |
|         else:
 | |
|             result = re.search(locked_regex, intrusive[RAW]) is not None and locked_code == intrusive[HTTPCODE]
 | |
|     elif options.string:
 | |
|         result = options.string in (intrusive[RAW] or "")
 | |
|     elif options.code:
 | |
|         result = options.code == intrusive[HTTPCODE]
 | |
|     else:
 | |
|         result = intrusive[HTTPCODE] != original[HTTPCODE] or (intrusive[HTTPCODE] != 200 and intrusive[TITLE] != original[TITLE]) or (re.search(protection_regex, intrusive[HTML]) is not None and re.search(protection_regex, original[HTML]) is None) or (difflib.SequenceMatcher(a=original[HTML] or "", b=intrusive[HTML] or "").quick_ratio() < QUICK_RATIO_THRESHOLD)
 | |
| 
 | |
|     if not payload.isdigit():
 | |
|         if result:
 | |
|             if options.debug:
 | |
|                 print("\r---%s" % (40 * ' '))
 | |
|                 print(payload)
 | |
|                 print(intrusive[HTTPCODE], intrusive[RAW])
 | |
|                 print("---")
 | |
| 
 | |
|             if intrusive[SERVER]:
 | |
|                 servers.add(re.sub(r"\s*\(.+\)\Z", "", intrusive[SERVER]))
 | |
|                 if len(servers) > 1:
 | |
|                     chained = True
 | |
|                     single_print(colorize("[!] multiple (reactive) rejection HTTP 'Server' headers detected (%s)" % ', '.join("'%s'" % _ for _ in sorted(servers))))
 | |
| 
 | |
|             if intrusive[HTTPCODE]:
 | |
|                 codes.add(intrusive[HTTPCODE])
 | |
|                 if len(codes) > 1:
 | |
|                     chained = True
 | |
|                     single_print(colorize("[!] multiple (reactive) rejection HTTP codes detected (%s)" % ', '.join("%s" % _ for _ in sorted(codes))))
 | |
| 
 | |
|             if heuristic and heuristic[HTML] and intrusive[HTML] and difflib.SequenceMatcher(a=heuristic[HTML] or "", b=intrusive[HTML] or "").quick_ratio() < QUICK_RATIO_THRESHOLD:
 | |
|                 chained = True
 | |
|                 single_print(colorize("[!] multiple (reactive) rejection HTML responses detected"))
 | |
| 
 | |
|     if payload == HEURISTIC_PAYLOAD:
 | |
|         heuristic = intrusive
 | |
| 
 | |
|     return result
 | |
| 
 | |
| def colorize(message):
 | |
|     if COLORIZE:
 | |
|         message = re.sub(r"\[(.)\]", lambda match: "[%s%s\033[00;49m]" % (LEVEL_COLORS[match.group(1)], match.group(1)), message)
 | |
| 
 | |
|         if any(_ in message for _ in ("rejected summary", "challenge detected")):
 | |
|             for match in re.finditer(r"[^\w]'([^)]+)'" if "rejected summary" in message else r"\('(.+)'\)", message):
 | |
|                 message = message.replace("'%s'" % match.group(1), "'\033[37m%s\033[00;49m'" % match.group(1), 1)
 | |
|         else:
 | |
|             for match in re.finditer(r"[^\w]'([^']+)'", message):
 | |
|                 message = message.replace("'%s'" % match.group(1), "'\033[37m%s\033[00;49m'" % match.group(1), 1)
 | |
| 
 | |
|         if "blind match" in message:
 | |
|             for match in re.finditer(r"\(((\d+)%)\)", message):
 | |
|                 message = message.replace(match.group(1), "\033[%dm%s\033[00;49m" % (92 if int(match.group(2)) >= 95 else (93 if int(match.group(2)) > 80 else 90), match.group(1)))
 | |
| 
 | |
|         if "hardness" in message:
 | |
|             for match in re.finditer(r"\(((\d+)%)\)", message):
 | |
|                 message = message.replace(match.group(1), "\033[%dm%s\033[00;49m" % (95 if " insane " in message else (91 if " hard " in message else (93 if " moderate " in message else 92)), match.group(1)))
 | |
| 
 | |
|     return message
 | |
| 
 | |
| def parse_args():
 | |
|     global options
 | |
| 
 | |
|     parser = optparse.OptionParser(version=VERSION)
 | |
|     parser.add_option("--delay", dest="delay", type=int, help="Delay (sec) between tests (default: 0)")
 | |
|     parser.add_option("--timeout", dest="timeout", type=int, help="Response timeout (sec) (default: 10)")
 | |
|     parser.add_option("--proxy", dest="proxy", help="HTTP proxy address (e.g. \"http://127.0.0.1:8080\")")
 | |
|     parser.add_option("--proxy-file", dest="proxy_file", help="Load (rotating) HTTP(s) proxy list from a file")
 | |
|     parser.add_option("--random-agent", dest="random_agent", action="store_true", help="Use random HTTP User-Agent header value")
 | |
|     parser.add_option("--code", dest="code", type=int, help="Expected HTTP code in rejected responses")
 | |
|     parser.add_option("--string", dest="string", help="Expected string in rejected responses")
 | |
|     parser.add_option("--post", dest="post", action="store_true", help="Use POST body for sending payloads")
 | |
|     parser.add_option("--debug", dest="debug", action="store_true", help=optparse.SUPPRESS_HELP)
 | |
|     parser.add_option("--fast", dest="fast", action="store_true", help=optparse.SUPPRESS_HELP)
 | |
|     parser.add_option("--lock", dest="lock", action="store_true", help=optparse.SUPPRESS_HELP)
 | |
| 
 | |
|     # Dirty hack(s) for help message
 | |
|     def _(self, *args):
 | |
|         retval = parser.formatter._format_option_strings(*args)
 | |
|         if len(retval) > MAX_HELP_OPTION_LENGTH:
 | |
|             retval = ("%%.%ds.." % (MAX_HELP_OPTION_LENGTH - parser.formatter.indent_increment)) % retval
 | |
|         return retval
 | |
| 
 | |
|     parser.usage = "python %s <host|url>" % parser.usage
 | |
|     parser.formatter._format_option_strings = parser.formatter.format_option_strings
 | |
|     parser.formatter.format_option_strings = type(parser.formatter.format_option_strings)(_, parser)
 | |
| 
 | |
|     for _ in ("-h", "--version"):
 | |
|         option = parser.get_option(_)
 | |
|         option.help = option.help.capitalize()
 | |
| 
 | |
|     try:
 | |
|         options, _ = parser.parse_args()
 | |
|     except SystemExit:
 | |
|         raise
 | |
| 
 | |
|     if len(sys.argv) > 1:
 | |
|         url = sys.argv[-1]
 | |
|         if not url.startswith("http"):
 | |
|             url = "http://%s" % url
 | |
|         options.url = url
 | |
|     else:
 | |
|         parser.print_help()
 | |
|         raise SystemExit
 | |
| 
 | |
|     for key in DEFAULTS:
 | |
|         if getattr(options, key, None) is None:
 | |
|             setattr(options, key, DEFAULTS[key])
 | |
| 
 | |
| def load_data():
 | |
|     global WAF_RECOGNITION_REGEX
 | |
| 
 | |
|     if os.path.isfile(DATA_JSON_FILE):
 | |
|         with codecs.open(DATA_JSON_FILE, "rb", encoding="utf8") as f:
 | |
|             DATA_JSON.update(json.load(f))
 | |
| 
 | |
|         WAF_RECOGNITION_REGEX = ""
 | |
|         for waf in DATA_JSON["wafs"]:
 | |
|             if DATA_JSON["wafs"][waf]["regex"]:
 | |
|                 WAF_RECOGNITION_REGEX += "%s|" % ("(?P<waf_%s>%s)" % (waf, DATA_JSON["wafs"][waf]["regex"]))
 | |
|             for signature in DATA_JSON["wafs"][waf]["signatures"]:
 | |
|                 SIGNATURES[signature] = waf
 | |
|         WAF_RECOGNITION_REGEX = WAF_RECOGNITION_REGEX.strip('|')
 | |
| 
 | |
|         flags = "".join(set(_ for _ in "".join(re.findall(r"\(\?(\w+)\)", WAF_RECOGNITION_REGEX))))
 | |
|         WAF_RECOGNITION_REGEX = "(?%s)%s" % (flags, re.sub(r"\(\?\w+\)", "", WAF_RECOGNITION_REGEX))  # patch for "DeprecationWarning: Flags not at the start of the expression" in Python3.7
 | |
|     else:
 | |
|         exit(colorize("[x] file '%s' is missing" % DATA_JSON_FILE))
 | |
| 
 | |
| def init():
 | |
|     os.chdir(os.path.abspath(os.path.dirname(__file__)))
 | |
| 
 | |
|     # Reference: http://blog.mathieu-leplatre.info/python-utf-8-print-fails-when-redirecting-stdout.html
 | |
|     if not PY3 and not IS_TTY:
 | |
|         sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout)
 | |
| 
 | |
|     print(colorize("[o] initializing handlers..."))
 | |
| 
 | |
|     # Reference: https://stackoverflow.com/a/28052583
 | |
|     if hasattr(ssl, "_create_unverified_context"):
 | |
|         ssl._create_default_https_context = ssl._create_unverified_context
 | |
| 
 | |
|     if options.proxy_file:
 | |
|         if os.path.isfile(options.proxy_file):
 | |
|             print(colorize("[o] loading proxy list..."))
 | |
| 
 | |
|             with codecs.open(options.proxy_file, "rb", encoding="utf8") as f:
 | |
|                 proxies.extend(re.sub(r"\s.*", "", _.strip()) for _ in f.read().strip().split('\n') if _.startswith("http"))
 | |
|                 random.shuffle(proxies)
 | |
|         else:
 | |
|             exit(colorize("[x] file '%s' does not exist" % options.proxy_file))
 | |
| 
 | |
| 
 | |
|     cookie_jar = CookieJar()
 | |
|     opener = build_opener(HTTPCookieProcessor(cookie_jar))
 | |
|     install_opener(opener)
 | |
| 
 | |
|     if options.proxy:
 | |
|         opener = build_opener(ProxyHandler({"http": options.proxy, "https": options.proxy}))
 | |
|         install_opener(opener)
 | |
| 
 | |
|     if options.random_agent:
 | |
|         revision = random.randint(20, 64)
 | |
|         platform = random.sample(("X11; %s %s" % (random.sample(("Linux", "Ubuntu; Linux", "U; Linux", "U; OpenBSD", "U; FreeBSD"), 1)[0], random.sample(("amd64", "i586", "i686", "amd64"), 1)[0]), "Windows NT %s%s" % (random.sample(("5.0", "5.1", "5.2", "6.0", "6.1", "6.2", "6.3", "10.0"), 1)[0], random.sample(("", "; Win64", "; WOW64"), 1)[0]), "Macintosh; Intel Mac OS X 10.%s" % random.randint(1, 11)), 1)[0]
 | |
|         user_agent = "Mozilla/5.0 (%s; rv:%d.0) Gecko/20100101 Firefox/%d.0" % (platform, revision, revision)
 | |
|         HEADERS["User-Agent"] = user_agent
 | |
| 
 | |
| def format_name(waf):
 | |
|     return "%s%s" % (DATA_JSON["wafs"][waf]["name"], (" (%s)" % DATA_JSON["wafs"][waf]["company"]) if DATA_JSON["wafs"][waf]["name"] != DATA_JSON["wafs"][waf]["company"] else "")
 | |
| 
 | |
| def non_blind_check(raw, silent=False):
 | |
|     retval = False
 | |
|     match = re.search(WAF_RECOGNITION_REGEX, raw or "")
 | |
|     if match:
 | |
|         retval = True
 | |
|         for _ in match.groupdict():
 | |
|             if match.group(_):
 | |
|                 waf = re.sub(r"\Awaf_", "", _)
 | |
|                 non_blind.add(waf)
 | |
|                 if not silent:
 | |
|                     single_print(colorize("[+] non-blind match: '%s'%s" % (format_name(waf), 20 * ' ')))
 | |
|     return retval
 | |
| 
 | |
| def run():
 | |
|     global original
 | |
| 
 | |
|     hostname = options.url.split("//")[-1].split('/')[0].split(':')[0]
 | |
| 
 | |
|     if not hostname.replace('.', "").isdigit():
 | |
|         print(colorize("[i] checking hostname '%s'..." % hostname))
 | |
|         try:
 | |
|             socket.getaddrinfo(hostname, None)
 | |
|         except socket.gaierror:
 | |
|             exit(colorize("[x] host '%s' does not exist" % hostname))
 | |
| 
 | |
|     results = ""
 | |
|     signature = b""
 | |
|     counter = 0
 | |
|     original = retrieve(options.url)
 | |
| 
 | |
|     if 300 <= (original[HTTPCODE] or 0) < 400 and original[URL]:
 | |
|         original = retrieve(original[URL])
 | |
| 
 | |
|     options.url = original[URL]
 | |
| 
 | |
|     if original[HTTPCODE] is None:
 | |
|         exit(colorize("[x] missing valid response"))
 | |
| 
 | |
|     if not any((options.string, options.code)) and original[HTTPCODE] >= 400:
 | |
|         non_blind_check(original[RAW])
 | |
|         if options.debug:
 | |
|             print("\r---%s" % (40 * ' '))
 | |
|             print(original[HTTPCODE], original[RAW])
 | |
|             print("---")
 | |
|         exit(colorize("[x] access to host '%s' seems to be restricted%s" % (hostname, (" (%d: '<title>%s</title>')" % (original[HTTPCODE], original[TITLE].strip())) if original[TITLE] else "")))
 | |
| 
 | |
|     challenge = None
 | |
|     if all(_ in original[HTML].lower() for _ in ("eval", "<script")):
 | |
|         match = re.search(r"(?is)<body[^>]*>(.*)</body>", re.sub(r"(?is)<script.+?</script>", "", original[HTML]))
 | |
|         if re.search(r"(?i)<(body|div)", original[HTML]) is None or (match and len(match.group(1)) == 0):
 | |
|             challenge = re.search(r"(?is)<script.+</script>", original[HTML]).group(0).replace("\n", "\\n")
 | |
|             print(colorize("[x] anti-robot JS challenge detected ('%s%s')" % (challenge[:MAX_JS_CHALLENGE_SNAPLEN], "..." if len(challenge) > MAX_JS_CHALLENGE_SNAPLEN else "")))
 | |
| 
 | |
|     protection_keywords = GENERIC_PROTECTION_KEYWORDS
 | |
|     protection_regex = GENERIC_PROTECTION_REGEX % '|'.join(keyword for keyword in protection_keywords if keyword not in original[HTML].lower())
 | |
| 
 | |
|     print(colorize("[i] running basic heuristic test..."))
 | |
|     if not check_payload(HEURISTIC_PAYLOAD):
 | |
|         check = False
 | |
|         if options.url.startswith("https://"):
 | |
|             options.url = options.url.replace("https://", "http://")
 | |
|             check = check_payload(HEURISTIC_PAYLOAD)
 | |
|         if not check:
 | |
|             if non_blind_check(intrusive[RAW]):
 | |
|                 exit(colorize("[x] unable to continue due to static responses%s" % (" (captcha)" if re.search(r"(?i)captcha", intrusive[RAW]) is not None else "")))
 | |
|             elif challenge is None:
 | |
|                 exit(colorize("[x] host '%s' does not seem to be protected" % hostname))
 | |
|             else:
 | |
|                 exit(colorize("[x] response not changing without JS challenge solved"))
 | |
| 
 | |
|     if options.fast and not non_blind:
 | |
|         exit(colorize("[x] fast exit because of missing non-blind match"))
 | |
| 
 | |
|     if not intrusive[HTTPCODE]:
 | |
|         print(colorize("[i] rejected summary: RST|DROP"))
 | |
|     else:
 | |
|         _ = "...".join(match.group(0) for match in re.finditer(GENERIC_ERROR_MESSAGE_REGEX, intrusive[HTML])).strip().replace("  ", " ")
 | |
|         print(colorize(("[i] rejected summary: %d ('%s%s')" % (intrusive[HTTPCODE], ("<title>%s</title>" % intrusive[TITLE]) if intrusive[TITLE] else "", "" if not _ or intrusive[HTTPCODE] < 400 else ("...%s" % _))).replace(" ('')", "")))
 | |
| 
 | |
|     found = non_blind_check(intrusive[RAW] if intrusive[HTTPCODE] is not None else original[RAW])
 | |
| 
 | |
|     if not found:
 | |
|         print(colorize("[-] non-blind match: -"))
 | |
| 
 | |
|     for item in DATA_JSON["payloads"]:
 | |
|         info, payload = item.split("::", 1)
 | |
|         counter += 1
 | |
| 
 | |
|         if IS_TTY:
 | |
|             sys.stdout.write(colorize("\r[i] running payload tests... (%d/%d)\r" % (counter, len(DATA_JSON["payloads"]))))
 | |
|             sys.stdout.flush()
 | |
| 
 | |
|         if counter % VERIFY_OK_INTERVAL == 0:
 | |
|             for i in xrange(VERIFY_RETRY_TIMES):
 | |
|                 if not check_payload(str(random.randint(1, 9)), protection_regex):
 | |
|                     break
 | |
|                 elif i == VERIFY_RETRY_TIMES - 1:
 | |
|                     exit(colorize("[x] host '%s' seems to be misconfigured or rejecting benign requests%s" % (hostname, (" (%d: '<title>%s</title>')" % (intrusive[HTTPCODE], intrusive[TITLE].strip())) if intrusive[TITLE] else "")))
 | |
|                 else:
 | |
|                     time.sleep(5)
 | |
| 
 | |
|         last = check_payload(payload, protection_regex)
 | |
|         non_blind_check(intrusive[RAW])
 | |
|         signature += struct.pack(">H", ((calc_hash(payload, binary=False) << 1) | last) & 0xffff)
 | |
|         results += 'x' if last else '.'
 | |
| 
 | |
|         if last and info not in blocked:
 | |
|             blocked.append(info)
 | |
| 
 | |
|     _ = calc_hash(signature)
 | |
|     signature = "%s:%s" % (_.encode("hex") if not hasattr(_, "hex") else _.hex(), base64.b64encode(signature).decode("ascii"))
 | |
| 
 | |
|     print(colorize("%s[=] results: '%s'" % ("\n" if IS_TTY else "", results)))
 | |
| 
 | |
|     hardness = 100 * results.count('x') / len(results)
 | |
|     print(colorize("[=] hardness: %s (%d%%)" % ("insane" if hardness >= 80 else ("hard" if hardness >= 50 else ("moderate" if hardness >= 30 else "easy")), hardness)))
 | |
| 
 | |
|     if blocked:
 | |
|         print(colorize("[=] blocked categories: %s" % ", ".join(blocked)))
 | |
| 
 | |
|     if not results.strip('.') or not results.strip('x'):
 | |
|         print(colorize("[-] blind match: -"))
 | |
| 
 | |
|         if re.search(r"(?i)captcha", original[HTML]) is not None:
 | |
|             exit(colorize("[x] there seems to be an activated captcha"))
 | |
|     else:
 | |
|         print(colorize("[=] signature: '%s'" % signature))
 | |
| 
 | |
|         if signature in SIGNATURES:
 | |
|             waf = SIGNATURES[signature]
 | |
|             print(colorize("[+] blind match: '%s' (100%%)" % format_name(waf)))
 | |
|         elif results.count('x') < MIN_MATCH_PARTIAL:
 | |
|             print(colorize("[-] blind match: -"))
 | |
|         else:
 | |
|             matches = {}
 | |
|             markers = set()
 | |
|             decoded = base64.b64decode(signature.split(':')[-1])
 | |
|             for i in xrange(0, len(decoded), 2):
 | |
|                 part = struct.unpack(">H", decoded[i: i + 2])[0]
 | |
|                 markers.add(part)
 | |
| 
 | |
|             for candidate in SIGNATURES:
 | |
|                 counter_y, counter_n = 0, 0
 | |
|                 decoded = base64.b64decode(candidate.split(':')[-1])
 | |
|                 for i in xrange(0, len(decoded), 2):
 | |
|                     part = struct.unpack(">H", decoded[i: i + 2])[0]
 | |
|                     if part in markers:
 | |
|                         counter_y += 1
 | |
|                     elif any(_ in markers for _ in (part & ~1, part | 1)):
 | |
|                         counter_n += 1
 | |
|                 result = int(round(100 * counter_y / (counter_y + counter_n)))
 | |
|                 if SIGNATURES[candidate] in matches:
 | |
|                     if result > matches[SIGNATURES[candidate]]:
 | |
|                         matches[SIGNATURES[candidate]] = result
 | |
|                 else:
 | |
|                     matches[SIGNATURES[candidate]] = result
 | |
| 
 | |
|             if chained:
 | |
|                 for _ in list(matches.keys()):
 | |
|                     if matches[_] < 90:
 | |
|                         del matches[_]
 | |
| 
 | |
|             if not matches:
 | |
|                 print(colorize("[-] blind match: - "))
 | |
|                 print(colorize("[!] probably chained web protection systems"))
 | |
|             else:
 | |
|                 matches = [(_[1], _[0]) for _ in matches.items()]
 | |
|                 matches.sort(reverse=True)
 | |
| 
 | |
|                 print(colorize("[+] blind match: %s" % ", ".join("'%s' (%d%%)" % (format_name(matches[i][1]), matches[i][0]) for i in xrange(min(len(matches), MAX_MATCHES) if matches[0][0] != 100 else 1))))
 | |
| 
 | |
|     print()
 | |
| 
 | |
| def main():
 | |
|     if "--version" not in sys.argv:
 | |
|         print(BANNER)
 | |
| 
 | |
|     parse_args()
 | |
|     init()
 | |
|     run()
 | |
| 
 | |
| load_data()
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     try:
 | |
|         main()
 | |
|     except KeyboardInterrupt:
 | |
|         exit(colorize("\r[x] Ctrl-C pressed"))
 |