From bc335fa2d3832c259eb8491243eca0b73f39121e Mon Sep 17 00:00:00 2001 From: ekultek Date: Wed, 1 Nov 2017 21:48:58 -0500 Subject: [PATCH] created a clickjacking scanner that will test a page for an X-Frame-Options header, if the header is not there, then it will create a basic HTML page with that URL as an iframe --- etc/checksum/md5sum.md5 | 8 +- etc/clickjacking_test_page.html | 5 + lib/attacks/clickjacking_scan/__init__.py | 112 ++++++++++++++++++++++ lib/core/settings.py | 8 +- var/google_search/search.py | 2 +- zeus.py | 16 +++- 6 files changed, 142 insertions(+), 9 deletions(-) create mode 100644 etc/clickjacking_test_page.html create mode 100644 lib/attacks/clickjacking_scan/__init__.py diff --git a/etc/checksum/md5sum.md5 b/etc/checksum/md5sum.md5 index 1f5f6dc..a0387a9 100644 --- a/etc/checksum/md5sum.md5 +++ b/etc/checksum/md5sum.md5 @@ -1,4 +1,4 @@ -7fa426efc9d5b26d93101c0058840788 ./zeus.py +b100d6428f04f18100e9358688004abd ./zeus.py 6ad5f22ec4a6f8324bfb1b01ab6d51ec ./etc/scripts/cleanup.sh 155c9482f690f1482f324a7ffd8b8098 ./etc/scripts/fix_pie.sh 0e435c641bc636ac0b3d54e032d9cf6a ./etc/scripts/install_nmap.sh @@ -9,6 +9,7 @@ 82cc68f46539d0255f7ce14cd86cd49b ./etc/link_ext.txt a3ee2d4610056c6fe270c2a74dda49f9 ./etc/dorks.txt b34aacdcb683a2de649c002601f53746 ./etc/xss_payloads.txt +f914fafb99953a9124295b381be2954b .etc/clickjacking_test_page.html d41d8cd98f00b204e9800998ecf8427e ./bin/__init__.py ff2511effe21aa36002f37ed9bfe47ec ./bin/unzip_gecko.py dc1eb4ebe0f372af48b5a9c107ebc68d ./bin/drivers/geckodriver-v0.18.0-linux32.tar.gz @@ -40,11 +41,12 @@ e2b494ba257444ed4a9a8a554dcbe250 ./lib/attacks/whois_lookup/whois.py 27358f26bda30d7356143c3ea1fa99c5 ./lib/attacks/nmap_scan/__init__.py 216999fa0e84866d5c1d96d5676034e4 ./lib/attacks/nmap_scan/nmap_opts.py f746d2867f493104a78d0540cf50c03f ./lib/attacks/intel_me/__init__.py +49c88c6927a141a688d3fc0c7c101019 .lib/attacks/clickjacking_scan/__init__.py 1faa2b5dfad6eb538bbfe42942d2a9da ./lib/core/errors.py d41d8cd98f00b204e9800998ecf8427e ./lib/core/__init__.py -97bfe118adfdfc3ef02c5dca4c0aec62 ./lib/core/settings.py +1781e9963d34604b626b6fe3db513bc7 ./lib/core/settings.py d41d8cd98f00b204e9800998ecf8427e ./var/google_search/__init__.py -2d984415adc457061e1757cbd79ea12b ./var/google_search/search.py +914ac7cf2e216ca878a5f711a71290a8 ./var/google_search/search.py d41d8cd98f00b204e9800998ecf8427e ./var/__init__.py d41d8cd98f00b204e9800998ecf8427e ./var/auto_issue/__init__.py 4506850a02aa18e12bef4efeb760ad9e ./var/auto_issue/github.py diff --git a/etc/clickjacking_test_page.html b/etc/clickjacking_test_page.html new file mode 100644 index 0000000..a0a21ef --- /dev/null +++ b/etc/clickjacking_test_page.html @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/lib/attacks/clickjacking_scan/__init__.py b/lib/attacks/clickjacking_scan/__init__.py new file mode 100644 index 0000000..20cd85f --- /dev/null +++ b/lib/attacks/clickjacking_scan/__init__.py @@ -0,0 +1,112 @@ +import requests + +import lib.core.settings +import var.auto_issue.github + + +class ClickJackingScanner(object): + + def __init__(self, url): + self.url = url + self.safe = "X-Frame-Options" + self.html = open(lib.core.settings.CLICKJACKING_TEST_PAGE_PATH).read() + + def generate_html(self): + """ + generate the HTML page if the clicking test was successfully completed + """ + return self.html.format(self.url) + + def extract_and_test_headers(self, **kwargs): + """ + extract the headers from the url given to test if they contain the correct protection + against clickjacking + """ + proxy = kwargs.get("proxy", None) + agent = kwargs.get("agent", None) + forward = kwargs.get("forward", None) + if forward is not None: + ip_addrs = lib.core.settings.create_random_ip() + headers = { + "user-agent": agent, + "X-Forwarded-From": "{}, {}, {}".format( + ip_addrs[0], ip_addrs[1], ip_addrs[2] + ), + "Connection": "close" + } + else: + headers = { + "user-agent": agent, + "Connection": "close" + } + req = requests.get(self.url, headers=headers, proxies=lib.core.settings.proxy_string_to_dict(proxy)) + headers = req.headers + if self.safe in headers: + return False + return True + + +def clickjacking_main(url, **kwargs): + """ + main function for the clickjacking scan + """ + agent = kwargs.get("agent", None) + proxy = kwargs.get("proxy", None) + forward = kwargs.get("forward", None) + verbose = kwargs.get("verbose", False) + batch = kwargs.get("batch", False) + + if not batch: + if lib.core.settings.URL_QUERY_REGEX.match(url): + question = lib.core.settings.prompt( + "it is recommended to use a URL without a GET(query) parameter, " + "heuristic testing has detected that the URL provided contains a " + "GET(query) parameter in it, would you like to continue", opts="yN" + ) + if question.lower().startswith("n"): + lib.core.settings.logger.info(lib.core.settings.set_color( + "automatically removing all queries from URL..." + )) + url = "http://{}".format(lib.core.settings.replace_http(url, complete=True)) + + scanner = ClickJackingScanner(url) + + if verbose: + lib.core.settings.logger.debug(lib.core.settings.set_color( + "generating HTML...", level=10 + )) + + data = scanner.generate_html() + + if verbose: + lib.core.settings.logger.debug(lib.core.settings.set_color( + "HTML generated successfully...", level=10 + )) + print("{}\n{}\n{}".format("-" * 30, data, "-" * 30)) + + try: + results = scanner.extract_and_test_headers(agent=agent, proxy=proxy, forward=forward) + + if results: + lib.core.settings.logger.info(lib.core.settings.set_color( + "it appears that provided URL '{}' is vulnerable to clickjacking, writing " + "to HTML file...".format(url) + )) + lib.core.settings.write_to_log_file( + data, + lib.core.settings.CLICKJACKING_RESULTS_PATH, + "{}-clickjacking.html".format(lib.core.settings.replace_http(url)) + ) + else: + lib.core.settings.logger.error(lib.core.settings.set_color( + "provided URL '{}' seems to have the correct protection from clickjacking...".format( + url + ), level=40 + )) + + except Exception as e: # until I figure out the errors, we'll just make issues about them + lib.core.settings.logger.exception(lib.core.settings.set_color( + "Zeus failed to process the clickjacking test and received " + "error code '{}'...".format(e), level=50 + )) + var.auto_issue.github.request_issue_creation() diff --git a/lib/core/settings.py b/lib/core/settings.py index a9c518a..3eb63ec 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -41,7 +41,7 @@ PATCH_ID = str(subprocess.check_output(["git", "rev-parse", "origin/master"]))[: # clone link CLONE = "https://github.com/ekultek/zeus-scanner.git" # current version -VERSION = "1.1.12.{}".format(PATCH_ID) +VERSION = "1.1.13".format(PATCH_ID) # colors to output depending on the version VERSION_TYPE_COLORS = {"dev": 33, "stable": 92, "other": 30} # version string formatting @@ -83,10 +83,14 @@ CLEANUP_TOOL_PATH = "{}/etc/scripts/cleanup.sh".format(os.getcwd()) LAUNCH_SQLMAP_API_TOOL = "{}/etc/scripts/launch_sqlmap_api.sh".format(os.getcwd()) # path to nmap installer NMAP_INSTALLER_TOOL = "{}/etc/scripts/install_nmap.sh".format(os.getcwd()) +# clickjacking HTML test page path +CLICKJACKING_TEST_PAGE_PATH = "{}/etc/clickjacking_test_page.html".format(os.getcwd()) # path to check if the program has been executed or not EXECUTED_PATH = "{}/bin/executed.txt".format(os.getcwd()) # paths to sqlmap and nmap TOOL_PATHS = "{}/bin/paths/path_config.ini".format(os.getcwd()) +# path to write the HTML in +CLICKJACKING_RESULTS_PATH = "{}/log/clickjacking-log".format(os.getcwd()) # the log for found admin pages on a site ADMIN_PAGE_FILE_PATH = "{}/log/admin-page-log".format(os.getcwd()) # path to the sitemap log file @@ -465,7 +469,7 @@ def write_to_log_file(data_to_write, path, filename): log.write(etree.tostring(data_to_write, pretty_print=True)) except TypeError: logger.warning(set_color( - "unable to serialize XML, writing as plain text (usually means the file already exists)...", + "unable to serialize data, writing as plain text (usually means the file already exists)...", level=30 )) log.write(data_to_write) diff --git a/var/google_search/search.py b/var/google_search/search.py index 9775140..96bf91d 100644 --- a/var/google_search/search.py +++ b/var/google_search/search.py @@ -275,7 +275,7 @@ def parse_search_results(query, url_to_search, verbose=False, **kwargs): ip_to_use = (create_random_ip(), create_random_ip(), create_random_ip()) if verbose: logger.debug(set_color( - "random IP address generated for headers '{}'...".format(ip_to_use), level=10 + "random IP addresses generated for headers '{}'...".format(ip_to_use), level=10 )) headers = { diff --git a/zeus.py b/zeus.py index 5160614..0e985ab 100755 --- a/zeus.py +++ b/zeus.py @@ -19,6 +19,7 @@ from lib.attacks.xss_scan import main_xss from lib.attacks.nmap_scan.nmap_opts import NMAP_API_OPTS from lib.attacks.sqlmap_scan.sqlmap_opts import SQLMAP_API_OPTIONS from lib.attacks.whois_lookup.whois import whois_lookup_main +from lib.attacks.clickjacking_scan import clickjacking_main from lib.core.errors import ( InvalidInputProvided, @@ -92,6 +93,8 @@ if __name__ == "__main__": help="Run an XSS scan on the found URL's") attacks.add_option("-w", "--whois-lookup", dest="performWhoisLookup", action="store_true", help="Perform a WhoIs lookup on the provided domain") + attacks.add_option("-c", "--clickjacking", dest="performClickjackingScan", action="store_true", + help="Perform a clickjacking scan on a provided URL") attacks.add_option("--sqlmap-args", dest="sqlmapArguments", metavar="SQLMAP-ARGS", help="Pass the arguments to send to the sqlmap API within quotes & " "separated by a comma. IE 'dbms mysql, verbose 3, level 5'") @@ -268,6 +271,7 @@ if __name__ == "__main__": admin = kwargs.get("admin", False) verbose = kwargs.get("verbose", False) whois = kwargs.get("whois", False) + clickjacking = kwargs.get("clickjacking", False) batch = kwargs.get("batch", False) auto_start = kwargs.get("auto_start", False) @@ -277,7 +281,8 @@ if __name__ == "__main__": "xss": opt.runXssScan, "admin": opt.adminPanelFinder, "intel": opt.intelCheck, - "whois": opt.performWhoisLookup + "whois": opt.performWhoisLookup, + "clickjacking": opt.performClickjackingScan } enabled = set() @@ -296,7 +301,7 @@ if __name__ == "__main__": if not batch: question = prompt( - "would you like to process found URL: '{}'gi".format(url), opts=["y", "N"] + "would you like to process found URL: '{}'".format(url), opts=["y", "N"] ) else: question = "y" @@ -332,6 +337,9 @@ if __name__ == "__main__": whois_lookup_main( url, verbose=opt.runInVerbose ) + elif clickjacking: + clickjacking_main(url, agent=agent_to_use, proxy=proxy_to_use, + forward=opt.forwardedForRandomIP, batch=opt.runInBatch) else: pass else: @@ -364,7 +372,8 @@ if __name__ == "__main__": options = [ opt.runSqliScan, opt.runPortScan, opt.intelCheck, opt.adminPanelFinder, - opt.runXssScan, opt.performWhoisLookup + opt.runXssScan, opt.performWhoisLookup, + opt.performClickjackingScan ] if any(options): with open(urls_to_use) as urls: @@ -374,6 +383,7 @@ if __name__ == "__main__": sqlmap=opt.runSqliScan, nmap=opt.runPortScan, intel=opt.intelCheck, xss=opt.runXssScan, whois=opt.performWhoisLookup, admin=opt.adminPanelFinder, + clickjacking=opt.performClickjackingScan, verbose=opt.runInVerbose, batch=opt.runInBatch, auto_start=opt.autoStartSqlmap )