initial version of dependent plugins

This commit is contained in:
blockomat2100 2021-11-08 07:09:39 +01:00
parent 3b553e1933
commit 52ba61e6eb
8 changed files with 601 additions and 453 deletions

View File

40
autorecon/helper/scan.py Normal file
View File

@ -0,0 +1,40 @@
import time
def calculate_elapsed_time(start_time, short=False):
elapsed_seconds = round(time.time() - start_time)
m, s = divmod(elapsed_seconds, 60)
h, m = divmod(m, 60)
elapsed_time = []
if short:
elapsed_time.append(str(h).zfill(2))
else:
if h == 1:
elapsed_time.append(str(h) + ' hour')
elif h > 1:
elapsed_time.append(str(h) + ' hours')
if short:
elapsed_time.append(str(m).zfill(2))
else:
if m == 1:
elapsed_time.append(str(m) + ' minute')
elif m > 1:
elapsed_time.append(str(m) + ' minutes')
if short:
elapsed_time.append(str(s).zfill(2))
else:
if s == 1:
elapsed_time.append(str(s) + ' second')
elif s > 1:
elapsed_time.append(str(s) + ' seconds')
else:
elapsed_time.append('less than a second')
if short:
return ':'.join(elapsed_time)
else:
return ', '.join(elapsed_time)

View File

@ -97,11 +97,12 @@ def fail(*args, sep=' ', end='\n', file=sys.stderr, **kvargs):
class CommandStreamReader(object): class CommandStreamReader(object):
def __init__(self, stream, target, tag, patterns=None, outfile=None): def __init__(self, stream, target, tag, patterns=None, outfile=None, plugin=None):
self.stream = stream self.stream = stream
self.target = target self.target = target
self.tag = tag self.tag = tag
self.lines = [] self.lines = []
self.plugin = plugin
self.patterns = patterns or [] self.patterns = patterns or []
self.outfile = outfile self.outfile = outfile
self.ended = False self.ended = False
@ -136,6 +137,22 @@ class CommandStreamReader(object):
else: else:
info('{bright}[{yellow}' + self.target.address + '{crst}/{bgreen}' + self.tag + '{crst}]{rst} {bmagenta}Matched Pattern: ' + match + '{rst}', verbosity=2) info('{bright}[{yellow}' + self.target.address + '{crst}/{bgreen}' + self.tag + '{crst}]{rst} {bmagenta}Matched Pattern: ' + match + '{rst}', verbosity=2)
file.writelines('Matched Pattern: ' + match + '\n\n') file.writelines('Matched Pattern: ' + match + '\n\n')
debug(str(self.plugin.__dict__))
next_plugins = self.target.autorecon.get_next_service_scan_plugins(self.plugin)
info(str(next_plugins))
for next_plugin in next_plugins:
info("Dict: %s" % str(self.target.__dict__))
for service, details in self.target.scans.get('services', {}).items():
for key, value in details.items():
if value.get('plugin') == self.plugin:
info("Value: %s" % str(value))
# info("Value: %s" % str(value))
#info("Service Details: %s" % str(details))
#new_service = Service()
self.target.autorecon.queue_new_service_scan(next_plugin, service)
#for next_plugin in next_plugins:
# async def service_scan(plugin, service, run_from_service_scan=False):
#autorecon_queue_service_scan(next_plugin, run_fr)
if self.outfile is not None: if self.outfile is not None:
with open(self.outfile, 'a') as writer: with open(self.outfile, 'a') as writer:

View File

