initial push for issue #142, created a few WAF scripts to detect, will also save the fingerprint of the WAF script if the protection is declared to be generic

This commit is contained in:
ekultek 2017-11-12 19:13:00 -06:00
parent 1eb861ae16
commit f2cad88415
12 changed files with 269 additions and 14 deletions

View file

@ -21,6 +21,13 @@ be18faeea6e7db9db6990d8667e2298f ./bin/drivers/geckodriver-v0.17.0-linux64.tar.
ca6935a72fd0527d15a78a17a35e56e8 ./bin/drivers/geckodriver-v0.19.0-linux64.tar.gz
4ccb56fb3700005c9f9188f84152f21a ./bin/drivers/geckodriver-v0.18.0-linux64.tar.gz
07cd383c8aef8ea5ef194a506141afd6 ./bin/drivers/geckodriver-v0.19.0-linux32.tar.gz
6ea65a0160c21e144e92334acc2e3667 ./lib/firewall/anquanbao.py
34b946ab1f9aaac397ba77d5f8c132b1 ./lib/firewall/cloudflare.py
1b5a2a7161db77ba570a629d25cb4055 ./lib/firewall/modsecurity.py
6b370050b40d8c1d2221424f756c7842 ./lib/firewall/paloalto.py
60973a0c2e34108dfb32c89ad46477b6 ./lib/firewall/sucuri.py
ea6e622b8eaf83b4f717020b91530e71 ./lib/firewall/webseal.py
783973a4c6af58907f6dbfe1b274c59c ./lib/firewall/generic.py
785c28da8b681a7e23964f99118b5aab ./lib/tamper_scripts/obfuscateordinal_encode.py
10bf1bc4ef0287d31633148fab557e8a ./lib/tamper_scripts/uppercase_encode.py
5b68de0ce3a783b870921b09b5222146 ./lib/tamper_scripts/hex_encode.py
@ -45,13 +52,13 @@ aa7268a8f085734a6c577c86440f7a1b ./lib/attacks/sqlmap_scan/sqlmap_opts.py
d41d8cd98f00b204e9800998ecf8427e ./lib/attacks/whois_lookup/__init__.py
d8fab18b15d1546f6585fe926c27868f ./lib/attacks/whois_lookup/whois.py
3917cdce61918f3992fc682105367ce8 ./lib/attacks/admin_panel_finder/__init__.py
77a8c4b040680cc00fcfe7924b1a4b1b ./lib/attacks/xss_scan/__init__.py
b50e4b7fb9fdef374c00b7ab0ef913e9 ./lib/attacks/xss_scan/__init__.py
27358f26bda30d7356143c3ea1fa99c5 ./lib/attacks/nmap_scan/__init__.py
21faf4679cdeaa731029a48f8963d6e7 ./lib/attacks/nmap_scan/nmap_opts.py
1faa2b5dfad6eb538bbfe42942d2a9da ./lib/core/errors.py
d41d8cd98f00b204e9800998ecf8427e ./lib/core/__init__.py
3dbc63c72d9e6b75630899561f333ab4 ./lib/core/settings.py
ad6622a5170e4ec74b5172f12ddcba9f ./lib/header_check/__init__.py
bb55719f0ac5853a4bef43a76378348f ./lib/core/settings.py
e25de81c51e5572d29e3e161dd35f41d ./lib/header_check/__init__.py
d41d8cd98f00b204e9800998ecf8427e ./var/google_search/__init__.py
670fb7fa0c618ce9712e0f73f7752925 ./var/google_search/search.py
d41d8cd98f00b204e9800998ecf8427e ./var/__init__.py

View file

