Skip to content

Commit

Permalink
Release 3.0: Major performance improvements, code refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
p0dalirius committed Jan 17, 2023
1 parent d5b90d6 commit ab1103e
Show file tree
Hide file tree
Showing 9 changed files with 214 additions and 98 deletions.
Binary file added .github/demo.mp4
Binary file not shown.
2 changes: 1 addition & 1 deletion apachetomcatscanner/Config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down
74 changes: 64 additions & 10 deletions apachetomcatscanner/Reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand Down
24 changes: 12 additions & 12 deletions apachetomcatscanner/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# File name : __main__.py
# Author : Podalirius (@podalirius_)
# Date created : 24 Jul 2022
import threading

import argparse
import os
Expand All @@ -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

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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.")
Expand All @@ -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.")
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand Down
10 changes: 10 additions & 0 deletions apachetomcatscanner/data/credentials.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,21 @@
"password": "admin",
"description": ""
},
{
"username": "admin",
"password": "",
"description": ""
},
{
"username": "admin",
"password": "tomcat",
"description": ""
},
{
"username": "tomcat",
"password": "",
"description": ""
},
{
"username": "tomcat",
"password": "admin",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,7 @@

<role rolename="tomcat"/>
<user username="admin" password="admin" roles="tomcat,admin-gui,manager-gui"/>
<user username="tomcat" password="tomcat" roles="tomcat,admin-gui,manager-gui"/>
<user username="tomcat" password="" roles="tomcat,admin-gui,manager-gui"/>

</tomcat-users>
54 changes: 54 additions & 0 deletions apachetomcatscanner/utils/network.py
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit ab1103e

Please sign in to comment.