@ -13,8 +13,9 @@ except ModuleNotFoundError:
colorama.init() colorama.init()
from autorecon.config import config, configurable_keys, configurable_boolean_keys from autorecon.config import config, configurable_keys, configurable_boolean_keys
from autorecon.helper.scan import calculate_elapsed_time
from autorecon.io import slugify, e, fformat, cprint, debug, info, warn, error, fail, CommandStreamReader from autorecon.io import slugify, e, fformat, cprint, debug, info, warn, error, fail, CommandStreamReader
from autorecon.plugins import Pattern, PortScan, ServiceScan, Report, AutoRecon from autorecon.plugins import Pattern, PortScan, ServiceScan, Report, AutoRecon, service_scan, get_semaphore
from autorecon.targets import Target, Service from autorecon.targets import Target, Service
VERSION = "2.0.5" VERSION = "2.0.5"
@ -41,43 +42,6 @@ terminal_settings = termios.tcgetattr(sys.stdin.fileno())
autorecon = AutoRecon() autorecon = AutoRecon()
def calculate_elapsed_time(start_time, short=False):
elapsed_seconds = round(time.time() - start_time)
m, s = divmod(elapsed_seconds, 60)
h, m = divmod(m, 60)
elapsed_time = []
if short:
elapsed_time.append(str(h).zfill(2))
else:
if h == 1:
elapsed_time.append(str(h) + ' hour')
elif h > 1:
elapsed_time.append(str(h) + ' hours')
if short:
elapsed_time.append(str(m).zfill(2))
else:
if m == 1:
elapsed_time.append(str(m) + ' minute')
elif m > 1:
elapsed_time.append(str(m) + ' minutes')
if short:
elapsed_time.append(str(s).zfill(2))
else:
if s == 1:
elapsed_time.append(str(s) + ' second')
elif s > 1:
elapsed_time.append(str(s) + ' seconds')
else:
elapsed_time.append('less than a second')
if short:
return ':'.join(elapsed_time)
else:
return ', '.join(elapsed_time)
def cancel_all_tasks(signal, frame): def cancel_all_tasks(signal, frame):
for task in asyncio.all_tasks(): for task in asyncio.all_tasks():
@ -162,40 +126,6 @@ async def keyboard():
input = input[1:] input = input[1:]
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
async def get_semaphore(autorecon):
semaphore = autorecon.service_scan_semaphore
while True:
# If service scan semaphore is locked, see if we can use port scan semaphore.
if semaphore.locked():
if semaphore != autorecon.port_scan_semaphore: # This will be true unless user sets max_scans == max_port_scans
port_scan_task_count = 0
for target in autorecon.scanning_targets:
for process_list in target.running_tasks.values():
if issubclass(process_list['plugin'].__class__, PortScan):
port_scan_task_count += 1
if not autorecon.pending_targets and (config['max_port_scans'] - port_scan_task_count) >= 1: # If no more targets, and we have room, use port scan semaphore.
if autorecon.port_scan_semaphore.locked():
await asyncio.sleep(1)
continue
semaphore = autorecon.port_scan_semaphore
break
else: # Do some math to see if we can use the port scan semaphore.
if (config['max_port_scans'] - (port_scan_task_count + (len(autorecon.pending_targets) * config['port_scan_plugin_count']))) >= 1:
if autorecon.port_scan_semaphore.locked():
await asyncio.sleep(1)
continue
semaphore = autorecon.port_scan_semaphore
break
else:
await asyncio.sleep(1)
else:
break
else:
break
return semaphore
async def port_scan(plugin, target): async def port_scan(plugin, target):
if config['ports']: if config['ports']:
if config['ports']['tcp'] or config['ports']['udp']: if config['ports']['tcp'] or config['ports']['udp']:
@ -261,93 +191,6 @@ async def port_scan(plugin, target):
info('Port scan {bblue}' + plugin.name + ' {green}(' + plugin.slug + '){rst} against {byellow}' + target.address + '{rst} finished in ' + elapsed_time, verbosity=2) info('Port scan {bblue}' + plugin.name + ' {green}(' + plugin.slug + '){rst} against {byellow}' + target.address + '{rst} finished in ' + elapsed_time, verbosity=2)
return {'type':'port', 'plugin':plugin, 'result':result} return {'type':'port', 'plugin':plugin, 'result':result}
async def service_scan(plugin, service):
semaphore = service.target.autorecon.service_scan_semaphore
if not config['force_services']:
semaphore = await get_semaphore(service.target.autorecon)
async with semaphore:
# Create variables for fformat references.
address = service.target.address
addressv6 = service.target.address
ipaddress = service.target.ip
ipaddressv6 = service.target.ip
scandir = service.target.scandir
protocol = service.protocol
port = service.port
name = service.name
if config['create_port_dirs']:
scandir = os.path.join(scandir, protocol + str(port))
os.makedirs(scandir, exist_ok=True)
os.makedirs(os.path.join(scandir, 'xml'), exist_ok=True)
# Special cases for HTTP.
http_scheme = 'https' if 'https' in service.name or service.secure is True else 'http'
nmap_extra = service.target.autorecon.args.nmap
if service.target.autorecon.args.nmap_append:
nmap_extra += ' ' + service.target.autorecon.args.nmap_append
if protocol == 'udp':
nmap_extra += ' -sU'
if service.target.ipversion == 'IPv6':
nmap_extra += ' -6'
if addressv6 == service.target.ip:
addressv6 = '[' + addressv6 + ']'
ipaddressv6 = '[' + ipaddressv6 + ']'
if config['proxychains'] and protocol == 'tcp':
nmap_extra += ' -sT'
tag = service.tag() + '/' + plugin.slug
info('Service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} running against {byellow}' + service.target.address + '{rst}', verbosity=1)
start_time = time.time()
async with service.target.lock:
service.target.running_tasks[tag] = {'plugin': plugin, 'processes': [], 'start': start_time}
try:
result = await plugin.run(service)
except Exception as ex:
exc_type, exc_value, exc_tb = sys.exc_info()
error_text = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb)[-2:])
raise Exception(cprint('Error: Service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} running against {byellow}' + service.target.address + '{rst} produced an exception:\n\n' + error_text, color=Fore.RED, char='!', printmsg=False))
for process_dict in service.target.running_tasks[tag]['processes']:
if process_dict['process'].returncode is None:
warn('A process was left running after service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} against {byellow}' + service.target.address + '{rst} finished. Please ensure non-blocking processes are awaited before the run coroutine finishes. Awaiting now.', verbosity=2)
await process_dict['process'].wait()
if process_dict['process'].returncode != 0:
errors = []
while True:
line = await process_dict['stderr'].readline()
if line is not None:
errors.append(line + '\n')
else:
break
error('Service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} ran a command against {byellow}' + service.target.address + '{rst} which returned a non-zero exit code (' + str(process_dict['process'].returncode) + '). Check ' + service.target.scandir + '/_errors.log for more details.', verbosity=2)
async with service.target.lock:
with open(os.path.join(service.target.scandir, '_errors.log'), 'a') as file:
file.writelines('[*] Service scan ' + plugin.name + ' (' + tag + ') ran a command which returned a non-zero exit code (' + str(process_dict['process'].returncode) + ').\n')
file.writelines('[-] Command: ' + process_dict['cmd'] + '\n')
if errors:
file.writelines(['[-] Error Output:\n'] + errors + ['\n'])
else:
file.writelines('\n')
elapsed_time = calculate_elapsed_time(start_time)
async with service.target.lock:
service.target.running_tasks.pop(tag, None)
info('Service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} against {byellow}' + service.target.address + '{rst} finished in ' + elapsed_time, verbosity=2)
return {'type':'service', 'plugin':plugin, 'result':result}
async def generate_report(plugin, targets): async def generate_report(plugin, targets):
semaphore = autorecon.service_scan_semaphore semaphore = autorecon.service_scan_semaphore
@ -400,7 +243,8 @@ async def scan_target(target):
target.reportdir = reportdir target.reportdir = reportdir
pending = [] # pending = []
autorecon = target.autorecon
heartbeat = asyncio.create_task(start_heartbeat(target, period=config['heartbeat'])) heartbeat = asyncio.create_task(start_heartbeat(target, period=config['heartbeat']))
@ -423,7 +267,7 @@ async def scan_target(target):
services.append(service) services.append(service)
if services: if services:
pending.append(asyncio.create_task(asyncio.sleep(0))) autorecon.pending.append(asyncio.create_task(asyncio.sleep(0)))
else: else:
error('No services were defined. Please check your service syntax: [tcp|udp]/<port>/<service-name>/[secure|insecure]') error('No services were defined. Please check your service syntax: [tcp|udp]/<port>/<service-name>/[secure|insecure]')
heartbeat.cancel() heartbeat.cancel()
@ -454,7 +298,7 @@ async def scan_target(target):
if matching_tags and not excluded_tags: if matching_tags and not excluded_tags:
target.scans['ports'][plugin.slug] = {'plugin':plugin, 'commands':[]} target.scans['ports'][plugin.slug] = {'plugin':plugin, 'commands':[]}
pending.append(asyncio.create_task(port_scan(plugin, target))) autorecon.pending.append(asyncio.create_task(port_scan(plugin, target)))
async with autorecon.lock: async with autorecon.lock:
autorecon.scanning_targets.append(target) autorecon.scanning_targets.append(target)
@ -463,8 +307,9 @@ async def scan_target(target):
info('Scanning target {byellow}' + target.address + '{rst}') info('Scanning target {byellow}' + target.address + '{rst}')
timed_out = False timed_out = False
while pending: while autorecon.pending:
done, pending = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED, timeout=1) done, autorecon.pending = await asyncio.wait(autorecon.pending, return_when=asyncio.FIRST_COMPLETED, timeout=1)
autorecon.pending = list(autorecon.pending)
# Check if global timeout has occurred. # Check if global timeout has occurred.
if config['target_timeout'] is not None: if config['target_timeout'] is not None:
@ -690,7 +535,7 @@ async def scan_target(target):
target.scans['services'][service] = {} target.scans['services'][service] = {}
target.scans['services'][service][plugin_tag] = {'plugin':plugin, 'commands':[]} target.scans['services'][service][plugin_tag] = {'plugin':plugin, 'commands':[]}
pending.add(asyncio.create_task(service_scan(plugin, service))) autorecon.pending.append(asyncio.create_task(service_scan(plugin, service)))
if not service_match: if not service_match:
warn('{byellow}[' + target.address + ']{srst} Service ' + service.full_tag() + ' did not match any plugins based on the service name.{rst}', verbosity=2) warn('{byellow}[' + target.address + ']{srst} Service ' + service.full_tag() + ' did not match any plugins based on the service name.{rst}', verbosity=2)
@ -717,17 +562,17 @@ async def scan_target(target):
break break
if matching_tags and not excluded_tags: if matching_tags and not excluded_tags:
pending.add(asyncio.create_task(generate_report(plugin, [target]))) autorecon.pending.append(asyncio.create_task(generate_report(plugin, [target])))
while pending:
done, pending = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED, timeout=1)
while autorecon.pending:
done, autorecon.pending = await asyncio.wait(autorecon.pending, return_when=asyncio.FIRST_COMPLETED, timeout=1)
autorecon.pending = list(autorecon.pending)
heartbeat.cancel() heartbeat.cancel()
elapsed_time = calculate_elapsed_time(start_time) elapsed_time = calculate_elapsed_time(start_time)
if timed_out: if timed_out:
for task in pending: for task in autorecon.pending:
task.cancel() task.cancel()
for process_list in target.running_tasks.values(): for process_list in target.running_tasks.values():

View File

@ -1,355 +1,563 @@
import asyncio, inspect, os, re, sys import asyncio, inspect, re
from typing import final from typing import final
from autorecon.config import config from autorecon.io import slugify, fail, CommandStreamReader
from autorecon.io import slugify, error, fail, CommandStreamReader
from autorecon.targets import Service from autorecon.targets import Service
from autorecon.config import config
from autorecon.io import info, warn, error, cprint
import os
import sys
import time
import traceback
from colorama import Fore
from autorecon.helper.scan import calculate_elapsed_time
async def get_semaphore(autorecon):
semaphore = autorecon.service_scan_semaphore
while True:
# If service scan semaphore is locked, see if we can use port scan semaphore.
if semaphore.locked():
if semaphore != autorecon.port_scan_semaphore: # This will be true unless user sets max_scans == max_port_scans
port_scan_task_count = 0
for target in autorecon.scanning_targets:
for process_list in target.running_tasks.values():
info(str(process_list['plugin'].__dict__))
info(type(process_list['plugin']))
if issubclass(process_list['plugin'].__class__, PortScan):
port_scan_task_count += 1
if not autorecon.pending_targets and (config[
'max_port_scans'] - port_scan_task_count) >= 1: # If no more targets, and we have room, use port scan semaphore.
if autorecon.port_scan_semaphore.locked():
await asyncio.sleep(1)
continue
semaphore = autorecon.port_scan_semaphore
break
else: # Do some math to see if we can use the port scan semaphore.
if (config['max_port_scans'] - (port_scan_task_count + (
len(autorecon.pending_targets) * config['port_scan_plugin_count']))) >= 1:
if autorecon.port_scan_semaphore.locked():
await asyncio.sleep(1)
continue
semaphore = autorecon.port_scan_semaphore
break
else:
await asyncio.sleep(1)
else:
break
else:
break
return semaphore
async def service_scan(plugin, service, run_from_service_scan=False):
# skip running service scan plugins that are meant to be run for specific services
if plugin.has_previous_plugins() and not run_from_service_scan:
return
semaphore = service.target.autorecon.service_scan_semaphore
if not config['force_services']:
semaphore = await get_semaphore(service.target.autorecon)
async with semaphore:
# Create variables for fformat references.
address = service.target.address
addressv6 = service.target.address
ipaddress = service.target.ip
ipaddressv6 = service.target.ip
scandir = service.target.scandir
protocol = service.protocol
port = service.port
name = service.name
if config['create_port_dirs']:
scandir = os.path.join(scandir, protocol + str(port))
os.makedirs(scandir, exist_ok=True)
os.makedirs(os.path.join(scandir, 'xml'), exist_ok=True)
# Special cases for HTTP.
http_scheme = 'https' if 'https' in service.name or service.secure is True else 'http'
nmap_extra = service.target.autorecon.args.nmap
if service.target.autorecon.args.nmap_append:
nmap_extra += ' ' + service.target.autorecon.args.nmap_append
if protocol == 'udp':
nmap_extra += ' -sU'
if service.target.ipversion == 'IPv6':
nmap_extra += ' -6'
if addressv6 == service.target.ip:
addressv6 = '[' + addressv6 + ']'
ipaddressv6 = '[' + ipaddressv6 + ']'
if config['proxychains'] and protocol == 'tcp':
nmap_extra += ' -sT'
tag = service.tag() + '/' + plugin.slug
info(
'Service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} running against {byellow}' + service.target.address + '{rst}',
verbosity=1)
start_time = time.time()
async with service.target.lock:
service.target.running_tasks[tag] = {'plugin': plugin, 'processes': [], 'start': start_time}
try:
result = await plugin.run(service)
except Exception as ex:
exc_type, exc_value, exc_tb = sys.exc_info()
error_text = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb)[-2:])
raise Exception(cprint(
'Error: Service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} running against {byellow}' + service.target.address + '{rst} produced an exception:\n\n' + error_text,
color=Fore.RED, char='!', printmsg=False))
for process_dict in service.target.running_tasks[tag]['processes']:
if process_dict['process'].returncode is None:
warn(
'A process was left running after service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} against {byellow}' + service.target.address + '{rst} finished. Please ensure non-blocking processes are awaited before the run coroutine finishes. Awaiting now.',
verbosity=2)
await process_dict['process'].wait()
if process_dict['process'].returncode != 0:
errors = []
while True:
line = await process_dict['stderr'].readline()
if line is not None:
errors.append(line + '\n')
else:
break
error(
'Service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} ran a command against {byellow}' + service.target.address + '{rst} which returned a non-zero exit code (' + str(
process_dict[
'process'].returncode) + '). Check ' + service.target.scandir + '/_errors.log for more details.',
verbosity=2)
async with service.target.lock:
with open(os.path.join(service.target.scandir, '_errors.log'), 'a') as file:
file.writelines(
'[*] Service scan ' + plugin.name + ' (' + tag + ') ran a command which returned a non-zero exit code (' + str(
process_dict['process'].returncode) + ').\n')
file.writelines('[-] Command: ' + process_dict['cmd'] + '\n')
if errors:
file.writelines(['[-] Error Output:\n'] + errors + ['\n'])
else:
file.writelines('\n')
elapsed_time = calculate_elapsed_time(start_time)
async with service.target.lock:
service.target.running_tasks.pop(tag, None)
info(
'Service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} against {byellow}' + service.target.address + '{rst} finished in ' + elapsed_time,
verbosity=2)
return {'type': 'service', 'plugin': plugin, 'result': result}
class Pattern: class Pattern:
def __init__(self, pattern, description=None, plugins=None):
self.pattern = pattern
self.description = description
if not plugins:
self.plugins = []
else:
self.plugins = plugins
def __init__(self, pattern, description=None):
self.pattern = pattern
self.description = description
class Plugin(object): class Plugin(object):
def __init__(self): def __init__(self):
self.name = None self.name = None
self.slug = None self.slug = None
self.description = None self.description = None
self.tags = ['default'] self.tags = ['default']
self.priority = 1 self.priority = 1
self.patterns = [] self.patterns = []
self.autorecon = None self.autorecon = None
self.disabled = False self.disabled = False
@final @final
def add_option(self, name, default=None, help=None): def add_option(self, name, default=None, help=None):
self.autorecon.add_argument(self, name, metavar='VALUE', default=default, help=help) self.autorecon.add_argument(self, name, metavar='VALUE', default=default, help=help)
@final @final
def add_constant_option(self, name, const, default=None, help=None): def add_constant_option(self, name, const, default=None, help=None):
self.autorecon.add_argument(self, name, action='store_const', const=const, default=default, help=help) self.autorecon.add_argument(self, name, action='store_const', const=const, default=default, help=help)
@final @final
def add_true_option(self, name, help=None): def add_true_option(self, name, help=None):
self.autorecon.add_argument(self, name, action='store_true', help=help) self.autorecon.add_argument(self, name, action='store_true', help=help)
@final @final
def add_false_option(self, name, help=None): def add_false_option(self, name, help=None):
self.autorecon.add_argument(self, name, action='store_false', help=help) self.autorecon.add_argument(self, name, action='store_false', help=help)
@final @final
def add_list_option(self, name, default=None, help=None): def add_list_option(self, name, default=None, help=None):
self.autorecon.add_argument(self, name, nargs='+', metavar='VALUE', default=default, help=help) self.autorecon.add_argument(self, name, nargs='+', metavar='VALUE', default=default, help=help)
@final @final
def add_choice_option(self, name, choices, default=None, help=None): def add_choice_option(self, name, choices, default=None, help=None):
if not isinstance(choices, list): if not isinstance(choices, list):
fail('The choices argument for ' + self.name + '\'s ' + name + ' choice option should be a list.') fail('The choices argument for ' + self.name + '\'s ' + name + ' choice option should be a list.')
self.autorecon.add_argument(self, name, choices=choices, default=default, help=help) self.autorecon.add_argument(self, name, choices=choices, default=default, help=help)
@final @final
def get_option(self, name): def get_option(self, name):
# TODO: make sure name is simple. # TODO: make sure name is simple.
name = self.slug.replace('-', '_') + '.' + slugify(name).replace('-', '_') name = self.slug.replace('-', '_') + '.' + slugify(name).replace('-', '_')
if name in vars(self.autorecon.args): if name in vars(self.autorecon.args):
return vars(self.autorecon.args)[name] return vars(self.autorecon.args)[name]
else: else:
return None return None
@final @final
def get_global_option(self, name, default=None): def get_global_option(self, name, default=None):
name = 'global.' + slugify(name).replace('-', '_') name = 'global.' + slugify(name).replace('-', '_')
if name in vars(self.autorecon.args): if name in vars(self.autorecon.args):
if vars(self.autorecon.args)[name] is None: if vars(self.autorecon.args)[name] is None:
if default: if default:
return default return default
else: else:
return None return None
else: else:
return vars(self.autorecon.args)[name] return vars(self.autorecon.args)[name]
else: else:
if default: if default:
return default return default
return None return None
@final @final
def get_global(self, name, default=None): def get_global(self, name, default=None):
return self.get_global_option(name, default) return self.get_global_option(name, default)
@final
def add_pattern(self, pattern, description=None, plugins=None):
try:
compiled = re.compile(pattern)
if description:
self.patterns.append(Pattern(compiled, description=description, plugins=plugins))
else:
self.patterns.append(Pattern(compiled, plugins=plugins))
except re.error:
fail('Error: The pattern "' + pattern + '" in the plugin "' + self.name + '" is invalid regex.')
@final
def add_pattern(self, pattern, description=None):
try:
compiled = re.compile(pattern)
if description:
self.patterns.append(Pattern(compiled, description=description))
else:
self.patterns.append(Pattern(compiled))
except re.error:
fail('Error: The pattern "' + pattern + '" in the plugin "' + self.name + '" is invalid regex.')
class PortScan(Plugin): class PortScan(Plugin):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.type = None self.type = None
self.specific_ports = False self.specific_ports = False
async def run(self, target):
raise NotImplementedError
async def run(self, target):
raise NotImplementedError
class ServiceScan(Plugin): class ServiceScan(Plugin):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.ports = {'tcp':[], 'udp':[]} self.ports = {'tcp': [], 'udp': []}
self.ignore_ports = {'tcp':[], 'udp':[]} self.ignore_ports = {'tcp': [], 'udp': []}
self.services = [] self.services = []
self.service_names = [] self.service_names = []
self.ignore_service_names = [] self.ignore_service_names = []
self.run_once_boolean = False self.run_once_boolean = False
self.require_ssl_boolean = False self.require_ssl_boolean = False
@final @final
def match_service(self, protocol, port, name, negative_match=False): def match_service(self, protocol, port, name, negative_match=False):
protocol = protocol.lower() protocol = protocol.lower()
if protocol not in ['tcp', 'udp']: if protocol not in ['tcp', 'udp']:
print('Invalid protocol.') print('Invalid protocol.')
sys.exit(1) sys.exit(1)
if not isinstance(port, list): if not isinstance(port, list):
port = [port] port = [port]
port = list(map(int, port)) port = list(map(int, port))
if not isinstance(name, list): if not isinstance(name, list):
name = [name] name = [name]
valid_regex = True valid_regex = True
for r in name: for r in name:
try: try:
re.compile(r) re.compile(r)
except re.error: except re.error:
print('Invalid regex: ' + r) print('Invalid regex: ' + r)
valid_regex = False valid_regex = False
if not valid_regex: if not valid_regex:
sys.exit(1) sys.exit(1)
service = {'protocol': protocol, 'port': port, 'name': name, 'negative_match': negative_match} service = {'protocol': protocol, 'port': port, 'name': name, 'negative_match': negative_match}
self.services.append(service) self.services.append(service)
@final @final
def match_port(self, protocol, port, negative_match=False): def match_port(self, protocol, port, negative_match=False):
protocol = protocol.lower() protocol = protocol.lower()
if protocol not in ['tcp', 'udp']: if protocol not in ['tcp', 'udp']:
print('Invalid protocol.') print('Invalid protocol.')
sys.exit(1) sys.exit(1)
else: else:
if not isinstance(port, list): if not isinstance(port, list):
port = [port] port = [port]
port = list(map(int, port)) port = list(map(int, port))
if negative_match: if negative_match:
self.ignore_ports[protocol] = list(set(self.ignore_ports[protocol] + port)) self.ignore_ports[protocol] = list(set(self.ignore_ports[protocol] + port))
else: else:
self.ports[protocol] = list(set(self.ports[protocol] + port)) self.ports[protocol] = list(set(self.ports[protocol] + port))
@final @final
def match_service_name(self, name, negative_match=False): def match_service_name(self, name, negative_match=False):
if not isinstance(name, list): if not isinstance(name, list):
name = [name] name = [name]
valid_regex = True valid_regex = True
for r in name: for r in name:
try: try:
re.compile(r) re.compile(r)
except re.error: except re.error:
print('Invalid regex: ' + r) print('Invalid regex: ' + r)
valid_regex = False valid_regex = False
if valid_regex: if valid_regex:
if negative_match: if negative_match:
self.ignore_service_names = list(set(self.ignore_service_names + name)) self.ignore_service_names = list(set(self.ignore_service_names + name))
else: else:
self.service_names = list(set(self.service_names + name)) self.service_names = list(set(self.service_names + name))
else: else:
sys.exit(1) sys.exit(1)
@final @final
def require_ssl(self, boolean): def require_ssl(self, boolean):
self.require_ssl_boolean = boolean self.require_ssl_boolean = boolean
@final @final
def run_once(self, boolean): def run_once(self, boolean):
self.run_once_boolean = boolean self.run_once_boolean = boolean
@final
def match_all_service_names(self, boolean):
if boolean:
# Add a "match all" service name.
self.match_service_name('.*')
def get_previous_plugin_names(self):
return []
@final
def has_previous_plugins(self):
if self.get_previous_plugin_names():
return True
return False
@final
def match_all_service_names(self, boolean):
if boolean:
# Add a "match all" service name.
self.match_service_name('.*')
class Report(Plugin): class Report(Plugin):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
class AutoRecon(object): class AutoRecon(object):
def __init__(self): def __init__(self):
self.pending_targets = [] self.pending = []
self.scanning_targets = [] self.pending_targets = []
self.completed_targets = [] self.scanning_targets = []
self.plugins = {} self.completed_targets = []
self.__slug_regex = re.compile('^[a-z0-9\-]+$') self.plugins = {}
self.plugin_types = {'port':[], 'service':[], 'report':[]} self.__slug_regex = re.compile('^[a-z0-9\-]+$')
self.port_scan_semaphore = None self.plugin_types = {'port': [], 'service': [], 'report': []}
self.service_scan_semaphore = None self.port_scan_semaphore = None
self.argparse = None self.service_scan_semaphore = None
self.argparse_group = None self.argparse = None
self.args = None self.argparse_group = None
self.missing_services = [] self.args = None
self.taglist = [] self.missing_services = []
self.tags = [] self.taglist = []
self.excluded_tags = [] self.tags = []
self.patterns = [] self.excluded_tags = []
self.errors = False self.patterns = []
self.lock = asyncio.Lock() self.errors = False
self.load_slug = None self.lock = asyncio.Lock()
self.load_module = None self.load_slug = None
self.load_module = None
def add_argument(self, plugin, name, **kwargs): def add_argument(self, plugin, name, **kwargs):
# TODO: make sure name is simple. # TODO: make sure name is simple.
name = '--' + plugin.slug + '.' + slugify(name) name = '--' + plugin.slug + '.' + slugify(name)
if self.argparse_group is None: if self.argparse_group is None:
self.argparse_group = self.argparse.add_argument_group('plugin arguments', description='These are optional arguments for certain plugins.') self.argparse_group = self.argparse.add_argument_group('plugin arguments',
self.argparse_group.add_argument(name, **kwargs) description='These are optional arguments for certain plugins.')
self.argparse_group.add_argument(name, **kwargs)
def extract_service(self, line, regex): def extract_service(self, line, regex):
if regex is None: if regex is None:
regex = '^(?P<port>\d+)\/(?P<protocol>(tcp|udp))(.*)open(\s*)(?P<service>[\w\-\/]+)(\s*)(.*)$' regex = '^(?P<port>\d+)\/(?P<protocol>(tcp|udp))(.*)open(\s*)(?P<service>[\w\-\/]+)(\s*)(.*)$'
match = re.search(regex, line) match = re.search(regex, line)
if match: if match:
protocol = match.group('protocol').lower() protocol = match.group('protocol').lower()
port = int(match.group('port')) port = int(match.group('port'))
service = match.group('service') service = match.group('service')
secure = True if 'ssl' in service or 'tls' in service else False secure = True if 'ssl' in service or 'tls' in service else False
if service.startswith('ssl/') or service.startswith('tls/'): if service.startswith('ssl/') or service.startswith('tls/'):
service = service[4:] service = service[4:]
return Service(protocol, port, service, secure) return Service(protocol, port, service, secure)
else: else:
return None return None
async def extract_services(self, stream, regex): async def extract_services(self, stream, regex):
if not isinstance(stream, CommandStreamReader): if not isinstance(stream, CommandStreamReader):
print('Error: extract_services must be passed an instance of a CommandStreamReader.') print('Error: extract_services must be passed an instance of a CommandStreamReader.')
sys.exit(1) sys.exit(1)
services = [] services = []
while True: while True:
line = await stream.readline() line = await stream.readline()
if line is not None: if line is not None:
service = self.extract_service(line, regex) service = self.extract_service(line, regex)
if service: if service:
services.append(service) services.append(service)
else: else:
break break
return services return services
def register(self, plugin, filename): def register(self, plugin, filename):
if plugin.disabled: if plugin.disabled:
return return
if plugin.name is None:
fail(
'Error: Plugin with class name "' + plugin.__class__.__name__ + '" in ' + filename + ' does not have a name.')
if plugin.name is None: for _, loaded_plugin in self.plugins.items():
fail('Error: Plugin with class name "' + plugin.__class__.__name__ + '" in ' + filename + ' does not have a name.') if plugin.name == loaded_plugin.name:
fail('Error: Duplicate plugin name "' + plugin.name + '" detected in ' + filename + '.',
file=sys.stderr)
for _, loaded_plugin in self.plugins.items(): if plugin.slug is None:
if plugin.name == loaded_plugin.name: plugin.slug = slugify(plugin.name)
fail('Error: Duplicate plugin name "' + plugin.name + '" detected in ' + filename + '.', file=sys.stderr) elif not self.__slug_regex.match(plugin.slug):
fail(
'Error: provided slug "' + plugin.slug + '" in ' + filename + ' is not valid (must only contain lowercase letters, numbers, and hyphens).',
file=sys.stderr)
if plugin.slug is None: if plugin.slug in config['protected_classes']:
plugin.slug = slugify(plugin.name) fail('Error: plugin slug "' + plugin.slug + '" in ' + filename + ' is a protected string. Please change.')
elif not self.__slug_regex.match(plugin.slug):
fail('Error: provided slug "' + plugin.slug + '" in ' + filename + ' is not valid (must only contain lowercase letters, numbers, and hyphens).', file=sys.stderr)
if plugin.slug in config['protected_classes']: if plugin.slug not in self.plugins:
fail('Error: plugin slug "' + plugin.slug + '" in ' + filename + ' is a protected string. Please change.')
if plugin.slug not in self.plugins: for _, loaded_plugin in self.plugins.items():
if plugin is loaded_plugin:
fail(
'Error: plugin "' + plugin.name + '" in ' + filename + ' already loaded as "' + loaded_plugin.name + '" (' + str(
loaded_plugin) + ')', file=sys.stderr)
for _, loaded_plugin in self.plugins.items(): configure_function_found = False
if plugin is loaded_plugin: run_coroutine_found = False
fail('Error: plugin "' + plugin.name + '" in ' + filename + ' already loaded as "' + loaded_plugin.name + '" (' + str(loaded_plugin) + ')', file=sys.stderr) manual_function_found = False
configure_function_found = False for member_name, member_value in inspect.getmembers(plugin, predicate=inspect.ismethod):
run_coroutine_found = False if member_name == 'configure':
manual_function_found = False configure_function_found = True
elif member_name == 'run' and inspect.iscoroutinefunction(member_value):
if len(inspect.getfullargspec(member_value).args) != 2:
fail(
'Error: the "run" coroutine in the plugin "' + plugin.name + '" in ' + filename + ' should have two arguments.',
file=sys.stderr)
run_coroutine_found = True
elif member_name == 'manual':
if len(inspect.getfullargspec(member_value).args) != 3:
fail(
'Error: the "manual" function in the plugin "' + plugin.name + '" in ' + filename + ' should have three arguments.',
file=sys.stderr)
manual_function_found = True
for member_name, member_value in inspect.getmembers(plugin, predicate=inspect.ismethod): if not run_coroutine_found and not manual_function_found:
if member_name == 'configure': fail(
configure_function_found = True 'Error: the plugin "' + plugin.name + '" in ' + filename + ' needs either a "manual" function, a "run" coroutine, or both.',
elif member_name == 'run' and inspect.iscoroutinefunction(member_value): file=sys.stderr)
if len(inspect.getfullargspec(member_value).args) != 2:
fail('Error: the "run" coroutine in the plugin "' + plugin.name + '" in ' + filename + ' should have two arguments.', file=sys.stderr)
run_coroutine_found = True
elif member_name == 'manual':
if len(inspect.getfullargspec(member_value).args) != 3:
fail('Error: the "manual" function in the plugin "' + plugin.name + '" in ' + filename + ' should have three arguments.', file=sys.stderr)
manual_function_found = True
if not run_coroutine_found and not manual_function_found: if issubclass(plugin.__class__, PortScan):
fail('Error: the plugin "' + plugin.name + '" in ' + filename + ' needs either a "manual" function, a "run" coroutine, or both.', file=sys.stderr) if plugin.type is None:
fail(
'Error: the PortScan plugin "' + plugin.name + '" in ' + filename + ' requires a type (either tcp or udp).')
else:
plugin.type = plugin.type.lower()
if plugin.type not in ['tcp', 'udp']:
fail(
'Error: the PortScan plugin "' + plugin.name + '" in ' + filename + ' has an invalid type (should be tcp or udp).')
self.plugin_types["port"].append(plugin)
elif issubclass(plugin.__class__, ServiceScan):
self.plugin_types["service"].append(plugin)
elif issubclass(plugin.__class__, Report):
self.plugin_types["report"].append(plugin)
else:
fail(
'Plugin "' + plugin.name + '" in ' + filename + ' is neither a PortScan, ServiceScan, nor a Report.',
file=sys.stderr)
if issubclass(plugin.__class__, PortScan): plugin.tags = [tag.lower() for tag in plugin.tags]
if plugin.type is None:
fail('Error: the PortScan plugin "' + plugin.name + '" in ' + filename + ' requires a type (either tcp or udp).')
else:
plugin.type = plugin.type.lower()
if plugin.type not in ['tcp', 'udp']:
fail('Error: the PortScan plugin "' + plugin.name + '" in ' + filename + ' has an invalid type (should be tcp or udp).')
self.plugin_types["port"].append(plugin)
elif issubclass(plugin.__class__, ServiceScan):
self.plugin_types["service"].append(plugin)
elif issubclass(plugin.__class__, Report):
self.plugin_types["report"].append(plugin)
else:
fail('Plugin "' + plugin.name + '" in ' + filename + ' is neither a PortScan, ServiceScan, nor a Report.', file=sys.stderr)
plugin.tags = [tag.lower() for tag in plugin.tags] # Add plugin tags to tag list.
[self.taglist.append(t) for t in plugin.tags if t not in self.tags]
# Add plugin tags to tag list. plugin.autorecon = self
[self.taglist.append(t) for t in plugin.tags if t not in self.tags] if configure_function_found:
plugin.configure()
self.plugins[plugin.slug] = plugin
else:
fail('Error: plugin slug "' + plugin.slug + '" in ' + filename + ' is already assigned.', file=sys.stderr)
plugin.autorecon = self async def execute(self, cmd, target, tag, patterns=None, outfile=None, errfile=None, plugin=None):
if configure_function_found: if patterns:
plugin.configure() combined_patterns = self.patterns + patterns
self.plugins[plugin.slug] = plugin else:
else: combined_patterns = self.patterns
fail('Error: plugin slug "' + plugin.slug + '" in ' + filename + ' is already assigned.', file=sys.stderr)
async def execute(self, cmd, target, tag, patterns=None, outfile=None, errfile=None): process = await asyncio.create_subprocess_shell(
if patterns: cmd,
combined_patterns = self.patterns + patterns stdin=open('/dev/null'),
else: stdout=asyncio.subprocess.PIPE,
combined_patterns = self.patterns stderr=asyncio.subprocess.PIPE)
cout = CommandStreamReader(process.stdout, target, tag, patterns=combined_patterns, outfile=outfile,
plugin=plugin)
cerr = CommandStreamReader(process.stderr, target, tag, patterns=combined_patterns, outfile=errfile,
plugin=plugin)
process = await asyncio.create_subprocess_shell( asyncio.create_task(cout._read())
cmd, asyncio.create_task(cerr._read())
stdin=open('/dev/null'),
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE)
cout = CommandStreamReader(process.stdout, target, tag, patterns=combined_patterns, outfile=outfile) return process, cout, cerr
cerr = CommandStreamReader(process.stderr, target, tag, patterns=combined_patterns, outfile=errfile)
asyncio.create_task(cout._read()) def get_plugin_by_name(self, name):
asyncio.create_task(cerr._read()) for key, value in self.plugins.items():
if value.name == name:
return self.plugins[key]
return process, cout, cerr def get_next_service_scan_plugins(self, current_plugin):
next_plugins = []
for plugin in self.plugin_types['service']:
if not plugin.has_previous_plugins():
continue
for previous_plugin_name in plugin.get_previous_plugin_names():
if current_plugin.name == previous_plugin_name:
next_plugins.append(plugin)
return next_plugins
def queue_new_service_scan(self, plugin, service):
# try using append. in the main method "pending" is sometimes set() and sometimes list()
self.pending.append(asyncio.create_task(service_scan(plugin, service, run_from_service_scan=True)))