@ -1,6 +1,5 @@
import os
import re
import threading
import tempfile
import importlib
try:
@ -118,9 +117,9 @@ def main_xss(start_url, proxy=None, agent=None, **kwargs):
"""
main attack method to be called
"""
batch = kwargs.get("batch", False)
tamper = kwargs.get("tamper", None)
verbose = kwargs.get("verbose", False)
batch = kwargs.get("batch", False)
if tamper:
lib.core.settings.logger.info(lib.core.settings.set_color(

View file

@ -47,12 +47,15 @@ except NameError:
# get the master patch ID when a patch is pushed to the program
PATCH_ID = str(subprocess.check_output(["git", "rev-parse", "origin/master"]))[:6]
# clone link
# clone link
CLONE = "https://github.com/ekultek/zeus-scanner.git"
# issue link
ISSUE_LINK = "https://github.com/ekultek/zeus-scanner/issues"
# current version <major.minor.commit.patch ID>
VERSION = "1.2.7.{}".format(PATCH_ID)
VERSION = "1.2.8".format(PATCH_ID)
# colors to output depending on the version
VERSION_TYPE_COLORS = {"dev": 33, "stable": 92, "other": 30}
@ -159,6 +162,9 @@ SPIDER_LOG_PATH = "{}/log/blackwidow-log".format(os.getcwd())
# cookies log path
COOKIE_LOG_PATH = "{}/log/cookies".format(os.getcwd())
# unknown firewall log path
UNKNOWN_FIREWALL_FINGERPRINT_PATH = "{}/log/unknown-firewall".format(os.getcwd())
# the current log file being used
CURRENT_LOG_FILE_PATH = "{}/log".format(os.getcwd())
@ -180,6 +186,16 @@ URL_REGEX = re.compile(r"((https?):((//)|(\\\\))+([\w\d:#@%/;$()~_?\+-=\\\.&](#!
# regex to discover if there are any results on the page
NO_RESULTS_REGEX = re.compile("did not match with any results.", re.IGNORECASE)
# WAF/IDS/IPS checking payload
PROTECTION_CHECK_PAYLOAD = (
"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')#"
)
# scripts to detect the WAF/IDS/IPS
DETECT_FIREWALL_PATH = "{}/lib/firewall".format(os.getcwd())
# search engines that the application can use
AUTHORIZED_SEARCH_ENGINES = {
"aol": "http://aol.com",
@ -215,7 +231,7 @@ URL_EXCLUDES = (
"www.google", "mail.google", "accounts.google",
"schema.org", "www.<b", "https://cid-", "https://<strong", # these are some weird things that get pulled up?
"plus.google", "www.w3.org", "schemas.live.com",
"torproject.org", "search-results.com"
"torproject.org", "search-results.com", "index.com"
)
# regular expressions used for DBMS recognition based on error message response

0
lib/firewall/__init__.py Normal file
View file

19
lib/firewall/anquanbao.py Normal file
View file

@ -0,0 +1,19 @@
import re
__item__ = "Anquanbao Web Application Firewall (Anquanbao)"
def detect(content, **kwargs):
headers = kwargs.get("headers", None)
content = str(content)
detection_scehmas = (re.compile(r"/aqb_cc/error/"), )
if headers is not None:
for detection in detection_scehmas:
if detection.search(content) is not None:
return True
try:
if re.compile(r"MISS").search(headers.get("X-Powered-By-Anquanbao")) is not None:
return True
except Exception:
pass

View file

@ -0,0 +1,20 @@
import re
__item__ = "CloudFlare Web Application Firewall (CloudFlare)"
def detect(content, **kwargs):
headers = kwargs.get("headers", None)
content = str(content)
detection_schemas = (re.compile(r"CloudFlare Ray ID:|var CloudFlare=", re.I),)
for detection in detection_schemas:
if detection.search(content) is not None:
return True
try:
if re.compile(r"cloudflare-nginx", re.I).search(headers.get("Server")) is not None:
return True
if re.compile(r"\A__cfduid=", re.I).search(headers.get("Cookie")) is not None:
return True
except Exception:
pass

21
lib/firewall/generic.py Normal file
View file

@ -0,0 +1,21 @@
import re
from lib.core.settings import PROTECTION_CHECK_PAYLOAD
__item__ = "Generic (Unknown)"
def detect(content, **kwargs):
content = str(content)
detection_schema = (
re.compile("blocked", re.I), re.compile("forbidden", re.I),
re.compile("illegal", re.I), re.compile("reported", re.I),
re.compile("logged", re.I), re.compile("access denied", re.I),
re.compile("ip address logged", re.I)
)
for detection in detection_schema:
if detection.search(content) is not None:
return True
if PROTECTION_CHECK_PAYLOAD in content:
return True

View file

@ -0,0 +1,16 @@
import re
__item__ = "ModSecurity: Open Source Web Application Firewall (Trustwave)"
def detect(content, **kwargs):
content = str(content)
detection_schema = (
re.compile(r"ModSecurity|NYOB", re.I),
re.compile(r"This error was generated by Mod_Security", re.I),
re.compile(r"Web Server at", re.I)
)
for detection in detection_schema:
if detection.search(content) is not None:
return True

16
lib/firewall/paloalto.py Normal file
View file

@ -0,0 +1,16 @@
import re
__item__ = "Palo Alto Firewall (Palo Alto Networks)"
def detect(content, **kwargs):
content = str(content)
detection_schemas = (
re.compile(r"\bhas been blocked in accordance with company policy\b"),
re.compile(r"<.+>Virus.Spyware.Download.Blocked<.+.>")
)
for detection in detection_schemas:
if detection.search(content) is not None:
return True

16
lib/firewall/sucuri.py Normal file
View file

@ -0,0 +1,16 @@
import re
__item__ = "Sucuri Firewall (Sucuri Cloudproxy)"
def detect(content, **kwargs):
content = str(content)
detection_schema = (
re.compile(r"Access Denied - Sucuri Website Firewall"),
re.compile(r"Sucuri WebSite Firewall - CloudProxy - Access Denied"),
re.compile(r"Questions\?.+cloudproxy@sucuri\.net")
)
for detection in detection_schema:
if detection.search(content) is not None:
return True

14
lib/firewall/webseal.py Normal file
View file

@ -0,0 +1,14 @@
import re
__item__ = "IBM Security Access Manager WebSEAL"
def detect(content, **kwargs):
content = str(content)
detection_schema = (
re.compile(r"\bWebSEAL\b"), re.compile(r"\bIBM\b")
)
for detection in list(detection_schema):
if detection.search(content) is not None:
return True

View file

@ -1,3 +1,7 @@
import os
import re
import importlib
import requests
from xml.dom import minidom
from requests.exceptions import ConnectionError
@ -9,13 +13,105 @@ from lib.core.settings import (
proxy_string_to_dict,
create_random_ip,
write_to_log_file,
HEADER_RESULT_PATH,
replace_http,
shutdown,
COOKIE_LOG_PATH
HEADER_RESULT_PATH,
COOKIE_LOG_PATH,
PROTECTION_CHECK_PAYLOAD,
DETECT_FIREWALL_PATH,
ISSUE_LINK,
DBMS_ERRORS,
UNKNOWN_FIREWALL_FINGERPRINT_PATH
)
def detect_protection(url, **kwargs):
verbose = kwargs.get("verbose", False)
agent = kwargs.get("agent", None)
proxy = kwargs.get("proxy", None)
xforward = kwargs.get("xforward", False)
if xforward:
ip_list = (
create_random_ip(),
create_random_ip(),
create_random_ip()
)
headers = {
"Connection": "close",
"user-agent": agent,
"X-Forward-From": "{}, {}, {}".format(ip_list[0], ip_list[1], ip_list[2])
}
else:
headers = {
"Connection": "close",
"user-agent": agent
}
url = "{} {}".format(url.strip(), PROTECTION_CHECK_PAYLOAD)
if verbose:
logger.debug(set_color(
"attempting connection to '{}'...".format(url), level=10
))
try:
protection_check_req = requests.get(
url, params=headers, proxies=proxy_string_to_dict(proxy), timeout=20
)
html, status, headers = protection_check_req.content, protection_check_req.status_code, protection_check_req.headers
for dbms in DBMS_ERRORS: # make sure there are no DBMS errors in the HTML
for regex in DBMS_ERRORS[dbms]:
if re.compile(regex).search(html) is not None:
logger.info(set_color(
"it appears that the WAF/IDS/IPS check threw a DBMS error and may be vulnerable "
"to SQL injection attacks. it appears the backend DBMS is '{}'...".format(dbms), level=25
))
retval = []
if status != 200 and "not found" not in html.lower():
file_list = [f for f in os.listdir(DETECT_FIREWALL_PATH) if not any(ex in f for ex in ["__init__", ".pyc"])]
for item in file_list:
item = item[:-3]
detection_name = "lib.firewall.{}"
detection_name = detection_name.format(item)
detection_name = importlib.import_module(detection_name)
if detection_name.detect(html, headers=headers, status=status):
retval.append(detection_name.__item__)
if len(retval) > 1:
if "Generic (Unknown)" in retval:
item = retval.index("Generic (Unknown)")
del retval[item]
else:
if retval[0] == "Generic (Unknown)":
logger.warning(set_color(
"identified WAF/IDS/IPS is unknown to Zeus, if you know the firewall and the context "
"of the firewall, please create an issue ({}), fingerprint of the firewall will be "
"written to a log file...".format(ISSUE_LINK), level=30
))
full_finger_print = "HTTP/1.1 {}\n{}\n{}".format(status, headers, html)
write_to_log_file(full_finger_print, UNKNOWN_FIREWALL_FINGERPRINT_PATH, "{}-fingerprint.html".format(
replace_http(url)
))
else:
retval = None
return ''.join(retval) if isinstance(retval, list) else retval
except Exception as e:
if "Read timed out." or "Connection reset by peer" in str(e):
logger.warning(set_color(
"detection request timed out, assuming no protection and continuing...", level=30
))
return None
else:
logger.exception(set_color(
"Zeus ran into an unexpected error '{}'...".format(e), level=50
))
request_issue_creation()
return None
def load_xml_data(path, start_node="header", search_node="name"):
"""
load the XML data
@ -52,7 +148,7 @@ def load_headers(url, **kwargs):
ip_list[0], ip_list[1], ip_list[2]
)
}
req = requests.get(url, params=header_value, proxies=proxy)
req = requests.get(url, params=header_value, proxies=proxy, timeout=10)
if len(req.cookies) > 0:
logger.info(set_color(
"found a request cookie, saving to file...", level=25
@ -91,6 +187,7 @@ def main_header_check(url, **kwargs):
agent = kwargs.get("agent", None)
proxy = kwargs.get("proxy", None)
xforward = kwargs.get("xforward", False)
identify = kwargs.get("identify", True)
protection = {"hostname": url}
definition = {
@ -100,6 +197,21 @@ def main_header_check(url, **kwargs):
"x-content": ("protection against MIME type attacks", "MIME"),
"content-security": ("protection against multiple attacks", "ALL")
}
if identify:
logger.info(set_color(
"checking if target URL is protected by some kind of WAF/IPS/IDS..."
))
identified = detect_protection(url, proxy=proxy, agent=agent, verbose=verbose, xforward=xforward)
if identified is None:
logger.info(set_color(
"no WAF/IDS/IPS has been identified on target URL...", level=25
))
else:
logger.warning(set_color(
"the target URL WAF/IDS/IPS has been identified as '{}'...".format(identified), level=30
))
if verbose:
logger.debug(set_color(
"loading XML data...", level=10
@ -111,14 +223,13 @@ def main_header_check(url, **kwargs):
try:
found_headers = load_headers(url, proxy=proxy, agent=agent, xforward=xforward)
except (ConnectionError, Exception) as e:
if "Max retries exceeded with url:" in str(e):
if "Read timed out." or "Connection reset by peer" in str(e):
found_headers = None
else:
logger.exception(set_color(
"Zeus has hit an unexpected error and cannot continue '{}'...".format(e), level=50
))
request_issue_creation()
shutdown()
if found_headers is not None:
if verbose: