Path: blob/master/thirdparty/identywaf/identYwaf.py
2992 views
#!/usr/bin/env python12"""3Copyright (c) 2019-2021 Miroslav Stampar (@stamparm), MIT4See the file 'LICENSE' for copying permission56The above copyright notice and this permission notice shall be included in7all copies or substantial portions of the Software.8"""910from __future__ import print_function1112import base6413import codecs14import difflib15import json16import locale17import optparse18import os19import random20import re21import ssl22import socket23import string24import struct25import sys26import time27import zlib2829PY3 = sys.version_info >= (3, 0)3031if PY3:32import http.cookiejar33import http.client as httplib34import urllib.request3536build_opener = urllib.request.build_opener37install_opener = urllib.request.install_opener38quote = urllib.parse.quote39urlopen = urllib.request.urlopen40CookieJar = http.cookiejar.CookieJar41ProxyHandler = urllib.request.ProxyHandler42Request = urllib.request.Request43HTTPCookieProcessor = urllib.request.HTTPCookieProcessor4445xrange = range46else:47import cookielib48import httplib49import urllib50import urllib25152build_opener = urllib2.build_opener53install_opener = urllib2.install_opener54quote = urllib.quote55urlopen = urllib2.urlopen56CookieJar = cookielib.CookieJar57ProxyHandler = urllib2.ProxyHandler58Request = urllib2.Request59HTTPCookieProcessor = urllib2.HTTPCookieProcessor6061NAME = "identYwaf"62VERSION = "1.0.131"63BANNER = r"""64` __ __ `65____ ___ ___ ____ ______ `| T T` __ __ ____ _____66l j| \ / _]| \ | T`| | |`| T__T T / T| __|67| T | \ / [_ | _ Yl_j l_j`| ~ |`| | | |Y o || l_68| | | D YY _]| | | | | `|___ |`| | | || || _|69j l | || [_ | | | | | `| !` \ / | | || ]70|____jl_____jl_____jl__j__j l__j `l____/ ` \_/\_/ l__j__jl__j (%s)%s""".strip("\n") % (VERSION, "\n")7172RAW, TEXT, HTTPCODE, SERVER, TITLE, HTML, URL = xrange(7)73COOKIE, UA, REFERER = "Cookie", "User-Agent", "Referer"74GET, POST = "GET", "POST"75GENERIC_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")76GENERIC_PROTECTION_REGEX = r"(?i)\b(%s)\b"77GENERIC_ERROR_MESSAGE_REGEX = r"\b[A-Z][\w, '-]*(protected by|security|unauthorized|detected|attack|error|rejected|allowed|suspicious|automated|blocked|invalid|denied|permission)[\w, '!-]*"78WAF_RECOGNITION_REGEX = None79HEURISTIC_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.py80PAYLOADS = []81SIGNATURES = {}82DATA_JSON = {}83DATA_JSON_FILE = os.path.join(os.path.dirname(__file__), "data.json")84MAX_HELP_OPTION_LENGTH = 1885IS_TTY = sys.stdout.isatty()86IS_WIN = os.name == "nt"87COLORIZE = not IS_WIN and IS_TTY88LEVEL_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"}89VERIFY_OK_INTERVAL = 590VERIFY_RETRY_TIMES = 391MIN_MATCH_PARTIAL = 592DEFAULTS = {"timeout": 10}93MAX_MATCHES = 594QUICK_RATIO_THRESHOLD = 0.295MAX_JS_CHALLENGE_SNAPLEN = 12096ENCODING_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.py97PROXY_TESTING_PAGE = "https://myexternalip.com/raw"9899if COLORIZE:100for _ in re.findall(r"`.+?`", BANNER):101BANNER = BANNER.replace(_, "\033[01;92m%s\033[00;49m" % _.strip('`'))102for _ in re.findall(r" [Do] ", BANNER):103BANNER = BANNER.replace(_, "\033[01;93m%s\033[00;49m" % _.strip('`'))104BANNER = re.sub(VERSION, r"\033[01;91m%s\033[00;49m" % VERSION, BANNER)105else:106BANNER = BANNER.replace('`', "")107108_ = random.randint(20, 64)109DEFAULT_USER_AGENT = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; %s; rv:%d.0) Gecko/20100101 Firefox/%d.0" % (NAME, _, _)110HEADERS = {"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"}111112original = None113options = None114intrusive = None115heuristic = None116chained = False117locked_code = None118locked_regex = None119non_blind = set()120seen = set()121blocked = []122servers = set()123codes = set()124proxies = list()125proxies_index = 0126127_exit = sys.exit128129def exit(message=None):130if message:131print("%s%s" % (message, ' ' * 20))132_exit(1)133134def retrieve(url, data=None):135global proxies_index136137retval = {}138139if proxies:140while True:141try:142opener = build_opener(ProxyHandler({"http": proxies[proxies_index], "https": proxies[proxies_index]}))143install_opener(opener)144proxies_index = (proxies_index + 1) % len(proxies)145urlopen(PROXY_TESTING_PAGE).read()146except KeyboardInterrupt:147raise148except:149pass150else:151break152153try:154req = Request("".join(url[_].replace(' ', "%20") if _ > url.find('?') else url[_] for _ in xrange(len(url))), data, HEADERS)155resp = urlopen(req, timeout=options.timeout)156retval[URL] = resp.url157retval[HTML] = resp.read()158retval[HTTPCODE] = resp.code159retval[RAW] = "%s %d %s\n%s\n%s" % (httplib.HTTPConnection._http_vsn_str, retval[HTTPCODE], resp.msg, str(resp.headers), retval[HTML])160except Exception as ex:161retval[URL] = getattr(ex, "url", url)162retval[HTTPCODE] = getattr(ex, "code", None)163try:164retval[HTML] = ex.read() if hasattr(ex, "read") else getattr(ex, "msg", str(ex))165except:166retval[HTML] = ""167retval[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])168169for encoding in re.findall(r"charset=[\s\"']?([\w-]+)", retval[RAW])[::-1] + ["utf8"]:170encoding = ENCODING_TRANSLATIONS.get(encoding, encoding)171try:172retval[HTML] = retval[HTML].decode(encoding, errors="replace")173break174except:175pass176177match = re.search(r"<title>\s*(?P<result>[^<]+?)\s*</title>", retval[HTML], re.I)178retval[TITLE] = match.group("result") if match and "result" in match.groupdict() else None179retval[TEXT] = re.sub(r"(?si)<script.+?</script>|<!--.+?-->|<style.+?</style>|<[^>]+>|\s+", " ", retval[HTML])180match = re.search(r"(?im)^Server: (.+)", retval[RAW])181retval[SERVER] = match.group(1).strip() if match else ""182return retval183184def calc_hash(value, binary=True):185value = value.encode("utf8") if not isinstance(value, bytes) else value186result = zlib.crc32(value) & 0xffff187if binary:188result = struct.pack(">H", result)189return result190191def single_print(message):192if message not in seen:193print(message)194seen.add(message)195196def check_payload(payload, protection_regex=GENERIC_PROTECTION_REGEX % '|'.join(GENERIC_PROTECTION_KEYWORDS)):197global chained198global heuristic199global intrusive200global locked_code201global locked_regex202203time.sleep(options.delay or 0)204if options.post:205_ = "%s=%s" % ("".join(random.sample(string.ascii_letters, 3)), quote(payload))206intrusive = retrieve(options.url, _)207else:208_ = "%s%s%s=%s" % (options.url, '?' if '?' not in options.url else '&', "".join(random.sample(string.ascii_letters, 3)), quote(payload))209intrusive = retrieve(_)210211if options.lock and not payload.isdigit():212if payload == HEURISTIC_PAYLOAD:213match = re.search(re.sub(r"Server:|Protected by", "".join(random.sample(string.ascii_letters, 6)), WAF_RECOGNITION_REGEX, flags=re.I), intrusive[RAW] or "")214if match:215result = True216217for _ in match.groupdict():218if match.group(_):219waf = re.sub(r"\Awaf_", "", _)220locked_regex = DATA_JSON["wafs"][waf]["regex"]221locked_code = intrusive[HTTPCODE]222break223else:224result = False225226if not result:227exit(colorize("[x] can't lock results to a non-blind match"))228else:229result = re.search(locked_regex, intrusive[RAW]) is not None and locked_code == intrusive[HTTPCODE]230elif options.string:231result = options.string in (intrusive[RAW] or "")232elif options.code:233result = options.code == intrusive[HTTPCODE]234else:235result = 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)236237if not payload.isdigit():238if result:239if options.debug:240print("\r---%s" % (40 * ' '))241print(payload)242print(intrusive[HTTPCODE], intrusive[RAW])243print("---")244245if intrusive[SERVER]:246servers.add(re.sub(r"\s*\(.+\)\Z", "", intrusive[SERVER]))247if len(servers) > 1:248chained = True249single_print(colorize("[!] multiple (reactive) rejection HTTP 'Server' headers detected (%s)" % ', '.join("'%s'" % _ for _ in sorted(servers))))250251if intrusive[HTTPCODE]:252codes.add(intrusive[HTTPCODE])253if len(codes) > 1:254chained = True255single_print(colorize("[!] multiple (reactive) rejection HTTP codes detected (%s)" % ', '.join("%s" % _ for _ in sorted(codes))))256257if heuristic and heuristic[HTML] and intrusive[HTML] and difflib.SequenceMatcher(a=heuristic[HTML] or "", b=intrusive[HTML] or "").quick_ratio() < QUICK_RATIO_THRESHOLD:258chained = True259single_print(colorize("[!] multiple (reactive) rejection HTML responses detected"))260261if payload == HEURISTIC_PAYLOAD:262heuristic = intrusive263264return result265266def colorize(message):267if COLORIZE:268message = re.sub(r"\[(.)\]", lambda match: "[%s%s\033[00;49m]" % (LEVEL_COLORS[match.group(1)], match.group(1)), message)269270if any(_ in message for _ in ("rejected summary", "challenge detected")):271for match in re.finditer(r"[^\w]'([^)]+)'" if "rejected summary" in message else r"\('(.+)'\)", message):272message = message.replace("'%s'" % match.group(1), "'\033[37m%s\033[00;49m'" % match.group(1), 1)273else:274for match in re.finditer(r"[^\w]'([^']+)'", message):275message = message.replace("'%s'" % match.group(1), "'\033[37m%s\033[00;49m'" % match.group(1), 1)276277if "blind match" in message:278for match in re.finditer(r"\(((\d+)%)\)", message):279message = 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)))280281if "hardness" in message:282for match in re.finditer(r"\(((\d+)%)\)", message):283message = 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)))284285return message286287def parse_args():288global options289290parser = optparse.OptionParser(version=VERSION)291parser.add_option("--delay", dest="delay", type=int, help="Delay (sec) between tests (default: 0)")292parser.add_option("--timeout", dest="timeout", type=int, help="Response timeout (sec) (default: 10)")293parser.add_option("--proxy", dest="proxy", help="HTTP proxy address (e.g. \"http://127.0.0.1:8080\")")294parser.add_option("--proxy-file", dest="proxy_file", help="Load (rotating) HTTP(s) proxy list from a file")295parser.add_option("--random-agent", dest="random_agent", action="store_true", help="Use random HTTP User-Agent header value")296parser.add_option("--code", dest="code", type=int, help="Expected HTTP code in rejected responses")297parser.add_option("--string", dest="string", help="Expected string in rejected responses")298parser.add_option("--post", dest="post", action="store_true", help="Use POST body for sending payloads")299parser.add_option("--debug", dest="debug", action="store_true", help=optparse.SUPPRESS_HELP)300parser.add_option("--fast", dest="fast", action="store_true", help=optparse.SUPPRESS_HELP)301parser.add_option("--lock", dest="lock", action="store_true", help=optparse.SUPPRESS_HELP)302303# Dirty hack(s) for help message304def _(self, *args):305retval = parser.formatter._format_option_strings(*args)306if len(retval) > MAX_HELP_OPTION_LENGTH:307retval = ("%%.%ds.." % (MAX_HELP_OPTION_LENGTH - parser.formatter.indent_increment)) % retval308return retval309310parser.usage = "python %s <host|url>" % parser.usage311parser.formatter._format_option_strings = parser.formatter.format_option_strings312parser.formatter.format_option_strings = type(parser.formatter.format_option_strings)(_, parser)313314for _ in ("-h", "--version"):315option = parser.get_option(_)316option.help = option.help.capitalize()317318try:319options, _ = parser.parse_args()320except SystemExit:321raise322323if len(sys.argv) > 1:324url = sys.argv[-1]325if not url.startswith("http"):326url = "http://%s" % url327options.url = url328else:329parser.print_help()330raise SystemExit331332for key in DEFAULTS:333if getattr(options, key, None) is None:334setattr(options, key, DEFAULTS[key])335336def load_data():337global WAF_RECOGNITION_REGEX338339if os.path.isfile(DATA_JSON_FILE):340with open(DATA_JSON_FILE, "r") as f:341DATA_JSON.update(json.load(f))342343WAF_RECOGNITION_REGEX = ""344for waf in DATA_JSON["wafs"]:345if DATA_JSON["wafs"][waf]["regex"]:346WAF_RECOGNITION_REGEX += "%s|" % ("(?P<waf_%s>%s)" % (waf, DATA_JSON["wafs"][waf]["regex"]))347for signature in DATA_JSON["wafs"][waf]["signatures"]:348SIGNATURES[signature] = waf349WAF_RECOGNITION_REGEX = WAF_RECOGNITION_REGEX.strip('|')350351flags = "".join(set(_ for _ in "".join(re.findall(r"\(\?(\w+)\)", WAF_RECOGNITION_REGEX))))352WAF_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.7353else:354exit(colorize("[x] file '%s' is missing" % DATA_JSON_FILE))355356def init():357os.chdir(os.path.abspath(os.path.dirname(__file__)))358359# Reference: http://blog.mathieu-leplatre.info/python-utf-8-print-fails-when-redirecting-stdout.html360if not PY3 and not IS_TTY:361sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout)362363print(colorize("[o] initializing handlers..."))364365# Reference: https://stackoverflow.com/a/28052583366if hasattr(ssl, "_create_unverified_context"):367ssl._create_default_https_context = ssl._create_unverified_context368369if options.proxy_file:370if os.path.isfile(options.proxy_file):371print(colorize("[o] loading proxy list..."))372373with open(options.proxy_file, "r") as f:374proxies.extend(re.sub(r"\s.*", "", _.strip()) for _ in f.read().strip().split('\n') if _.startswith("http"))375random.shuffle(proxies)376else:377exit(colorize("[x] file '%s' does not exist" % options.proxy_file))378379380cookie_jar = CookieJar()381opener = build_opener(HTTPCookieProcessor(cookie_jar))382install_opener(opener)383384if options.proxy:385opener = build_opener(ProxyHandler({"http": options.proxy, "https": options.proxy}))386install_opener(opener)387388if options.random_agent:389revision = random.randint(20, 64)390platform = 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]391user_agent = "Mozilla/5.0 (%s; rv:%d.0) Gecko/20100101 Firefox/%d.0" % (platform, revision, revision)392HEADERS["User-Agent"] = user_agent393394def format_name(waf):395return "%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 "")396397def non_blind_check(raw, silent=False):398retval = False399match = re.search(WAF_RECOGNITION_REGEX, raw or "")400if match:401retval = True402for _ in match.groupdict():403if match.group(_):404waf = re.sub(r"\Awaf_", "", _)405non_blind.add(waf)406if not silent:407single_print(colorize("[+] non-blind match: '%s'%s" % (format_name(waf), 20 * ' ')))408return retval409410def run():411global original412413hostname = options.url.split("//")[-1].split('/')[0].split(':')[0]414415if not hostname.replace('.', "").isdigit():416print(colorize("[i] checking hostname '%s'..." % hostname))417try:418socket.getaddrinfo(hostname, None)419except socket.gaierror:420exit(colorize("[x] host '%s' does not exist" % hostname))421422results = ""423signature = b""424counter = 0425original = retrieve(options.url)426427if 300 <= (original[HTTPCODE] or 0) < 400 and original[URL]:428original = retrieve(original[URL])429430options.url = original[URL]431432if original[HTTPCODE] is None:433exit(colorize("[x] missing valid response"))434435if not any((options.string, options.code)) and original[HTTPCODE] >= 400:436non_blind_check(original[RAW])437if options.debug:438print("\r---%s" % (40 * ' '))439print(original[HTTPCODE], original[RAW])440print("---")441exit(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 "")))442443challenge = None444if all(_ in original[HTML].lower() for _ in ("eval", "<script")):445match = re.search(r"(?is)<body[^>]*>(.*)</body>", re.sub(r"(?is)<script.+?</script>", "", original[HTML]))446if re.search(r"(?i)<(body|div)", original[HTML]) is None or (match and len(match.group(1)) == 0):447challenge = re.search(r"(?is)<script.+</script>", original[HTML]).group(0).replace("\n", "\\n")448print(colorize("[x] anti-robot JS challenge detected ('%s%s')" % (challenge[:MAX_JS_CHALLENGE_SNAPLEN], "..." if len(challenge) > MAX_JS_CHALLENGE_SNAPLEN else "")))449450protection_keywords = GENERIC_PROTECTION_KEYWORDS451protection_regex = GENERIC_PROTECTION_REGEX % '|'.join(keyword for keyword in protection_keywords if keyword not in original[HTML].lower())452453print(colorize("[i] running basic heuristic test..."))454if not check_payload(HEURISTIC_PAYLOAD):455check = False456if options.url.startswith("https://"):457options.url = options.url.replace("https://", "http://")458check = check_payload(HEURISTIC_PAYLOAD)459if not check:460if non_blind_check(intrusive[RAW]):461exit(colorize("[x] unable to continue due to static responses%s" % (" (captcha)" if re.search(r"(?i)captcha", intrusive[RAW]) is not None else "")))462elif challenge is None:463exit(colorize("[x] host '%s' does not seem to be protected" % hostname))464else:465exit(colorize("[x] response not changing without JS challenge solved"))466467if options.fast and not non_blind:468exit(colorize("[x] fast exit because of missing non-blind match"))469470if not intrusive[HTTPCODE]:471print(colorize("[i] rejected summary: RST|DROP"))472else:473_ = "...".join(match.group(0) for match in re.finditer(GENERIC_ERROR_MESSAGE_REGEX, intrusive[HTML])).strip().replace(" ", " ")474print(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(" ('')", "")))475476found = non_blind_check(intrusive[RAW] if intrusive[HTTPCODE] is not None else original[RAW])477478if not found:479print(colorize("[-] non-blind match: -"))480481for item in DATA_JSON["payloads"]:482info, payload = item.split("::", 1)483counter += 1484485if IS_TTY:486sys.stdout.write(colorize("\r[i] running payload tests... (%d/%d)\r" % (counter, len(DATA_JSON["payloads"]))))487sys.stdout.flush()488489if counter % VERIFY_OK_INTERVAL == 0:490for i in xrange(VERIFY_RETRY_TIMES):491if not check_payload(str(random.randint(1, 9)), protection_regex):492break493elif i == VERIFY_RETRY_TIMES - 1:494exit(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 "")))495else:496time.sleep(5)497498last = check_payload(payload, protection_regex)499non_blind_check(intrusive[RAW])500signature += struct.pack(">H", ((calc_hash(payload, binary=False) << 1) | last) & 0xffff)501results += 'x' if last else '.'502503if last and info not in blocked:504blocked.append(info)505506_ = calc_hash(signature)507signature = "%s:%s" % (_.encode("hex") if not hasattr(_, "hex") else _.hex(), base64.b64encode(signature).decode("ascii"))508509print(colorize("%s[=] results: '%s'" % ("\n" if IS_TTY else "", results)))510511hardness = 100 * results.count('x') // len(results)512print(colorize("[=] hardness: %s (%d%%)" % ("insane" if hardness >= 80 else ("hard" if hardness >= 50 else ("moderate" if hardness >= 30 else "easy")), hardness)))513514if blocked:515print(colorize("[=] blocked categories: %s" % ", ".join(blocked)))516517if not results.strip('.') or not results.strip('x'):518print(colorize("[-] blind match: -"))519520if re.search(r"(?i)captcha", original[HTML]) is not None:521exit(colorize("[x] there seems to be an activated captcha"))522else:523print(colorize("[=] signature: '%s'" % signature))524525if signature in SIGNATURES:526waf = SIGNATURES[signature]527print(colorize("[+] blind match: '%s' (100%%)" % format_name(waf)))528elif results.count('x') < MIN_MATCH_PARTIAL:529print(colorize("[-] blind match: -"))530else:531matches = {}532markers = set()533decoded = base64.b64decode(signature.split(':')[-1])534for i in xrange(0, len(decoded), 2):535part = struct.unpack(">H", decoded[i: i + 2])[0]536markers.add(part)537538for candidate in SIGNATURES:539counter_y, counter_n = 0, 0540decoded = base64.b64decode(candidate.split(':')[-1])541for i in xrange(0, len(decoded), 2):542part = struct.unpack(">H", decoded[i: i + 2])[0]543if part in markers:544counter_y += 1545elif any(_ in markers for _ in (part & ~1, part | 1)):546counter_n += 1547result = int(round(100.0 * counter_y / (counter_y + counter_n)))548if SIGNATURES[candidate] in matches:549if result > matches[SIGNATURES[candidate]]:550matches[SIGNATURES[candidate]] = result551else:552matches[SIGNATURES[candidate]] = result553554if chained:555for _ in list(matches.keys()):556if matches[_] < 90:557del matches[_]558559if not matches:560print(colorize("[-] blind match: - "))561print(colorize("[!] probably chained web protection systems"))562else:563matches = [(_[1], _[0]) for _ in matches.items()]564matches.sort(reverse=True)565566print(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))))567568print()569570def main():571if "--version" not in sys.argv:572print(BANNER)573574parse_args()575init()576run()577578load_data()579580if __name__ == "__main__":581try:582main()583except KeyboardInterrupt:584exit(colorize("\r[x] Ctrl-C pressed"))585586587