Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sqlmapproject
GitHub Repository: sqlmapproject/sqlmap
Path: blob/master/thirdparty/identywaf/identYwaf.py
2992 views
1
#!/usr/bin/env python
2
3
"""
4
Copyright (c) 2019-2021 Miroslav Stampar (@stamparm), MIT
5
See the file 'LICENSE' for copying permission
6
7
The above copyright notice and this permission notice shall be included in
8
all copies or substantial portions of the Software.
9
"""
10
11
from __future__ import print_function
12
13
import base64
14
import codecs
15
import difflib
16
import json
17
import locale
18
import optparse
19
import os
20
import random
21
import re
22
import ssl
23
import socket
24
import string
25
import struct
26
import sys
27
import time
28
import zlib
29
30
PY3 = sys.version_info >= (3, 0)
31
32
if PY3:
33
import http.cookiejar
34
import http.client as httplib
35
import urllib.request
36
37
build_opener = urllib.request.build_opener
38
install_opener = urllib.request.install_opener
39
quote = urllib.parse.quote
40
urlopen = urllib.request.urlopen
41
CookieJar = http.cookiejar.CookieJar
42
ProxyHandler = urllib.request.ProxyHandler
43
Request = urllib.request.Request
44
HTTPCookieProcessor = urllib.request.HTTPCookieProcessor
45
46
xrange = range
47
else:
48
import cookielib
49
import httplib
50
import urllib
51
import urllib2
52
53
build_opener = urllib2.build_opener
54
install_opener = urllib2.install_opener
55
quote = urllib.quote
56
urlopen = urllib2.urlopen
57
CookieJar = cookielib.CookieJar
58
ProxyHandler = urllib2.ProxyHandler
59
Request = urllib2.Request
60
HTTPCookieProcessor = urllib2.HTTPCookieProcessor
61
62
NAME = "identYwaf"
63
VERSION = "1.0.131"
64
BANNER = r"""
65
` __ __ `
66
____ ___ ___ ____ ______ `| T T` __ __ ____ _____
67
l j| \ / _]| \ | T`| | |`| T__T T / T| __|
68
| T | \ / [_ | _ Yl_j l_j`| ~ |`| | | |Y o || l_
69
| | | D YY _]| | | | | `|___ |`| | | || || _|
70
j l | || [_ | | | | | `| !` \ / | | || ]
71
|____jl_____jl_____jl__j__j l__j `l____/ ` \_/\_/ l__j__jl__j (%s)%s""".strip("\n") % (VERSION, "\n")
72
73
RAW, TEXT, HTTPCODE, SERVER, TITLE, HTML, URL = xrange(7)
74
COOKIE, UA, REFERER = "Cookie", "User-Agent", "Referer"
75
GET, POST = "GET", "POST"
76
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")
77
GENERIC_PROTECTION_REGEX = r"(?i)\b(%s)\b"
78
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, '!-]*"
79
WAF_RECOGNITION_REGEX = None
80
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
81
PAYLOADS = []
82
SIGNATURES = {}
83
DATA_JSON = {}
84
DATA_JSON_FILE = os.path.join(os.path.dirname(__file__), "data.json")
85
MAX_HELP_OPTION_LENGTH = 18
86
IS_TTY = sys.stdout.isatty()
87
IS_WIN = os.name == "nt"
88
COLORIZE = not IS_WIN and IS_TTY
89
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"}
90
VERIFY_OK_INTERVAL = 5
91
VERIFY_RETRY_TIMES = 3
92
MIN_MATCH_PARTIAL = 5
93
DEFAULTS = {"timeout": 10}
94
MAX_MATCHES = 5
95
QUICK_RATIO_THRESHOLD = 0.2
96
MAX_JS_CHALLENGE_SNAPLEN = 120
97
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
98
PROXY_TESTING_PAGE = "https://myexternalip.com/raw"
99
100
if COLORIZE:
101
for _ in re.findall(r"`.+?`", BANNER):
102
BANNER = BANNER.replace(_, "\033[01;92m%s\033[00;49m" % _.strip('`'))
103
for _ in re.findall(r" [Do] ", BANNER):
104
BANNER = BANNER.replace(_, "\033[01;93m%s\033[00;49m" % _.strip('`'))
105
BANNER = re.sub(VERSION, r"\033[01;91m%s\033[00;49m" % VERSION, BANNER)
106
else:
107
BANNER = BANNER.replace('`', "")
108
109
_ = random.randint(20, 64)
110
DEFAULT_USER_AGENT = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; %s; rv:%d.0) Gecko/20100101 Firefox/%d.0" % (NAME, _, _)
111
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"}
112
113
original = None
114
options = None
115
intrusive = None
116
heuristic = None
117
chained = False
118
locked_code = None
119
locked_regex = None
120
non_blind = set()
121
seen = set()
122
blocked = []
123
servers = set()
124
codes = set()
125
proxies = list()
126
proxies_index = 0
127
128
_exit = sys.exit
129
130
def exit(message=None):
131
if message:
132
print("%s%s" % (message, ' ' * 20))
133
_exit(1)
134
135
def retrieve(url, data=None):
136
global proxies_index
137
138
retval = {}
139
140
if proxies:
141
while True:
142
try:
143
opener = build_opener(ProxyHandler({"http": proxies[proxies_index], "https": proxies[proxies_index]}))
144
install_opener(opener)
145
proxies_index = (proxies_index + 1) % len(proxies)
146
urlopen(PROXY_TESTING_PAGE).read()
147
except KeyboardInterrupt:
148
raise
149
except:
150
pass
151
else:
152
break
153
154
try:
155
req = Request("".join(url[_].replace(' ', "%20") if _ > url.find('?') else url[_] for _ in xrange(len(url))), data, HEADERS)
156
resp = urlopen(req, timeout=options.timeout)
157
retval[URL] = resp.url
158
retval[HTML] = resp.read()
159
retval[HTTPCODE] = resp.code
160
retval[RAW] = "%s %d %s\n%s\n%s" % (httplib.HTTPConnection._http_vsn_str, retval[HTTPCODE], resp.msg, str(resp.headers), retval[HTML])
161
except Exception as ex:
162
retval[URL] = getattr(ex, "url", url)
163
retval[HTTPCODE] = getattr(ex, "code", None)
164
try:
165
retval[HTML] = ex.read() if hasattr(ex, "read") else getattr(ex, "msg", str(ex))
166
except:
167
retval[HTML] = ""
168
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])
169
170
for encoding in re.findall(r"charset=[\s\"']?([\w-]+)", retval[RAW])[::-1] + ["utf8"]:
171
encoding = ENCODING_TRANSLATIONS.get(encoding, encoding)
172
try:
173
retval[HTML] = retval[HTML].decode(encoding, errors="replace")
174
break
175
except:
176
pass
177
178
match = re.search(r"<title>\s*(?P<result>[^<]+?)\s*</title>", retval[HTML], re.I)
179
retval[TITLE] = match.group("result") if match and "result" in match.groupdict() else None
180
retval[TEXT] = re.sub(r"(?si)<script.+?</script>|<!--.+?-->|<style.+?</style>|<[^>]+>|\s+", " ", retval[HTML])
181
match = re.search(r"(?im)^Server: (.+)", retval[RAW])
182
retval[SERVER] = match.group(1).strip() if match else ""
183
return retval
184
185
def calc_hash(value, binary=True):
186
value = value.encode("utf8") if not isinstance(value, bytes) else value
187
result = zlib.crc32(value) & 0xffff
188
if binary:
189
result = struct.pack(">H", result)
190
return result
191
192
def single_print(message):
193
if message not in seen:
194
print(message)
195
seen.add(message)
196
197
def check_payload(payload, protection_regex=GENERIC_PROTECTION_REGEX % '|'.join(GENERIC_PROTECTION_KEYWORDS)):
198
global chained
199
global heuristic
200
global intrusive
201
global locked_code
202
global locked_regex
203
204
time.sleep(options.delay or 0)
205
if options.post:
206
_ = "%s=%s" % ("".join(random.sample(string.ascii_letters, 3)), quote(payload))
207
intrusive = retrieve(options.url, _)
208
else:
209
_ = "%s%s%s=%s" % (options.url, '?' if '?' not in options.url else '&', "".join(random.sample(string.ascii_letters, 3)), quote(payload))
210
intrusive = retrieve(_)
211
212
if options.lock and not payload.isdigit():
213
if payload == HEURISTIC_PAYLOAD:
214
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 "")
215
if match:
216
result = True
217
218
for _ in match.groupdict():
219
if match.group(_):
220
waf = re.sub(r"\Awaf_", "", _)
221
locked_regex = DATA_JSON["wafs"][waf]["regex"]
222
locked_code = intrusive[HTTPCODE]
223
break
224
else:
225
result = False
226
227
if not result:
228
exit(colorize("[x] can't lock results to a non-blind match"))
229
else:
230
result = re.search(locked_regex, intrusive[RAW]) is not None and locked_code == intrusive[HTTPCODE]
231
elif options.string:
232
result = options.string in (intrusive[RAW] or "")
233
elif options.code:
234
result = options.code == intrusive[HTTPCODE]
235
else:
236
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)
237
238
if not payload.isdigit():
239
if result:
240
if options.debug:
241
print("\r---%s" % (40 * ' '))
242
print(payload)
243
print(intrusive[HTTPCODE], intrusive[RAW])
244
print("---")
245
246
if intrusive[SERVER]:
247
servers.add(re.sub(r"\s*\(.+\)\Z", "", intrusive[SERVER]))
248
if len(servers) > 1:
249
chained = True
250
single_print(colorize("[!] multiple (reactive) rejection HTTP 'Server' headers detected (%s)" % ', '.join("'%s'" % _ for _ in sorted(servers))))
251
252
if intrusive[HTTPCODE]:
253
codes.add(intrusive[HTTPCODE])
254
if len(codes) > 1:
255
chained = True
256
single_print(colorize("[!] multiple (reactive) rejection HTTP codes detected (%s)" % ', '.join("%s" % _ for _ in sorted(codes))))
257
258
if heuristic and heuristic[HTML] and intrusive[HTML] and difflib.SequenceMatcher(a=heuristic[HTML] or "", b=intrusive[HTML] or "").quick_ratio() < QUICK_RATIO_THRESHOLD:
259
chained = True
260
single_print(colorize("[!] multiple (reactive) rejection HTML responses detected"))
261
262
if payload == HEURISTIC_PAYLOAD:
263
heuristic = intrusive
264
265
return result
266
267
def colorize(message):
268
if COLORIZE:
269
message = re.sub(r"\[(.)\]", lambda match: "[%s%s\033[00;49m]" % (LEVEL_COLORS[match.group(1)], match.group(1)), message)
270
271
if any(_ in message for _ in ("rejected summary", "challenge detected")):
272
for match in re.finditer(r"[^\w]'([^)]+)'" if "rejected summary" in message else r"\('(.+)'\)", message):
273
message = message.replace("'%s'" % match.group(1), "'\033[37m%s\033[00;49m'" % match.group(1), 1)
274
else:
275
for match in re.finditer(r"[^\w]'([^']+)'", message):
276
message = message.replace("'%s'" % match.group(1), "'\033[37m%s\033[00;49m'" % match.group(1), 1)
277
278
if "blind match" in message:
279
for match in re.finditer(r"\(((\d+)%)\)", message):
280
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)))
281
282
if "hardness" in message:
283
for match in re.finditer(r"\(((\d+)%)\)", message):
284
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)))
285
286
return message
287
288
def parse_args():
289
global options
290
291
parser = optparse.OptionParser(version=VERSION)
292
parser.add_option("--delay", dest="delay", type=int, help="Delay (sec) between tests (default: 0)")
293
parser.add_option("--timeout", dest="timeout", type=int, help="Response timeout (sec) (default: 10)")
294
parser.add_option("--proxy", dest="proxy", help="HTTP proxy address (e.g. \"http://127.0.0.1:8080\")")
295
parser.add_option("--proxy-file", dest="proxy_file", help="Load (rotating) HTTP(s) proxy list from a file")
296
parser.add_option("--random-agent", dest="random_agent", action="store_true", help="Use random HTTP User-Agent header value")
297
parser.add_option("--code", dest="code", type=int, help="Expected HTTP code in rejected responses")
298
parser.add_option("--string", dest="string", help="Expected string in rejected responses")
299
parser.add_option("--post", dest="post", action="store_true", help="Use POST body for sending payloads")
300
parser.add_option("--debug", dest="debug", action="store_true", help=optparse.SUPPRESS_HELP)
301
parser.add_option("--fast", dest="fast", action="store_true", help=optparse.SUPPRESS_HELP)
302
parser.add_option("--lock", dest="lock", action="store_true", help=optparse.SUPPRESS_HELP)
303
304
# Dirty hack(s) for help message
305
def _(self, *args):
306
retval = parser.formatter._format_option_strings(*args)
307
if len(retval) > MAX_HELP_OPTION_LENGTH:
308
retval = ("%%.%ds.." % (MAX_HELP_OPTION_LENGTH - parser.formatter.indent_increment)) % retval
309
return retval
310
311
parser.usage = "python %s <host|url>" % parser.usage
312
parser.formatter._format_option_strings = parser.formatter.format_option_strings
313
parser.formatter.format_option_strings = type(parser.formatter.format_option_strings)(_, parser)
314
315
for _ in ("-h", "--version"):
316
option = parser.get_option(_)
317
option.help = option.help.capitalize()
318
319
try:
320
options, _ = parser.parse_args()
321
except SystemExit:
322
raise
323
324
if len(sys.argv) > 1:
325
url = sys.argv[-1]
326
if not url.startswith("http"):
327
url = "http://%s" % url
328
options.url = url
329
else:
330
parser.print_help()
331
raise SystemExit
332
333
for key in DEFAULTS:
334
if getattr(options, key, None) is None:
335
setattr(options, key, DEFAULTS[key])
336
337
def load_data():
338
global WAF_RECOGNITION_REGEX
339
340
if os.path.isfile(DATA_JSON_FILE):
341
with open(DATA_JSON_FILE, "r") as f:
342
DATA_JSON.update(json.load(f))
343
344
WAF_RECOGNITION_REGEX = ""
345
for waf in DATA_JSON["wafs"]:
346
if DATA_JSON["wafs"][waf]["regex"]:
347
WAF_RECOGNITION_REGEX += "%s|" % ("(?P<waf_%s>%s)" % (waf, DATA_JSON["wafs"][waf]["regex"]))
348
for signature in DATA_JSON["wafs"][waf]["signatures"]:
349
SIGNATURES[signature] = waf
350
WAF_RECOGNITION_REGEX = WAF_RECOGNITION_REGEX.strip('|')
351
352
flags = "".join(set(_ for _ in "".join(re.findall(r"\(\?(\w+)\)", WAF_RECOGNITION_REGEX))))
353
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
354
else:
355
exit(colorize("[x] file '%s' is missing" % DATA_JSON_FILE))
356
357
def init():
358
os.chdir(os.path.abspath(os.path.dirname(__file__)))
359
360
# Reference: http://blog.mathieu-leplatre.info/python-utf-8-print-fails-when-redirecting-stdout.html
361
if not PY3 and not IS_TTY:
362
sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout)
363
364
print(colorize("[o] initializing handlers..."))
365
366
# Reference: https://stackoverflow.com/a/28052583
367
if hasattr(ssl, "_create_unverified_context"):
368
ssl._create_default_https_context = ssl._create_unverified_context
369
370
if options.proxy_file:
371
if os.path.isfile(options.proxy_file):
372
print(colorize("[o] loading proxy list..."))
373
374
with open(options.proxy_file, "r") as f:
375
proxies.extend(re.sub(r"\s.*", "", _.strip()) for _ in f.read().strip().split('\n') if _.startswith("http"))
376
random.shuffle(proxies)
377
else:
378
exit(colorize("[x] file '%s' does not exist" % options.proxy_file))
379
380
381
cookie_jar = CookieJar()
382
opener = build_opener(HTTPCookieProcessor(cookie_jar))
383
install_opener(opener)
384
385
if options.proxy:
386
opener = build_opener(ProxyHandler({"http": options.proxy, "https": options.proxy}))
387
install_opener(opener)
388
389
if options.random_agent:
390
revision = random.randint(20, 64)
391
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]
392
user_agent = "Mozilla/5.0 (%s; rv:%d.0) Gecko/20100101 Firefox/%d.0" % (platform, revision, revision)
393
HEADERS["User-Agent"] = user_agent
394
395
def format_name(waf):
396
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 "")
397
398
def non_blind_check(raw, silent=False):
399
retval = False
400
match = re.search(WAF_RECOGNITION_REGEX, raw or "")
401
if match:
402
retval = True
403
for _ in match.groupdict():
404
if match.group(_):
405
waf = re.sub(r"\Awaf_", "", _)
406
non_blind.add(waf)
407
if not silent:
408
single_print(colorize("[+] non-blind match: '%s'%s" % (format_name(waf), 20 * ' ')))
409
return retval
410
411
def run():
412
global original
413
414
hostname = options.url.split("//")[-1].split('/')[0].split(':')[0]
415
416
if not hostname.replace('.', "").isdigit():
417
print(colorize("[i] checking hostname '%s'..." % hostname))
418
try:
419
socket.getaddrinfo(hostname, None)
420
except socket.gaierror:
421
exit(colorize("[x] host '%s' does not exist" % hostname))
422
423
results = ""
424
signature = b""
425
counter = 0
426
original = retrieve(options.url)
427
428
if 300 <= (original[HTTPCODE] or 0) < 400 and original[URL]:
429
original = retrieve(original[URL])
430
431
options.url = original[URL]
432
433
if original[HTTPCODE] is None:
434
exit(colorize("[x] missing valid response"))
435
436
if not any((options.string, options.code)) and original[HTTPCODE] >= 400:
437
non_blind_check(original[RAW])
438
if options.debug:
439
print("\r---%s" % (40 * ' '))
440
print(original[HTTPCODE], original[RAW])
441
print("---")
442
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 "")))
443
444
challenge = None
445
if all(_ in original[HTML].lower() for _ in ("eval", "<script")):
446
match = re.search(r"(?is)<body[^>]*>(.*)</body>", re.sub(r"(?is)<script.+?</script>", "", original[HTML]))
447
if re.search(r"(?i)<(body|div)", original[HTML]) is None or (match and len(match.group(1)) == 0):
448
challenge = re.search(r"(?is)<script.+</script>", original[HTML]).group(0).replace("\n", "\\n")
449
print(colorize("[x] anti-robot JS challenge detected ('%s%s')" % (challenge[:MAX_JS_CHALLENGE_SNAPLEN], "..." if len(challenge) > MAX_JS_CHALLENGE_SNAPLEN else "")))
450
451
protection_keywords = GENERIC_PROTECTION_KEYWORDS
452
protection_regex = GENERIC_PROTECTION_REGEX % '|'.join(keyword for keyword in protection_keywords if keyword not in original[HTML].lower())
453
454
print(colorize("[i] running basic heuristic test..."))
455
if not check_payload(HEURISTIC_PAYLOAD):
456
check = False
457
if options.url.startswith("https://"):
458
options.url = options.url.replace("https://", "http://")
459
check = check_payload(HEURISTIC_PAYLOAD)
460
if not check:
461
if non_blind_check(intrusive[RAW]):
462
exit(colorize("[x] unable to continue due to static responses%s" % (" (captcha)" if re.search(r"(?i)captcha", intrusive[RAW]) is not None else "")))
463
elif challenge is None:
464
exit(colorize("[x] host '%s' does not seem to be protected" % hostname))
465
else:
466
exit(colorize("[x] response not changing without JS challenge solved"))
467
468
if options.fast and not non_blind:
469
exit(colorize("[x] fast exit because of missing non-blind match"))
470
471
if not intrusive[HTTPCODE]:
472
print(colorize("[i] rejected summary: RST|DROP"))
473
else:
474
_ = "...".join(match.group(0) for match in re.finditer(GENERIC_ERROR_MESSAGE_REGEX, intrusive[HTML])).strip().replace(" ", " ")
475
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(" ('')", "")))
476
477
found = non_blind_check(intrusive[RAW] if intrusive[HTTPCODE] is not None else original[RAW])
478
479
if not found:
480
print(colorize("[-] non-blind match: -"))
481
482
for item in DATA_JSON["payloads"]:
483
info, payload = item.split("::", 1)
484
counter += 1
485
486
if IS_TTY:
487
sys.stdout.write(colorize("\r[i] running payload tests... (%d/%d)\r" % (counter, len(DATA_JSON["payloads"]))))
488
sys.stdout.flush()
489
490
if counter % VERIFY_OK_INTERVAL == 0:
491
for i in xrange(VERIFY_RETRY_TIMES):
492
if not check_payload(str(random.randint(1, 9)), protection_regex):
493
break
494
elif i == VERIFY_RETRY_TIMES - 1:
495
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 "")))
496
else:
497
time.sleep(5)
498
499
last = check_payload(payload, protection_regex)
500
non_blind_check(intrusive[RAW])
501
signature += struct.pack(">H", ((calc_hash(payload, binary=False) << 1) | last) & 0xffff)
502
results += 'x' if last else '.'
503
504
if last and info not in blocked:
505
blocked.append(info)
506
507
_ = calc_hash(signature)
508
signature = "%s:%s" % (_.encode("hex") if not hasattr(_, "hex") else _.hex(), base64.b64encode(signature).decode("ascii"))
509
510
print(colorize("%s[=] results: '%s'" % ("\n" if IS_TTY else "", results)))
511
512
hardness = 100 * results.count('x') // len(results)
513
print(colorize("[=] hardness: %s (%d%%)" % ("insane" if hardness >= 80 else ("hard" if hardness >= 50 else ("moderate" if hardness >= 30 else "easy")), hardness)))
514
515
if blocked:
516
print(colorize("[=] blocked categories: %s" % ", ".join(blocked)))
517
518
if not results.strip('.') or not results.strip('x'):
519
print(colorize("[-] blind match: -"))
520
521
if re.search(r"(?i)captcha", original[HTML]) is not None:
522
exit(colorize("[x] there seems to be an activated captcha"))
523
else:
524
print(colorize("[=] signature: '%s'" % signature))
525
526
if signature in SIGNATURES:
527
waf = SIGNATURES[signature]
528
print(colorize("[+] blind match: '%s' (100%%)" % format_name(waf)))
529
elif results.count('x') < MIN_MATCH_PARTIAL:
530
print(colorize("[-] blind match: -"))
531
else:
532
matches = {}
533
markers = set()
534
decoded = base64.b64decode(signature.split(':')[-1])
535
for i in xrange(0, len(decoded), 2):
536
part = struct.unpack(">H", decoded[i: i + 2])[0]
537
markers.add(part)
538
539
for candidate in SIGNATURES:
540
counter_y, counter_n = 0, 0
541
decoded = base64.b64decode(candidate.split(':')[-1])
542
for i in xrange(0, len(decoded), 2):
543
part = struct.unpack(">H", decoded[i: i + 2])[0]
544
if part in markers:
545
counter_y += 1
546
elif any(_ in markers for _ in (part & ~1, part | 1)):
547
counter_n += 1
548
result = int(round(100.0 * counter_y / (counter_y + counter_n)))
549
if SIGNATURES[candidate] in matches:
550
if result > matches[SIGNATURES[candidate]]:
551
matches[SIGNATURES[candidate]] = result
552
else:
553
matches[SIGNATURES[candidate]] = result
554
555
if chained:
556
for _ in list(matches.keys()):
557
if matches[_] < 90:
558
del matches[_]
559
560
if not matches:
561
print(colorize("[-] blind match: - "))
562
print(colorize("[!] probably chained web protection systems"))
563
else:
564
matches = [(_[1], _[0]) for _ in matches.items()]
565
matches.sort(reverse=True)
566
567
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))))
568
569
print()
570
571
def main():
572
if "--version" not in sys.argv:
573
print(BANNER)
574
575
parse_args()
576
init()
577
run()
578
579
load_data()
580
581
if __name__ == "__main__":
582
try:
583
main()
584
except KeyboardInterrupt:
585
exit(colorize("\r[x] Ctrl-C pressed"))
586
587