From 8cb40ad564505f371c8993d5c0b02975c1249124 Mon Sep 17 00:00:00 2001 From: Tib3rius <48113936+Tib3rius@users.noreply.github.com> Date: Sat, 28 Aug 2021 23:59:01 -0400 Subject: [PATCH] Added --ports Added ability to scan specific ports. --- autorecon.py | 97 ++++++++++++++++++++++++++++++++++++ plugins/default-port-scan.py | 21 +++++++- 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/autorecon.py b/autorecon.py index 51923c5..1b7f1f9 100644 --- a/autorecon.py +++ b/autorecon.py @@ -27,6 +27,7 @@ class Target: self.reportdir = '' self.scandir = '' self.lock = asyncio.Lock() + self.ports = None self.pending_services = [] self.services = [] self.scans = [] @@ -326,6 +327,7 @@ class PortScan(Plugin): def __init__(self): super().__init__() + self.type = None async def run(self, target): raise NotImplementedError @@ -440,6 +442,7 @@ class AutoRecon(object): self.excluded_tags = [] self.patterns = [] self.configurable_keys = [ + 'ports', 'max_scans', 'max_port_scans', 'tags', @@ -463,6 +466,7 @@ class AutoRecon(object): self.config = { 'protected_classes': ['autorecon', 'target', 'service', 'commandstreamreader', 'plugin', 'portscan', 'servicescan', 'global', 'pattern'], 'global_file': os.path.dirname(os.path.realpath(__file__)) + '/global.toml', + 'ports': None, 'max_scans': 50, 'max_port_scans': None, 'tags': 'default', @@ -819,6 +823,23 @@ def handle_keyboard(key): info('{bgreen}' + current_time + '{rst} - There is {byellow}1{rst} scan still running against {byellow}' + target.address + '{rst}' + tasks_list) async def port_scan(plugin, target): + if autorecon.config['ports']['tcp'] or autorecon.config['ports']['udp']: + target.ports = {'tcp':None, 'udp':None} + if autorecon.config['ports']['tcp']: + target.ports['tcp'] = ','.join(autorecon.config['ports']['tcp']) + if autorecon.config['ports']['udp']: + target.ports['udp'] = ','.join(autorecon.config['ports']['udp']) + if plugin.type is None: + warn('Port scan {bblue}' + plugin.name + ' (' + plugin.slug + '){rst} does not have a type set, and --ports was used. Skipping.') + return {'type':'port', 'plugin':plugin, 'result':[]} + else: + if plugin.type == 'tcp' and not autorecon.config['ports']['tcp']: + warn('Port scan {bblue}' + plugin.name + ' (' + plugin.slug + '){rst} is a TCP port scan but no TCP ports were set using --ports. Skipping') + return {'type':'port', 'plugin':plugin, 'result':[]} + elif plugin.type == 'udp' and not autorecon.config['ports']['udp']: + warn('Port scan {bblue}' + plugin.name + ' (' + plugin.slug + '){rst} is a UDP port scan but no UDP ports were set using --ports. Skipping') + return {'type':'port', 'plugin':plugin, 'result':[]} + async with target.autorecon.port_scan_semaphore: info('Port scan {bblue}' + plugin.name + ' (' + plugin.slug + '){rst} running against {byellow}' + target.address + '{rst}') @@ -1257,6 +1278,7 @@ async def main(): parser = argparse.ArgumentParser(add_help=False, description='Network reconnaissance tool to port scan and automatically enumerate services found on multiple targets.') parser.add_argument('targets', action='store', help='IP addresses (e.g. 10.0.0.1), CIDR notation (e.g. 10.0.0.1/24), or resolvable hostnames (e.g. foo.bar) to scan.', nargs='*') parser.add_argument('-t', '--targets', action='store', type=str, default='', dest='target_file', help='Read targets from file.') + parser.add_argument('-p', '--ports', action='store', type=str, help='Comma separated list of ports / port ranges to scan. Specify TCP/UDP ports by prepending list with T:/U: To scan both TCP/UDP, put port(s) at start or specify B: e.g. 53,T:21-25,80,U:123,B:123. Default: %(default)s') parser.add_argument('-m', '--max-scans', action='store', type=int, help='The maximum number of concurrent scans to run. Default: %(default)s') parser.add_argument('-mp', '--max-port-scans', action='store', type=int, help='The maximum number of concurrent port scans to run. Default: 10 (approx 20%% of max-scans unless specified)') parser.add_argument('-c', '--config', action='store', type=str, default=os.path.dirname(os.path.realpath(__file__)) + '/config.toml', dest='config_file', help='Location of AutoRecon\'s config file. Default: %(default)s') @@ -1465,6 +1487,81 @@ async def main(): autorecon.config[key] = args_dict[key] autorecon.args = args + if autorecon.config['ports']: + ports_to_scan = {'tcp':[], 'udp':[]} + unique = {'tcp':[], 'udp':[]} + + ports = autorecon.config['ports'].split(',') + mode = 'both' + for port in ports: + port = port.strip() + if port == '': + continue + + if port.startswith('B:'): + mode = 'both' + port = port.split('B:')[1] + elif port.startswith('T:'): + mode = 'tcp' + port = port.split('T:')[1] + elif port.startswith('U:'): + mode = 'udp' + port = port.split('U:')[1] + + match = re.search('^([0-9]+)\-([0-9]+)$', port) + if match: + num1 = int(match.group(1)) + num2 = int(match.group(2)) + + if num1 > 65535: + fail('Error: A provided port number was too high: ' + str(num1)) + + if num2 > 65535: + fail('Error: A provided port number was too high: ' + str(num2)) + + if num1 == num2: + port_range = [num1] + + if num2 > num1: + port_range = list(range(num1, num2 + 1, 1)) + else: + port_range = list(range(num2, num1 + 1, 1)) + num1 = num1 + num2 + num2 = num1 - num2 + num1 = num1 - num2 + + if mode == 'tcp' or mode == 'both': + for num in port_range: + if num in ports_to_scan['tcp']: + ports_to_scan['tcp'].remove(num) + ports_to_scan['tcp'].append(str(num1) + '-' + str(num2)) + unique['tcp'] = list(set(unique['tcp'] + port_range)) + + if mode == 'udp' or mode == 'both': + for num in port_range: + if num in ports_to_scan['udp']: + ports_to_scan['udp'].remove(num) + ports_to_scan['udp'].append(str(num1) + '-' + str(num2)) + unique['udp'] = list(set(unique['tcp'] + port_range)) + else: + match = re.search('^[0-9]+$', port) + if match: + num = int(port) + + if num > 65535: + fail('Error: A provided port number was too high: ' + str(num)) + + if mode == 'tcp' or mode == 'both': + ports_to_scan['tcp'].append(str(num)) if num not in unique['tcp'] else ports_to_scan['tcp'] + unique['tcp'].append(num) + + if mode == 'udp' or mode == 'both': + ports_to_scan['udp'].append(str(num)) if num not in unique['udp'] else ports_to_scan['udp'] + unique['udp'].append(num) + else: + fail('Error: Invalid port number: ' + str(port)) + autorecon.config['ports'] = ports_to_scan + if autorecon.config['max_scans'] <= 0: error('Argument -m/--max-scans must be at least 1.') errors = True diff --git a/plugins/default-port-scan.py b/plugins/default-port-scan.py index 7ac82f3..0cd812e 100644 --- a/plugins/default-port-scan.py +++ b/plugins/default-port-scan.py @@ -6,11 +6,18 @@ class QuickTCPPortScan(PortScan): def __init__(self): super().__init__() self.name = "Top TCP Ports" + self.type = 'tcp' self.tags = ["default", "default-port-scan"] self.priority = 0 async def run(self, target): - process, stdout, stderr = await target.execute('nmap {nmap_extra} -sV -sC --version-all -oN "{scandir}/_quick_tcp_nmap.txt" -oX "{scandir}/xml/_quick_tcp_nmap.xml" {address}', blocking=False) + if target.ports: + if target.ports['tcp']: + process, stdout, stderr = await target.execute('nmap {nmap_extra} -A --osscan-guess --version-all -p ' + target.ports['tcp'] + ' -oN "{scandir}/_custom_ports_tcp_nmap.txt" -oX "{scandir}/xml/_custom_ports_tcp_nmap.xml" {address}', blocking=False) + else: + return [] + else: + process, stdout, stderr = await target.execute('nmap {nmap_extra} -A --osscan-guess --version-all -oN "{scandir}/_quick_tcp_nmap.txt" -oX "{scandir}/xml/_quick_tcp_nmap.xml" {address}', blocking=False) services = await target.extract_services(stdout) await process.wait() return services @@ -20,9 +27,12 @@ class AllTCPPortScan(PortScan): def __init__(self): super().__init__() self.name = "All TCP Ports" + self.type = 'tcp' self.tags = ["default", "default-port-scan", "long"] async def run(self, target): + if target.ports: # Don't run this plugin if there are custom ports. + return [] process, stdout, stderr = await target.execute('nmap {nmap_extra} -A --osscan-guess --version-all -p- -oN "{scandir}/_full_tcp_nmap.txt" -oX "{scandir}/xml/_full_tcp_nmap.xml" {address}', blocking=False) services = await target.extract_services(stdout) await process.wait() @@ -33,12 +43,19 @@ class Top100UDPPortScan(PortScan): def __init__(self): super().__init__() self.name = "Top 100 UDP Ports" + self.type = 'udp' self.tags = ["default", "default-port-scan", "long"] async def run(self, target): # Only run UDP scan if user is root. if os.getuid() == 0: - process, stdout, stderr = await target.execute('nmap {nmap_extra} -sU -A --version-all --top-ports 100 -oN "{scandir}/_top_20_udp_nmap.txt" -oX "{scandir}/xml/_top_20_udp_nmap.xml" {address}', blocking=False) + if target.ports: + if target.ports['udp']: + process, stdout, stderr = await target.execute('nmap {nmap_extra} -sU -A --version-all -p ' + target.ports['udp'] + ' -oN "{scandir}/_custom_ports_udp_nmap.txt" -oX "{scandir}/xml/_custom_ports_udp_nmap.xml" {address}', blocking=False) + else: + return [] + else: + process, stdout, stderr = await target.execute('nmap {nmap_extra} -sU -A --version-all --top-ports 100 -oN "{scandir}/_top_100_udp_nmap.txt" -oX "{scandir}/xml/_top_100_udp_nmap.xml" {address}', blocking=False) services = await target.extract_services(stdout) await process.wait() return services