Refactoring codebase.
Moved most of the core functionality to modules. Plugins updates with new module name.
This commit is contained in:
parent
0b37730304
commit
0efedca423
955
autorecon.py
955
autorecon.py
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,59 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
configurable_keys = [
|
||||||
|
'ports',
|
||||||
|
'max_scans',
|
||||||
|
'max_port_scans',
|
||||||
|
'tags',
|
||||||
|
'exclude_tags',
|
||||||
|
'plugins_dir',
|
||||||
|
'add_plugins-dir',
|
||||||
|
'outdir',
|
||||||
|
'single_target',
|
||||||
|
'only_scans_dir',
|
||||||
|
'create_port_dirs',
|
||||||
|
'heartbeat',
|
||||||
|
'timeout',
|
||||||
|
'target_timeout',
|
||||||
|
'nmap',
|
||||||
|
'nmap_append',
|
||||||
|
'disable_sanity_checks',
|
||||||
|
'disable_keyboard_control',
|
||||||
|
'force_services',
|
||||||
|
'accessible',
|
||||||
|
'verbose'
|
||||||
|
]
|
||||||
|
|
||||||
|
configurable_boolean_keys = [
|
||||||
|
'single_target',
|
||||||
|
'only_scans_dir',
|
||||||
|
'create_port_dirs',
|
||||||
|
'disable_sanity_checks',
|
||||||
|
'accessible'
|
||||||
|
]
|
||||||
|
|
||||||
|
config = {
|
||||||
|
'protected_classes': ['autorecon', 'target', 'service', 'commandstreamreader', 'plugin', 'portscan', 'servicescan', 'global', 'pattern'],
|
||||||
|
'global_file': os.path.dirname(os.path.realpath(os.path.join(__file__, '..'))) + '/global.toml',
|
||||||
|
'ports': None,
|
||||||
|
'max_scans': 50,
|
||||||
|
'max_port_scans': None,
|
||||||
|
'tags': 'default',
|
||||||
|
'exclude_tags': None,
|
||||||
|
'plugins_dir': os.path.dirname(os.path.abspath(os.path.join(__file__, '..'))) + '/plugins',
|
||||||
|
'add_plugins_dir': None,
|
||||||
|
'outdir': 'results',
|
||||||
|
'single_target': False,
|
||||||
|
'only_scans_dir': False,
|
||||||
|
'create_port_dirs': False,
|
||||||
|
'heartbeat': 60,
|
||||||
|
'timeout': None,
|
||||||
|
'target_timeout': None,
|
||||||
|
'nmap': '-vv --reason -Pn',
|
||||||
|
'nmap_append': '',
|
||||||
|
'disable_sanity_checks': False,
|
||||||
|
'disable_keyboard_control': False,
|
||||||
|
'force_services': None,
|
||||||
|
'accessible': False,
|
||||||
|
'verbose': 0
|
||||||
|
}
|
|
@ -0,0 +1,163 @@
|
||||||
|
import asyncio, colorama, os, re, string, sys, unidecode
|
||||||
|
from colorama import Fore, Style
|
||||||
|
from autorecon.config import config
|
||||||
|
|
||||||
|
def slugify(name):
|
||||||
|
return re.sub(r'[\W_]+', '-', unidecode.unidecode(name).lower()).strip('-')
|
||||||
|
|
||||||
|
def e(*args, frame_index=1, **kvargs):
|
||||||
|
frame = sys._getframe(frame_index)
|
||||||
|
|
||||||
|
vals = {}
|
||||||
|
|
||||||
|
vals.update(frame.f_globals)
|
||||||
|
vals.update(frame.f_locals)
|
||||||
|
vals.update(kvargs)
|
||||||
|
|
||||||
|
return string.Formatter().vformat(' '.join(args), args, vals)
|
||||||
|
|
||||||
|
def fformat(s):
|
||||||
|
return e(s, frame_index=3)
|
||||||
|
|
||||||
|
def cprint(*args, color=Fore.RESET, char='*', sep=' ', end='\n', frame_index=1, file=sys.stdout, printmsg=True, **kvargs):
|
||||||
|
frame = sys._getframe(frame_index)
|
||||||
|
|
||||||
|
vals = {
|
||||||
|
'bgreen': Fore.GREEN + Style.BRIGHT,
|
||||||
|
'bred': Fore.RED + Style.BRIGHT,
|
||||||
|
'bblue': Fore.BLUE + Style.BRIGHT,
|
||||||
|
'byellow': Fore.YELLOW + Style.BRIGHT,
|
||||||
|
'bmagenta': Fore.MAGENTA + Style.BRIGHT,
|
||||||
|
|
||||||
|
'green': Fore.GREEN,
|
||||||
|
'red': Fore.RED,
|
||||||
|
'blue': Fore.BLUE,
|
||||||
|
'yellow': Fore.YELLOW,
|
||||||
|
'magenta': Fore.MAGENTA,
|
||||||
|
|
||||||
|
'bright': Style.BRIGHT,
|
||||||
|
'srst': Style.NORMAL,
|
||||||
|
'crst': Fore.RESET,
|
||||||
|
'rst': Style.NORMAL + Fore.RESET
|
||||||
|
}
|
||||||
|
|
||||||
|
if config['accessible']:
|
||||||
|
vals = {'bgreen':'', 'bred':'', 'bblue':'', 'byellow':'', 'bmagenta':'', 'green':'', 'red':'', 'blue':'', 'yellow':'', 'magenta':'', 'bright':'', 'srst':'', 'crst':'', 'rst':''}
|
||||||
|
|
||||||
|
vals.update(frame.f_globals)
|
||||||
|
vals.update(frame.f_locals)
|
||||||
|
vals.update(kvargs)
|
||||||
|
|
||||||
|
unfmt = ''
|
||||||
|
if char is not None and not config['accessible']:
|
||||||
|
unfmt += color + '[' + Style.BRIGHT + char + Style.NORMAL + ']' + Fore.RESET + sep
|
||||||
|
unfmt += sep.join(args)
|
||||||
|
|
||||||
|
fmted = unfmt
|
||||||
|
|
||||||
|
for attempt in range(10):
|
||||||
|
try:
|
||||||
|
fmted = string.Formatter().vformat(unfmt, args, vals)
|
||||||
|
break
|
||||||
|
except KeyError as err:
|
||||||
|
key = err.args[0]
|
||||||
|
unfmt = unfmt.replace('{' + key + '}', '{{' + key + '}}')
|
||||||
|
|
||||||
|
if printmsg:
|
||||||
|
print(fmted, sep=sep, end=end, file=file)
|
||||||
|
else:
|
||||||
|
return fmted
|
||||||
|
|
||||||
|
def debug(*args, color=Fore.GREEN, sep=' ', end='\n', file=sys.stdout, **kvargs):
|
||||||
|
if verbose >= 2:
|
||||||
|
if config['accessible']:
|
||||||
|
args = ('Debug:',) + args
|
||||||
|
cprint(*args, color=color, char='-', sep=sep, end=end, file=file, frame_index=2, **kvargs)
|
||||||
|
|
||||||
|
def info(*args, sep=' ', end='\n', file=sys.stdout, **kvargs):
|
||||||
|
cprint(*args, color=Fore.BLUE, char='*', sep=sep, end=end, file=file, frame_index=2, **kvargs)
|
||||||
|
|
||||||
|
def warn(*args, sep=' ', end='\n', file=sys.stderr,**kvargs):
|
||||||
|
if config['accessible']:
|
||||||
|
args = ('Warning:',) + args
|
||||||
|
cprint(*args, color=Fore.YELLOW, char='!', sep=sep, end=end, file=file, frame_index=2, **kvargs)
|
||||||
|
|
||||||
|
def error(*args, sep=' ', end='\n', file=sys.stderr, **kvargs):
|
||||||
|
if config['accessible']:
|
||||||
|
args = ('Error:',) + args
|
||||||
|
cprint(*args, color=Fore.RED, char='!', sep=sep, end=end, file=file, frame_index=2, **kvargs)
|
||||||
|
|
||||||
|
def fail(*args, sep=' ', end='\n', file=sys.stderr, **kvargs):
|
||||||
|
if config['accessible']:
|
||||||
|
args = ('Failure:',) + args
|
||||||
|
cprint(*args, color=Fore.RED, char='!', sep=sep, end=end, file=file, frame_index=2, **kvargs)
|
||||||
|
exit(-1)
|
||||||
|
|
||||||
|
class CommandStreamReader(object):
|
||||||
|
|
||||||
|
def __init__(self, stream, target, tag, patterns=None, outfile=None):
|
||||||
|
self.stream = stream
|
||||||
|
self.target = target
|
||||||
|
self.tag = tag
|
||||||
|
self.lines = []
|
||||||
|
self.patterns = patterns or []
|
||||||
|
self.outfile = outfile
|
||||||
|
self.ended = False
|
||||||
|
|
||||||
|
# Read lines from the stream until it ends.
|
||||||
|
async def _read(self):
|
||||||
|
while True:
|
||||||
|
if self.stream.at_eof():
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
line = (await self.stream.readline()).decode('utf8').rstrip()
|
||||||
|
except ValueError:
|
||||||
|
error('{bright}[{yellow}' + self.target.address + '{crst}/{bgreen}' + self.tag + '{crst}]{rst} A line was longer than 64 KiB and cannot be processed. Ignoring.')
|
||||||
|
continue
|
||||||
|
|
||||||
|
if config['verbose'] >= 2:
|
||||||
|
if line != '':
|
||||||
|
info('{bright}[{yellow}' + self.target.address + '{crst}/{bgreen}' + self.tag + '{crst}]{rst} ' + line.replace('{', '{{').replace('}', '}}'))
|
||||||
|
|
||||||
|
# Check lines for pattern matches.
|
||||||
|
for p in self.patterns:
|
||||||
|
matches = p.pattern.findall(line)
|
||||||
|
for match in matches:
|
||||||
|
async with self.target.lock:
|
||||||
|
with open(os.path.join(self.target.scandir, '_patterns.log'), 'a') as file:
|
||||||
|
if p.description:
|
||||||
|
if config['verbose'] >= 1:
|
||||||
|
info('{bright}[{yellow}' + self.target.address + '{crst}/{bgreen}' + self.tag + '{crst}]{rst} {bmagenta}' + p.description.replace('{match}', match) + '{rst}')
|
||||||
|
file.writelines(p.description.replace('{match}', match) + '\n\n')
|
||||||
|
else:
|
||||||
|
if config['verbose'] >= 1:
|
||||||
|
info('{bright}[{yellow}' + self.target.address + '{crst}/{bgreen}' + self.tag + '{crst}]{rst} {bmagenta}Matched Pattern: ' + match + '{rst}')
|
||||||
|
file.writelines('Matched Pattern: ' + match + '\n\n')
|
||||||
|
|
||||||
|
if self.outfile is not None:
|
||||||
|
with open(self.outfile, 'a') as writer:
|
||||||
|
writer.write(line + '\n')
|
||||||
|
self.lines.append(line)
|
||||||
|
self.ended = True
|
||||||
|
|
||||||
|
# Read a line from the stream cache.
|
||||||
|
async def readline(self):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
return self.lines.pop(0)
|
||||||
|
except IndexError:
|
||||||
|
if self.ended:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
|
# Read all lines from the stream cache.
|
||||||
|
async def readlines(self):
|
||||||
|
lines = []
|
||||||
|
while True:
|
||||||
|
line = await self.readline()
|
||||||
|
if line is not None:
|
||||||
|
lines.append(line)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return lines
|
|
@ -0,0 +1,337 @@
|
||||||
|
import asyncio, inspect, os, re, sys
|
||||||
|
from typing import final
|
||||||
|
from autorecon.config import config
|
||||||
|
from autorecon.io import slugify, error, fail, CommandStreamReader
|
||||||
|
from autorecon.targets import Service
|
||||||
|
|
||||||
|
class Pattern:
|
||||||
|
|
||||||
|
def __init__(self, pattern, description=None):
|
||||||
|
self.pattern = pattern
|
||||||
|
self.description = description
|
||||||
|
|
||||||
|
class Plugin(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.name = None
|
||||||
|
self.slug = None
|
||||||
|
self.description = None
|
||||||
|
self.tags = ['default']
|
||||||
|
self.priority = 1
|
||||||
|
self.patterns = []
|
||||||
|
self.autorecon = None
|
||||||
|
self.disabled = False
|
||||||
|
|
||||||
|
@final
|
||||||
|
def add_option(self, name, default=None, help=None):
|
||||||
|
self.autorecon.add_argument(self, name, metavar='VALUE', default=default, help=help)
|
||||||
|
|
||||||
|
@final
|
||||||
|
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)
|
||||||
|
|
||||||
|
@final
|
||||||
|
def add_true_option(self, name, help=None):
|
||||||
|
self.autorecon.add_argument(self, name, action='store_true', help=help)
|
||||||
|
|
||||||
|
@final
|
||||||
|
def add_false_option(self, name, help=None):
|
||||||
|
self.autorecon.add_argument(self, name, action='store_false', help=help)
|
||||||
|
|
||||||
|
@final
|
||||||
|
def add_list_option(self, name, default=None, help=None):
|
||||||
|
self.autorecon.add_argument(self, name, nargs='+', metavar='VALUE', default=default, help=help)
|
||||||
|
|
||||||
|
@final
|
||||||
|
def add_choice_option(self, name, choices, default=None, help=None):
|
||||||
|
if not isinstance(choices, 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)
|
||||||
|
|
||||||
|
@final
|
||||||
|
def get_option(self, name):
|
||||||
|
# TODO: make sure name is simple.
|
||||||
|
name = self.slug.replace('-', '_') + '.' + slugify(name).replace('-', '_')
|
||||||
|
|
||||||
|
if name in vars(self.autorecon.args):
|
||||||
|
return vars(self.autorecon.args)[name]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@final
|
||||||
|
def get_global_option(self, name, default=None):
|
||||||
|
name = 'global.' + slugify(name).replace('-', '_')
|
||||||
|
|
||||||
|
if name in vars(self.autorecon.args):
|
||||||
|
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
|
||||||
|
return None
|
||||||
|
|
||||||
|
@final
|
||||||
|
def get_global(self, name, default=None):
|
||||||
|
return self.get_global_option(name, default)
|
||||||
|
|
||||||
|
@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):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.type = None
|
||||||
|
|
||||||
|
async def run(self, target):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
class ServiceScan(Plugin):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
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()
|
||||||
|
if protocol not in ['tcp', 'udp']:
|
||||||
|
print('Invalid protocol.')
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
if not isinstance(port, list):
|
||||||
|
port = [port]
|
||||||
|
|
||||||
|
port = list(map(int, port))
|
||||||
|
|
||||||
|
if negative_match:
|
||||||
|
self.ignore_ports[protocol] = list(set(self.ignore_ports[protocol] + port))
|
||||||
|
else:
|
||||||
|
self.ports[protocol] = list(set(self.ports[protocol] + port))
|
||||||
|
|
||||||
|
@final
|
||||||
|
def match_service_name(self, name, negative_match=False):
|
||||||
|
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 valid_regex:
|
||||||
|
if negative_match:
|
||||||
|
self.ignore_service_names = list(set(self.ignore_service_names + name))
|
||||||
|
else:
|
||||||
|
self.service_names = list(set(self.service_names + name))
|
||||||
|
else:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
@final
|
||||||
|
def require_ssl(self, boolean):
|
||||||
|
self.require_ssl_boolean = boolean
|
||||||
|
|
||||||
|
@final
|
||||||
|
def run_once(self, boolean):
|
||||||
|
self.run_once_boolean = boolean
|
||||||
|
|
||||||
|
@final
|
||||||
|
def match_all_service_names(self, boolean):
|
||||||
|
self.match_all_service_names_boolean = boolean
|
||||||
|
|
||||||
|
class AutoRecon(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.pending_targets = []
|
||||||
|
self.scanning_targets = []
|
||||||
|
self.plugins = {}
|
||||||
|
self.__slug_regex = re.compile('^[a-z0-9\-]+$')
|
||||||
|
self.plugin_types = {'port':[], 'service':[]}
|
||||||
|
self.port_scan_semaphore = None
|
||||||
|
self.service_scan_semaphore = None
|
||||||
|
self.argparse = None
|
||||||
|
self.argparse_group = None
|
||||||
|
self.args = None
|
||||||
|
self.missing_services = []
|
||||||
|
self.taglist = []
|
||||||
|
self.tags = []
|
||||||
|
self.excluded_tags = []
|
||||||
|
self.patterns = []
|
||||||
|
self.errors = False
|
||||||
|
self.lock = asyncio.Lock()
|
||||||
|
self.load_slug = None
|
||||||
|
self.load_module = None
|
||||||
|
|
||||||
|
def add_argument(self, plugin, name, **kwargs):
|
||||||
|
# TODO: make sure name is simple.
|
||||||
|
name = '--' + plugin.slug + '.' + slugify(name)
|
||||||
|
|
||||||
|
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.add_argument(name, **kwargs)
|
||||||
|
|
||||||
|
def extract_service(self, line, regex):
|
||||||
|
if regex is None:
|
||||||
|
regex = '^(?P<port>\d+)\/(?P<protocol>(tcp|udp))(.*)open(\s*)(?P<service>[\w\-\/]+)(\s*)(.*)$'
|
||||||
|
match = re.search(regex, line)
|
||||||
|
if match:
|
||||||
|
protocol = match.group('protocol').lower()
|
||||||
|
port = int(match.group('port'))
|
||||||
|
service = match.group('service')
|
||||||
|
secure = True if 'ssl' in service or 'tls' in service else False
|
||||||
|
|
||||||
|
if service.startswith('ssl/') or service.startswith('tls/'):
|
||||||
|
service = service[4:]
|
||||||
|
|
||||||
|
return Service(protocol, port, service, secure)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def extract_services(self, stream, regex):
|
||||||
|
if not isinstance(stream, CommandStreamReader):
|
||||||
|
print('Error: extract_services must be passed an instance of a CommandStreamReader.')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
services = []
|
||||||
|
while True:
|
||||||
|
line = await stream.readline()
|
||||||
|
if line is not None:
|
||||||
|
service = self.extract_service(line, regex)
|
||||||
|
if service:
|
||||||
|
services.append(service)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return services
|
||||||
|
|
||||||
|
def register(self, plugin, filename):
|
||||||
|
if plugin.disabled:
|
||||||
|
return
|
||||||
|
|
||||||
|
for _, loaded_plugin in self.plugins.items():
|
||||||
|
if plugin.name == loaded_plugin.name:
|
||||||
|
fail('Error: Duplicate plugin name "' + plugin.name + '" detected in ' + filename + '.', file=sys.stderr)
|
||||||
|
|
||||||
|
if plugin.slug is None:
|
||||||
|
plugin.slug = slugify(plugin.name)
|
||||||
|
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']:
|
||||||
|
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)
|
||||||
|
|
||||||
|
configure_function_found = False
|
||||||
|
run_coroutine_found = False
|
||||||
|
manual_function_found = False
|
||||||
|
|
||||||
|
for member_name, member_value in inspect.getmembers(plugin, predicate=inspect.ismethod):
|
||||||
|
if member_name == 'configure':
|
||||||
|
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
|
||||||
|
|
||||||
|
if not run_coroutine_found and not manual_function_found:
|
||||||
|
fail('Error: the plugin "' + plugin.name + '" in ' + filename + ' needs either a "manual" function, a "run" coroutine, or both.', file=sys.stderr)
|
||||||
|
|
||||||
|
#from autorecon import PortScan, ServiceScan
|
||||||
|
if issubclass(plugin.__class__, PortScan):
|
||||||
|
self.plugin_types["port"].append(plugin)
|
||||||
|
elif issubclass(plugin.__class__, ServiceScan):
|
||||||
|
self.plugin_types["service"].append(plugin)
|
||||||
|
else:
|
||||||
|
fail('Plugin "' + plugin.name + '" in ' + filename + ' is neither a PortScan nor a ServiceScan.', 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]
|
||||||
|
|
||||||
|
plugin.autorecon = self
|
||||||
|
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)
|
||||||
|
|
||||||
|
async def execute(self, cmd, target, tag, patterns=None, outfile=None, errfile=None):
|
||||||
|
if patterns:
|
||||||
|
combined_patterns = self.patterns + patterns
|
||||||
|
else:
|
||||||
|
combined_patterns = self.patterns
|
||||||
|
|
||||||
|
process = await asyncio.create_subprocess_shell(
|
||||||
|
cmd,
|
||||||
|
stdin=open('/dev/null'),
|
||||||
|
stdout=asyncio.subprocess.PIPE,
|
||||||
|
stderr=asyncio.subprocess.PIPE)
|
||||||
|
|
||||||
|
cout = CommandStreamReader(process.stdout, target, tag, patterns=combined_patterns, outfile=outfile)
|
||||||
|
cerr = CommandStreamReader(process.stderr, target, tag, patterns=combined_patterns, outfile=errfile)
|
||||||
|
|
||||||
|
asyncio.create_task(cout._read())
|
||||||
|
asyncio.create_task(cerr._read())
|
||||||
|
|
||||||
|
return process, cout, cerr
|
|
@ -0,0 +1,172 @@
|
||||||
|
import asyncio, inspect, os
|
||||||
|
from typing import final
|
||||||
|
from autorecon.config import config
|
||||||
|
from autorecon.io import e, info
|
||||||
|
|
||||||
|
class Target:
|
||||||
|
|
||||||
|
def __init__(self, address, ipversion, type, autorecon):
|
||||||
|
self.address = address
|
||||||
|
self.ipversion = ipversion
|
||||||
|
self.type = type
|
||||||
|
self.autorecon = autorecon
|
||||||
|
self.basedir = ''
|
||||||
|
self.reportdir = ''
|
||||||
|
self.scandir = ''
|
||||||
|
self.lock = asyncio.Lock()
|
||||||
|
self.ports = None
|
||||||
|
self.pending_services = []
|
||||||
|
self.services = []
|
||||||
|
self.scans = []
|
||||||
|
self.running_tasks = {}
|
||||||
|
|
||||||
|
async def add_service(self, service):
|
||||||
|
async with self.lock:
|
||||||
|
self.pending_services.append(service)
|
||||||
|
|
||||||
|
def extract_service(self, line, regex=None):
|
||||||
|
return self.autorecon.extract_service(line, regex)
|
||||||
|
|
||||||
|
async def extract_services(self, stream, regex=None):
|
||||||
|
return await self.autorecon.extract_services(stream, regex)
|
||||||
|
|
||||||
|
async def execute(self, cmd, blocking=True, outfile=None, errfile=None):
|
||||||
|
target = self
|
||||||
|
|
||||||
|
# Create variables for command references.
|
||||||
|
address = target.address
|
||||||
|
addressv6 = target.address
|
||||||
|
scandir = target.scandir
|
||||||
|
|
||||||
|
nmap_extra = target.autorecon.args.nmap
|
||||||
|
if target.autorecon.args.nmap_append:
|
||||||
|
nmap_extra += ' ' + target.autorecon.args.nmap_append
|
||||||
|
|
||||||
|
if target.ipversion == 'IPv6':
|
||||||
|
nmap_extra += ' -6'
|
||||||
|
addressv6 = '[' + addressv6 + ']'
|
||||||
|
|
||||||
|
plugin = inspect.currentframe().f_back.f_locals['self']
|
||||||
|
|
||||||
|
cmd = e(cmd)
|
||||||
|
|
||||||
|
tag = plugin.slug
|
||||||
|
|
||||||
|
if config['verbose'] >= 1:
|
||||||
|
info('Port scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} is running the following command against {byellow}' + address + '{rst}: ' + cmd)
|
||||||
|
|
||||||
|
if outfile is not None:
|
||||||
|
outfile = os.path.join(target.scandir, e(outfile))
|
||||||
|
|
||||||
|
if errfile is not None:
|
||||||
|
errfile = os.path.join(target.scandir, e(errfile))
|
||||||
|
|
||||||
|
async with target.lock:
|
||||||
|
with open(os.path.join(target.scandir, '_commands.log'), 'a') as file:
|
||||||
|
file.writelines(cmd + '\n\n')
|
||||||
|
|
||||||
|
process, stdout, stderr = await target.autorecon.execute(cmd, target, tag, patterns=plugin.patterns, outfile=outfile, errfile=errfile)
|
||||||
|
|
||||||
|
target.running_tasks[tag]['processes'].append({'process': process, 'stderr': stderr, 'cmd': cmd})
|
||||||
|
|
||||||
|
# If process should block, sleep until stdout and stderr have finished.
|
||||||
|
if blocking:
|
||||||
|
while (not (stdout.ended and stderr.ended)):
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
await process.wait()
|
||||||
|
|
||||||
|
return process, stdout, stderr
|
||||||
|
|
||||||
|
class Service:
|
||||||
|
|
||||||
|
def __init__(self, protocol, port, name, secure=False):
|
||||||
|
self.target = None
|
||||||
|
self.protocol = protocol.lower()
|
||||||
|
self.port = int(port)
|
||||||
|
self.name = name
|
||||||
|
self.secure = secure
|
||||||
|
self.manual_commands = {}
|
||||||
|
|
||||||
|
@final
|
||||||
|
def tag(self):
|
||||||
|
return self.protocol + '/' + str(self.port) + '/' + self.name
|
||||||
|
|
||||||
|
@final
|
||||||
|
def full_tag(self):
|
||||||
|
return self.protocol + '/' + str(self.port) + '/' + self.name + '/' + ('secure' if self.secure else 'insecure')
|
||||||
|
|
||||||
|
@final
|
||||||
|
def add_manual_commands(self, description, commands):
|
||||||
|
if not isinstance(commands, list):
|
||||||
|
commands = [commands]
|
||||||
|
if description not in self.manual_commands:
|
||||||
|
self.manual_commands[description] = []
|
||||||
|
|
||||||
|
# Merge in new unique commands, while preserving order.
|
||||||
|
[self.manual_commands[description].append(m) for m in commands if m not in self.manual_commands[description]]
|
||||||
|
|
||||||
|
@final
|
||||||
|
def add_manual_command(self, description, command):
|
||||||
|
self.add_manual_commands(description, command)
|
||||||
|
|
||||||
|
@final
|
||||||
|
async def execute(self, cmd, blocking=True, outfile=None, errfile=None):
|
||||||
|
target = self.target
|
||||||
|
|
||||||
|
# Create variables for command references.
|
||||||
|
address = target.address
|
||||||
|
addressv6 = target.address
|
||||||
|
scandir = target.scandir
|
||||||
|
protocol = self.protocol
|
||||||
|
port = self.port
|
||||||
|
name = self.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 self.name or self.secure is True else 'http'
|
||||||
|
|
||||||
|
nmap_extra = target.autorecon.args.nmap
|
||||||
|
if target.autorecon.args.nmap_append:
|
||||||
|
nmap_extra += ' ' + target.autorecon.args.nmap_append
|
||||||
|
|
||||||
|
if protocol == 'udp':
|
||||||
|
nmap_extra += ' -sU'
|
||||||
|
|
||||||
|
if target.ipversion == 'IPv6':
|
||||||
|
nmap_extra += ' -6'
|
||||||
|
addressv6 = '[' + addressv6 + ']'
|
||||||
|
|
||||||
|
plugin = inspect.currentframe().f_back.f_locals['self']
|
||||||
|
|
||||||
|
cmd = e(cmd)
|
||||||
|
|
||||||
|
tag = self.tag() + '/' + plugin.slug
|
||||||
|
|
||||||
|
if config['verbose'] >= 1:
|
||||||
|
info('Service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} is running the following command against {byellow}' + address + '{rst}: ' + cmd)
|
||||||
|
|
||||||
|
if outfile is not None:
|
||||||
|
outfile = os.path.join(scandir, e(outfile))
|
||||||
|
|
||||||
|
if errfile is not None:
|
||||||
|
errfile = os.path.join(scandir, e(errfile))
|
||||||
|
|
||||||
|
async with target.lock:
|
||||||
|
with open(os.path.join(target.scandir, '_commands.log'), 'a') as file:
|
||||||
|
file.writelines(cmd + '\n\n')
|
||||||
|
|
||||||
|
process, stdout, stderr = await target.autorecon.execute(cmd, target, tag, patterns=plugin.patterns, outfile=outfile, errfile=errfile)
|
||||||
|
|
||||||
|
target.running_tasks[tag]['processes'].append({'process': process, 'stderr': stderr, 'cmd': cmd})
|
||||||
|
|
||||||
|
# If process should block, sleep until stdout and stderr have finished.
|
||||||
|
if blocking:
|
||||||
|
while (not (stdout.ended and stderr.ended)):
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
await process.wait()
|
||||||
|
|
||||||
|
return process, stdout, stderr
|
|
@ -1,4 +1,4 @@
|
||||||
from autorecon import ServiceScan
|
from autorecon.plugins import ServiceScan
|
||||||
|
|
||||||
class NmapMongoDB(ServiceScan):
|
class NmapMongoDB(ServiceScan):
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from autorecon import PortScan, error
|
from autorecon.plugins import PortScan
|
||||||
|
from autorecon.io import error
|
||||||
import os
|
import os
|
||||||
|
|
||||||
class QuickTCPPortScan(PortScan):
|
class QuickTCPPortScan(PortScan):
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from autorecon import ServiceScan
|
from autorecon.plugins import ServiceScan
|
||||||
|
|
||||||
class NmapDNS(ServiceScan):
|
class NmapDNS(ServiceScan):
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from autorecon import ServiceScan
|
from autorecon.plugins import ServiceScan
|
||||||
|
|
||||||
class NmapFTP(ServiceScan):
|
class NmapFTP(ServiceScan):
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from autorecon import PortScan, Service
|
from autorecon.plugins import PortScan
|
||||||
|
from autorecon.targets import Service
|
||||||
import re
|
import re
|
||||||
|
|
||||||
class GuesPortScan(PortScan):
|
class GuesPortScan(PortScan):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from autorecon import ServiceScan, error, info, fformat
|
from autorecon.plugins import ServiceScan
|
||||||
|
from autorecon.io import error, info, fformat
|
||||||
from shutil import which
|
from shutil import which
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from autorecon import ServiceScan
|
from autorecon.plugins import ServiceScan
|
||||||
|
|
||||||
class NmapKerberos(ServiceScan):
|
class NmapKerberos(ServiceScan):
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from autorecon import ServiceScan
|
from autorecon.plugins import ServiceScan
|
||||||
|
|
||||||
class NmapLDAP(ServiceScan):
|
class NmapLDAP(ServiceScan):
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from autorecon import ServiceScan, fformat
|
from autorecon.plugins import ServiceScan
|
||||||
|
from autorecon.io import fformat
|
||||||
|
|
||||||
class NmapCassandra(ServiceScan):
|
class NmapCassandra(ServiceScan):
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from autorecon import ServiceScan
|
from autorecon.plugins import ServiceScan
|
||||||
|
|
||||||
class NmapNFS(ServiceScan):
|
class NmapNFS(ServiceScan):
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from autorecon import ServiceScan
|
from autorecon.plugins import ServiceScan
|
||||||
|
|
||||||
class NmapRDP(ServiceScan):
|
class NmapRDP(ServiceScan):
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from autorecon import ServiceScan, error
|
from autorecon.plugins import ServiceScan
|
||||||
|
from autorecon.io import error
|
||||||
from shutil import which
|
from shutil import which
|
||||||
|
|
||||||
class NmapRedis(ServiceScan):
|
class NmapRedis(ServiceScan):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from autorecon import ServiceScan, error, warn
|
from autorecon.plugins import ServiceScan
|
||||||
|
from autorecon.io import error, warn
|
||||||
from shutil import which
|
from shutil import which
|
||||||
|
|
||||||
class NmapRPC(ServiceScan):
|
class NmapRPC(ServiceScan):
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from autorecon import ServiceScan
|
from autorecon.plugins import ServiceScan
|
||||||
|
|
||||||
class NmapRsync(ServiceScan):
|
class NmapRsync(ServiceScan):
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from autorecon import ServiceScan
|
from autorecon.plugins import ServiceScan
|
||||||
|
|
||||||
class NmapSIP(ServiceScan):
|
class NmapSIP(ServiceScan):
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from autorecon import ServiceScan
|
from autorecon.plugins import ServiceScan
|
||||||
|
|
||||||
class NmapSMB(ServiceScan):
|
class NmapSMB(ServiceScan):
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from autorecon import ServiceScan
|
from autorecon.plugins import ServiceScan
|
||||||
|
|
||||||
class NmapSMTP(ServiceScan):
|
class NmapSMTP(ServiceScan):
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from autorecon import ServiceScan
|
from autorecon.plugins import ServiceScan
|
||||||
|
|
||||||
class NmapSNMP(ServiceScan):
|
class NmapSNMP(ServiceScan):
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from autorecon import ServiceScan
|
from autorecon.plugins import ServiceScan
|
||||||
|
|
||||||
class NmapSSH(ServiceScan):
|
class NmapSSH(ServiceScan):
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from autorecon import ServiceScan
|
from autorecon.plugins import ServiceScan
|
||||||
|
|
||||||
class SSLScan(ServiceScan):
|
class SSLScan(ServiceScan):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue