diff --git a/.github/demo.mp4 b/.github/demo.mp4
new file mode 100644
index 0000000..4799ca7
Binary files /dev/null and b/.github/demo.mp4 differ
diff --git a/apachetomcatscanner/Config.py b/apachetomcatscanner/Config.py
index a30a6f1..77f3cc9 100644
--- a/apachetomcatscanner/Config.py
+++ b/apachetomcatscanner/Config.py
@@ -15,7 +15,7 @@ class Config(object):
request_timeout = 1
request_proxies = {}
- request_no_check_certificate = False
+ request_no_check_certificate = True
request_available_schemes = ["http"]
diff --git a/apachetomcatscanner/Reporter.py b/apachetomcatscanner/Reporter.py
index ce7dc18..7d958f2 100644
--- a/apachetomcatscanner/Reporter.py
+++ b/apachetomcatscanner/Reporter.py
@@ -17,22 +17,70 @@ class Reporter(object):
data = {}
- def __init__(self, config):
+ def __init__(self, config, vulns_db):
super(Reporter, self).__init__()
self.config = config
+ self.vulns_db = vulns_db
+ self._new_results = []
- def report_result(self, computer_ip, computer_port, tomcat_version, manager_accessible, default_credentials, cves):
+ def report_result(self, computer_ip, computer_port, tomcat_version, manager_accessible, credentials_found):
computer_port = str(computer_port)
+
+ finding = {}
+ finding["computer_ip"] = computer_ip
+ finding["computer_port"] = computer_port
+ finding["tomcat_version"] = tomcat_version
+ finding["manager_accessible"] = manager_accessible
+ finding["credentials_found"] = credentials_found
+
if computer_ip not in self.data.keys():
self.data[computer_ip] = {}
if str(computer_port) not in self.data[computer_ip].keys():
self.data[computer_ip][computer_port] = {}
- self.data[computer_ip][computer_port]["computer_ip"] = computer_ip
- self.data[computer_ip][computer_port]["computer_port"] = computer_port
- self.data[computer_ip][computer_port]["tomcat_version"] = tomcat_version
- self.data[computer_ip][computer_port]["manager_accessible"] = manager_accessible
- self.data[computer_ip][computer_port]["default_credentials"] = default_credentials
- self.data[computer_ip][computer_port]["cves"] = cves
+ self.data[computer_ip][computer_port] = finding
+ self._new_results.append(finding)
+
+ def print_new_results(self):
+ try:
+ for finding in self._new_results:
+
+ # List of cves
+ cve_str = ""
+ if self.config.list_cves_mode == True:
+ cve_list = self.vulns_db.get_vulnerabilities_of_version_sorted_by_criticity(finding["tomcat_version"], colors=True, reverse=True)
+ if len(cve_list) != 0:
+ cve_str = "CVEs: %s" % ', '.join(cve_list)
+
+ # credentials_str = "username:%s\npassword:%s" % (credentials_found[0][1]["username"], credentials_found[0][1]["password"])
+
+ if finding["manager_accessible"]:
+ print("[>] [Apache Tomcat/\x1b[1;95m%s\x1b[0m] on \x1b[1;93m%s\x1b[0m:\x1b[1;93m%s\x1b[0m (manager:\x1b[1;92maccessible\x1b[0m) %s\x1b[0m " % (
+ finding["tomcat_version"],
+ finding["computer_ip"],
+ finding["computer_port"],
+ cve_str
+ )
+ )
+ if len(finding["credentials_found"]) != 0:
+ for statuscode, creds in finding["credentials_found"]:
+ if len(creds["description"]) != 0:
+ print(" | Valid user: \x1b[1;92m%s\x1b[0m | password:\x1b[1;92m%s\x1b[0m | \x1b[94m%s\x1b[0m" % (creds["username"], creds["password"], creds["description"]))
+ else:
+ print(" | Valid user: \x1b[1;92m%s\x1b[0m | password:\x1b[1;92m%s\x1b[0m" % (creds["username"], creds["password"]))
+
+ else:
+ print("[>] [Apache Tomcat/\x1b[1;95m%s\x1b[0m] on \x1b[1;93m%s\x1b[0m:\x1b[1;93m%s\x1b[0m (manager:\x1b[1;91mnot accessible\x1b[0m) %s\x1b[0m " % (
+ finding["tomcat_version"],
+ finding["computer_ip"],
+ finding["computer_port"],
+ cve_str
+ )
+ )
+
+ self._new_results.remove(finding)
+ except Exception as e:
+ if self.config.debug_mode:
+ print("[Error in %s] %s" % (__name__, e))
def export_xlsx(self, path_to_file):
basepath = os.path.dirname(path_to_file)
@@ -58,13 +106,16 @@ def export_xlsx(self, path_to_file):
for computername in self.data.keys():
computer = self.data[computername]
for port in computer.keys():
+ cve_list = self.vulns_db.get_vulnerabilities_of_version_sorted_by_criticity(computer[port]["tomcat_version"], colors=False, reverse=True)
+ cve_str = ', '.join([cve["cve"]["id"] for cve in cve_list])
+
data = [
computer[port]["computer_ip"],
computer[port]["computer_port"],
computer[port]["tomcat_version"],
str(computer[port]["manager_accessible"]).upper(),
computer[port]["default_credentials"],
- computer[port]["cves"]
+ cve_str
]
worksheet.write_row(row_id, 0, data)
row_id += 1
@@ -100,13 +151,16 @@ def export_sqlite(self, path_to_file):
for computername in self.data.keys():
computer = self.data[computername]
for port in computer.keys():
+ cve_list = self.vulns_db.get_vulnerabilities_of_version_sorted_by_criticity(computer[port]["tomcat_version"], colors=False, reverse=True)
+ cve_str = ', '.join([cve["cve"]["id"] for cve in cve_list])
+
cursor.execute("INSERT INTO results VALUES (?, ?, ?, ?, ?, ?)", (
computer[port]["computer_ip"],
computer[port]["computer_port"],
computer[port]["tomcat_version"],
str(computer[port]["manager_accessible"]).upper(),
computer[port]["default_credentials"],
- computer[port]["cves"]
+ cve_str
)
)
conn.commit()
diff --git a/apachetomcatscanner/__main__.py b/apachetomcatscanner/__main__.py
index a4c98f1..c7831ae 100755
--- a/apachetomcatscanner/__main__.py
+++ b/apachetomcatscanner/__main__.py
@@ -3,6 +3,7 @@
# File name : __main__.py
# Author : Podalirius (@podalirius_)
# Date created : 24 Jul 2022
+import threading
import argparse
import os
@@ -11,14 +12,14 @@
from apachetomcatscanner.Reporter import Reporter
from apachetomcatscanner.Config import Config
from apachetomcatscanner.VulnerabilitiesDB import VulnerabilitiesDB
-from apachetomcatscanner.utils.scan import scan_worker
+from apachetomcatscanner.utils.scan import scan_worker, monitor_thread
from sectools.windows.ldap import get_computers_from_domain, get_servers_from_domain, get_subnets
from sectools.network.domains import is_fqdn
from sectools.network.ip import is_ipv4_cidr, is_ipv4_addr, is_ipv6_addr, expand_cidr, expand_port_range
from concurrent.futures import ThreadPoolExecutor
-VERSION = "2.3.5"
+VERSION = "3.0"
banner = """Apache Tomcat Scanner v%s - by @podalirius_\n""" % VERSION
@@ -90,8 +91,6 @@ def load_targets(options, config):
# Sort uniq on targets list
targets = sorted(list(set(targets)))
- print(targets)
-
final_targets = []
# Parsing target to filter IP/DNS/CIDR
for target in targets:
@@ -128,12 +127,12 @@ def parseArgs():
parser.add_argument("-v", "--verbose", default=False, action="store_true", help="Verbose mode. (default: False)")
parser.add_argument("--debug", default=False, action="store_true", help="Debug mode, for huge verbosity. (default: False)")
parser.add_argument("-C", "--list-cves", default=False, action="store_true", help="List CVE ids affecting each version found. (default: False)")
- parser.add_argument("-T", "--threads", default=8, type=int, help="Number of threads (default: 5)")
+ parser.add_argument("-T", "--threads", default=250, type=int, help="Number of threads (default: 250)")
parser.add_argument("-s", "--servers-only", default=False, action="store_true", help="If querying ActiveDirectory, only get servers and not all computer objects. (default: False)")
parser.add_argument("--only-http", default=False, action="store_true", help="Scan only with HTTP scheme. (default: False, scanning with both HTTP and HTTPs)")
parser.add_argument("--only-https", default=False, action="store_true", help="Scan only with HTTPs scheme. (default: False, scanning with both HTTP and HTTPs)")
- parser.add_argument("--no-check-certificate", default=False, action="store_true", help="Do not check certificate. (default: False)")
+ # parser.add_argument("--no-check-certificate", default=False, action="store_true", help="Do not check certificate. (default: False)")
group_export = parser.add_argument_group("Export results")
group_export.add_argument("--export-xlsx", dest="export_xlsx", type=str, default=None, required=False, help="Output XLSX file to store the results in.")
@@ -152,7 +151,7 @@ def parseArgs():
group_targets_source = parser.add_argument_group("Targets")
group_targets_source.add_argument("-tf", "--targets-file", default=None, type=str, help="Path to file containing a line by line list of targets.")
group_targets_source.add_argument("-tt", "--target", default=[], type=str, action='append', help="Target IP, FQDN or CIDR")
- group_targets_source.add_argument("-tp", "--target-ports", default="80,443,8080", type=str, help="Target ports to scan top search for Apache Tomcat servers.")
+ group_targets_source.add_argument("-tp", "--target-ports", default="80,443,8080,8081,9080,9081,10080", type=str, help="Target ports to scan top search for Apache Tomcat servers.")
group_targets_source.add_argument("-ad", "--auth-domain", default="", type=str, help="Windows domain to authenticate to.")
group_targets_source.add_argument("-ai", "--auth-dc-ip", default=None, type=str, help="IP of the domain controller.")
group_targets_source.add_argument("-au", "--auth-user", default=None, type=str, help="Username of the domain account.")
@@ -189,14 +188,13 @@ def main():
config.set_request_available_schemes(only_http=options.only_http, only_https=options.only_https)
config.set_request_timeout(options.request_timeout)
config.set_request_proxies(options.proxy_ip, options.proxy_port)
- config.set_request_no_check_certificate(options.no_check_certificate)
+ # config.set_request_no_check_certificate(options.no_check_certificate)
config.set_list_cves_mode(options.list_cves)
config.load_credentials_from_options(options.tomcat_username, options.tomcat_password, options.tomcat_usernames_file, options.tomcat_passwords_file)
- reporter = Reporter(config=config)
-
vulns_db = VulnerabilitiesDB(config=config)
+ reporter = Reporter(config=config, vulns_db=vulns_db)
# Parsing targets and ports
targets = load_targets(options, config)
@@ -211,10 +209,12 @@ def main():
# Exploring targets
if len(targets) != 0 and options.threads != 0:
print("[+] Searching for Apache Tomcats servers on specified targets ...")
- with ThreadPoolExecutor(max_workers=min(options.threads, len(targets))) as tp:
+ monitor_data = {"actions_performed": 0, "total": (len(targets)*len(ports)), "lock": threading.Lock()}
+ with ThreadPoolExecutor(max_workers=min(options.threads, 1+monitor_data["total"])) as tp:
+ tp.submit(monitor_thread, reporter, config, monitor_data)
for target in targets:
for port in ports:
- tp.submit(scan_worker, target, port, reporter, vulns_db, config)
+ tp.submit(scan_worker, target, port, reporter, config, monitor_data)
print("[+] All done!")
if options.export_xlsx is not None:
diff --git a/apachetomcatscanner/data/credentials.json b/apachetomcatscanner/data/credentials.json
index 8252a38..b298552 100644
--- a/apachetomcatscanner/data/credentials.json
+++ b/apachetomcatscanner/data/credentials.json
@@ -5,11 +5,21 @@
"password": "admin",
"description": ""
},
+ {
+ "username": "admin",
+ "password": "",
+ "description": ""
+ },
{
"username": "admin",
"password": "tomcat",
"description": ""
},
+ {
+ "username": "tomcat",
+ "password": "",
+ "description": ""
+ },
{
"username": "tomcat",
"password": "admin",
diff --git a/apachetomcatscanner/test_environement/versions/6.0.43/tomcat-users.xml b/apachetomcatscanner/test_environement/versions/6.0.43/tomcat-users.xml
index e3debc9..c486880 100644
--- a/apachetomcatscanner/test_environement/versions/6.0.43/tomcat-users.xml
+++ b/apachetomcatscanner/test_environement/versions/6.0.43/tomcat-users.xml
@@ -29,5 +29,7 @@
+
+
diff --git a/apachetomcatscanner/utils/network.py b/apachetomcatscanner/utils/network.py
new file mode 100644
index 0000000..68bbeb1
--- /dev/null
+++ b/apachetomcatscanner/utils/network.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# File name : network.py
+# Author : Podalirius (@podalirius_)
+# Date created : 17 Jan 2023
+
+import socket
+
+import requests
+# Disable warnings of insecure connection for invalid certificates
+requests.packages.urllib3.disable_warnings()
+# Allow use of deprecated and weak cipher methods
+requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS += ':HIGH:!DH:!aNULL'
+try:
+ requests.packages.urllib3.contrib.pyopenssl.util.ssl_.DEFAULT_CIPHERS += ':HIGH:!DH:!aNULL'
+except AttributeError:
+ pass
+
+
+def is_target_a_windows_machine(target) -> bool:
+ # if port 135 and 445 open
+ if is_port_open(target, 135) and is_port_open(target, 445):
+ return True
+ else:
+ return False
+
+
+def is_target_a_windows_domain_controller(target) -> bool:
+ # if port 135 and 445 and 88 open
+ if is_target_a_windows_machine(target) and is_port_open(target, 88):
+ return True
+ else:
+ return False
+
+
+def is_port_open(target, port) -> bool:
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
+ s.settimeout(1)
+ return s.connect_ex((target, port)) == 0
+
+
+def is_http_accessible(target, port, config, scheme="http"):
+ url = "%s://%s:%d/" % (scheme, target, port)
+ try:
+ r = requests.get(
+ url,
+ timeout=config.request_timeout,
+ proxies=config.request_proxies,
+ verify=(not (config.request_no_check_certificate))
+ )
+ return True
+ except Exception as e:
+ config.debug("Error in is_http_accessible('%s', %d, '%s'): %s " % (target, port, scheme, e))
+ return False
\ No newline at end of file
diff --git a/apachetomcatscanner/utils/scan.py b/apachetomcatscanner/utils/scan.py
index 3b30a15..b5104a4 100644
--- a/apachetomcatscanner/utils/scan.py
+++ b/apachetomcatscanner/utils/scan.py
@@ -5,35 +5,23 @@
# Date created : 29 Jul 2022
import base64
+import datetime
import re
-import requests
-
+import time
+from apachetomcatscanner.utils.network import is_port_open, is_http_accessible
-def is_target_a_windows_machine() -> bool:
- # if port 135 and 445 open
- pass
-
-def is_target_a_windows_domain_controller() -> bool:
- # if port 135 and 445 and 88 and ldap/ldaps open
+import requests
+# Disable warnings of insecure connection for invalid certificates
+requests.packages.urllib3.disable_warnings()
+# Allow use of deprecated and weak cipher methods
+requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS += ':HIGH:!DH:!aNULL'
+try:
+ requests.packages.urllib3.contrib.pyopenssl.util.ssl_.DEFAULT_CIPHERS += ':HIGH:!DH:!aNULL'
+except AttributeError:
pass
-def is_http_accessible(target, port, config, scheme="http"):
- url = "%s://%s:%d/" % (scheme, target, port)
- try:
- r = requests.get(
- url,
- timeout=config.request_timeout,
- proxies=config.request_proxies,
- verify=(not (config.request_no_check_certificate))
- )
- return True
- except Exception as e:
- config.debug("Error in is_http_accessible('%s', %d, '%s'): %s " % (target, port, scheme, e))
- return False
-
-
def is_tomcat_manager_accessible(target, port, config, scheme="http"):
path = "/manager/html"
url = "%s://%s:%d%s" % (scheme, target, port, path)
@@ -97,56 +85,64 @@ def try_default_credentials(target, port, config, scheme="http"):
return found_credentials
-def scan_worker(target, port, reporter, vulns_db, config):
- config.debug("scan_worker('%s', %d, ...)" % (target, port))
- result = {"target": target}
- for scheme in config.get_request_available_schemes():
- if is_http_accessible(target, port, config, scheme):
- result["version"] = get_version_from_malformed_http_request(target, port, config, scheme)
- if result["version"] is not None:
- config.debug("Found version %s" % result["version"])
-
- result["manager_accessible"] = is_tomcat_manager_accessible(target, port, config, scheme)
-
- credentials = []
- if result["manager_accessible"]:
- config.debug("Manager is accessible")
- # Test for default credentials
- credentials = try_default_credentials(target, port, config, scheme)
-
- str_found_creds = []
- if len(credentials) != 0:
- for statuscode, creds in credentials:
- str_found_creds.append("(username:\x1b[1;92m%s\x1b[0m password:\x1b[1;92m%s\x1b[0m)" % (creds["username"], creds["password"]))
-
- # List of cves
- cve_str = ""
- if config.list_cves_mode == True:
- cve_list = vulns_db.get_vulnerabilities_of_version_sorted_by_criticity(result["version"], colors=True, reverse=True)
- if len(cve_list) != 0:
- cve_str = "CVEs: %s" % ', '.join(cve_list)
-
- print("[>] [Apache Tomcat/\x1b[1;95m%s\x1b[0m] on \x1b[1;93m%s\x1b[0m:\x1b[1;93m%d\x1b[0m (manager:%s) %s %s\x1b[0m " % (
- result["version"],
- target,
- port,
- ("\x1b[1;92maccessible\x1b[0m" if result["manager_accessible"] else "\x1b[1;91mnot accessible\x1b[0m"),
- ' '.join(str_found_creds),
- cve_str
- )
- )
-
- cve_list = vulns_db.get_vulnerabilities_of_version_sorted_by_criticity(result["version"], colors=False, reverse=True)
- credentials_str = "username:%s\npassword:%s" % (credentials[0][1]["username"], credentials[0][1]["password"])
- cve_str = ', '.join([cve["cve"]["id"] for cve in cve_list])
-
- reporter.report_result(
- target,
- port,
- result["version"],
- result["manager_accessible"],
- credentials_str,
- cve_str
- )
+def scan_worker(target, port, reporter, config, monitor_data):
+ try:
+ result = {"target": target}
+
+ if is_port_open(target, port):
+ for scheme in config.get_request_available_schemes():
+ if is_http_accessible(target, port, config, scheme):
+ result["version"] = get_version_from_malformed_http_request(target, port, config, scheme)
+ if result["version"] is not None:
+ config.debug("Found version %s" % result["version"])
+
+ result["manager_accessible"] = is_tomcat_manager_accessible(target, port, config, scheme)
+
+ credentials_found = []
+ if result["manager_accessible"]:
+ config.debug("Manager is accessible")
+ # Test for default credentials
+ credentials_found = try_default_credentials(target, port, config, scheme)
+
+ reporter.report_result(
+ target,
+ port,
+ result["version"],
+ result["manager_accessible"],
+ credentials_found
+ )
+
+ monitor_data["lock"].acquire()
+ monitor_data["actions_performed"] = monitor_data["actions_performed"] + 1
+ # print("Updated for port %d" % port)
+ monitor_data["lock"].release()
+
+ except Exception as e:
+ if config.debug_mode:
+ print("[Error in %s] %s" % (__name__, e))
+
+
+def monitor_thread(reporter, config, monitor_data):
+ last_check, monitoring = 0, True
+ while monitoring:
+ new_check = monitor_data["actions_performed"]
+ rate = (new_check - last_check)
+ if not config.debug_mode:
+ print("\r", end="")
+ reporter.print_new_results()
+ print("[%s] Status (%d/%d) %5.2f %% | Rate %d tests/s " % (
+ datetime.datetime.now().strftime("%Y/%m/%d %Hh%Mm%Ss"),
+ new_check, monitor_data["total"], (new_check/monitor_data["total"])*100,
+ rate
+ ),
+ end=("" if not config.debug_mode else "\n")
+ )
+ last_check = new_check
+ time.sleep(1)
+ if rate == 0 and monitor_data["actions_performed"] == monitor_data["total"]:
+ monitoring = False
+ if len(reporter._new_results) != 0:
+ reporter.print_new_results()
+ print()
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 146730b..9a912a4 100644
--- a/setup.py
+++ b/setup.py
@@ -14,7 +14,7 @@
setuptools.setup(
name="apachetomcatscanner",
- version="2.3.5",
+ version="3.0",
description="",
url="https://github.com/p0dalirius/ApacheTomcatScanner",
author="Podalirius",