From b60fcfc9ca0c52c8cda84fc1237bd877702d54a9 Mon Sep 17 00:00:00 2001 From: Tib3rius <48113936+Tib3rius@users.noreply.github.com> Date: Fri, 27 Aug 2021 15:16:26 -0400 Subject: [PATCH] Updates & Bug Fixes Updated global option parsing to allow default None values by removing the "default=" setting. Added a match_service() function to ServiceScan plugins to match combinations of protocol/port/name. Fixed bug in status times. Removed defaul from global.domain. Added new WinRM detection plugin. --- autorecon.py | 56 +++++++++++++++++++++++++++++++++++++++++++++---- global.toml | 3 +-- plugins/misc.py | 32 +++++++++++++++++++++++++++- 3 files changed, 84 insertions(+), 7 deletions(-) diff --git a/autorecon.py b/autorecon.py index cc0da26..41904f0 100644 --- a/autorecon.py +++ b/autorecon.py @@ -288,7 +288,13 @@ class Plugin(object): name = 'global.' + slugify(name).replace('-', '_') if name in vars(self.autorecon.args): - return vars(self.autorecon.args)[name] + if vars(self.autorecon.args)[name] is None: + if default: + return default + else: + return None + else: + return vars(self.autorecon.args)[name] else: if default: return default @@ -323,12 +329,42 @@ class ServiceScan(Plugin): super().__init__() self.ports = {'tcp':[], 'udp':[]} self.ignore_ports = {'tcp':[], 'udp':[]} + self.services = [] self.service_names = [] self.ignore_service_names = [] self.match_all_service_names_boolean = False self.run_once_boolean = False self.require_ssl_boolean = False + @final + def match_service(self, protocol, port, name, negative_match=False): + protocol = protocol.lower() + if protocol not in ['tcp', 'udp']: + print('Invalid protocol.') + sys.exit(1) + + if not isinstance(port, list): + port = [port] + + port = list(map(int, port)) + + if not isinstance(name, list): + name = [name] + + valid_regex = True + for r in name: + try: + re.compile(r) + except re.error: + print('Invalid regex: ' + r) + valid_regex = False + + if not valid_regex: + sys.exit(1) + + service = {'protocol': protocol, 'port': port, 'name': name, 'negative_match': negative_match} + self.services.append(service) + @final def match_port(self, protocol, port, negative_match=False): protocol = protocol.lower() @@ -673,7 +709,7 @@ def calculate_elapsed_time(start_time, short=False): elapsed_time = [] if short: - elapsed_time.append(str(h)) + elapsed_time.append(str(h).zfill(2)) else: if h == 1: elapsed_time.append(str(h) + ' hour') @@ -681,7 +717,7 @@ def calculate_elapsed_time(start_time, short=False): elapsed_time.append(str(h) + ' hours') if short: - elapsed_time.append(str(m)) + elapsed_time.append(str(m).zfill(2)) else: if m == 1: elapsed_time.append(str(m) + ' minute') @@ -689,7 +725,7 @@ def calculate_elapsed_time(start_time, short=False): elapsed_time.append(str(m) + ' minutes') if short: - elapsed_time.append(str(s)) + elapsed_time.append(str(s).zfill(2)) else: if s == 1: elapsed_time.append(str(s) + ' second') @@ -1069,6 +1105,18 @@ async def scan_target(target): plugin_service_match = False plugin_tag = service.tag() + '/' + plugin.slug + for service_dict in plugin.services: + if service_dict['protocol'] == protocol and port in service_dict['port']: + for name in service_dict['name']: + if service_dict['negative_match']: + if name not in plugin.ignore_service_names: + plugin.ignore_service_names.append(name) + else: + if name not in plugin.service_names: + plugin.service_names.append(name) + else: + continue + for s in plugin.service_names: if re.search(s, service.name): plugin_service_match = True diff --git a/global.toml b/global.toml index 2ea6ce1..7dbe866 100644 --- a/global.toml +++ b/global.toml @@ -7,5 +7,4 @@ default = '/usr/share/seclists/Passwords/darkweb2017-top100.txt' help = 'A wordlist of passwords, useful for bruteforcing. Default: %(default)s' [global.domain] -default = false -help = 'The domain to use (if known). Used for DNS and/or Active Directory.' +help = 'The domain to use (if known). Used for DNS and/or Active Directory. Default: %(default)s' diff --git a/plugins/misc.py b/plugins/misc.py index 5af0105..f33f95b 100644 --- a/plugins/misc.py +++ b/plugins/misc.py @@ -1,4 +1,4 @@ -from autorecon import ServiceScan +from autorecon import ServiceScan, fformat class NmapCassandra(ServiceScan): @@ -142,3 +142,33 @@ class NmapVNC(ServiceScan): async def run(self, service): await service.execute('nmap {nmap_extra} -sV -p {port} --script="banner,(vnc* or realvnc* or ssl*) and not (brute or broadcast or dos or external or fuzzer)" --script-args="unsafe=1" -oN "{scandir}/{protocol}_{port}_vnc_nmap.txt" -oX "{scandir}/xml/{protocol}_{port}_vnc_nmap.xml" {address}') + +class WinRMDetection(ServiceScan): + + def __init__(self): + super().__init__() + self.name = 'WinRM Detection' + self.tags = ['default', 'safe', 'winrm'] + + def configure(self): + self.match_service_name('^wsman') + self.match_service('tcp', [5985, 5986], '^http') + + async def run(self, service): + filename = fformat('{scandir}/{protocol}_{port}_winrm-detection.txt') + with open(filename, mode='wt', encoding='utf8') as winrm: + winrm.write('WinRM was possibly detected running on ' + service.protocol + ' port ' + str(service.port) + '.\nCheck _manual_commands.txt for manual commands you can run against this service.') + + def manual(self, service, plugin_was_run): + service.add_manual_commands('Bruteforce logins:', [ + 'crackmapexec winrm {address} -d ' + self.get_global('domain', default='') + ' -u ' + self.get_global('username_wordlist', default='/usr/share/seclists/Usernames/top-usernames-shortlist.txt') + ' -p ' + self.get_global('password_wordlist', default='/usr/share/seclists/Passwords/darkweb2017-top100.txt') + ]) + + service.add_manual_commands('Check login (requires credentials):', [ + 'crackmapexec winrm {address} -d ' + self.get_global('domain', default='') + ' -u -p -x "whoami"' + ]) + + service.add_manual_commands('Evil WinRM (gem install evil-winrm):', [ + 'evil-winrm -u -p -i {address}', + 'evil-winrm -u -H -i {address}' + ])