View File

@ -121,7 +121,7 @@ class Service:
self.add_manual_commands(description, command) self.add_manual_commands(description, command)
@final @final
async def execute(self, cmd, blocking=True, outfile=None, errfile=None, future_outfile=None): async def execute(self, cmd, blocking=True, outfile=None, errfile=None, future_outfile=None, plugin=None):
target = self.target target = self.target
# Create variables for command references. # Create variables for command references.
@ -182,7 +182,7 @@ class Service:
with open(os.path.join(target.scandir, '_commands.log'), 'a') as file: with open(os.path.join(target.scandir, '_commands.log'), 'a') as file:
file.writelines(cmd + '\n\n') file.writelines(cmd + '\n\n')
process, stdout, stderr = await target.autorecon.execute(cmd, target, tag, patterns=plugin.patterns, outfile=outfile, errfile=errfile) process, stdout, stderr = await target.autorecon.execute(cmd, target, tag, patterns=plugin.patterns, outfile=outfile, errfile=errfile, plugin=plugin)
target.running_tasks[tag]['processes'].append({'process': process, 'stderr': stderr, 'cmd': cmd}) target.running_tasks[tag]['processes'].append({'process': process, 'stderr': stderr, 'cmd': cmd})

View File

View File

@ -0,0 +1,38 @@
from autorecon.plugins import ServiceScan
class DirectoryListing(ServiceScan):
def __init__(self):
super().__init__()
self.name = "Directory Listing"
self.tags = ['default', 'safe', 'http', 'test']
def configure(self):
self.match_service_name('^http')
self.match_service_name('^nacn_http$', negative_match=True)
self.add_pattern('<h1>Directory listing for', description='Directory Listing enabled',
plugin_names=["Directory Listing Verify"])
async def run(self, service):
await service.execute('curl {http_scheme}://{addressv6}:{port}')
class DirectoryListingVerify(ServiceScan):
"""
this is a useless plugin that is only run, if directory listing was found.
"""
def __init__(self):
super().__init__()
self.name = "Directory Listing verify"
self.tags = ['default', 'safe', 'http', 'test']
def configure(self):
self.match_service_name('^http')
self.match_service_name('^nacn_http$', negative_match=True)
async def run(self, service):
await service.execute('curl {http_scheme}://{addressv6}:{port}/?id=1')
def get_previous_plugin_names(self):
return ["Directory Listing"]