mirror of
https://github.com/Ekultek/Zeus-Scanner.git
synced 2026-03-11 08:55:51 +00:00
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:
parent
1eb861ae16
commit
f2cad88415
12 changed files with 269 additions and 14 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
0
lib/firewall/__init__.py
Normal file
19
lib/firewall/anquanbao.py
Normal file
19
lib/firewall/anquanbao.py
Normal 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
|
||||
20
lib/firewall/cloudflare.py
Normal file
20
lib/firewall/cloudflare.py
Normal 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
21
lib/firewall/generic.py
Normal 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
|
||||
16
lib/firewall/modsecurity.py
Normal file
16
lib/firewall/modsecurity.py
Normal 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
16
lib/firewall/paloalto.py
Normal 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
16
lib/firewall/sucuri.py
Normal 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
14
lib/firewall/webseal.py
Normal 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
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in a new issue