diff --git a/autorecon/io.py b/autorecon/io.py index 760a659..10cb5ab 100644 --- a/autorecon/io.py +++ b/autorecon/io.py @@ -2,182 +2,191 @@ 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('-') + return re.sub(r'[\W_]+', '-', unidecode.unidecode(name).lower()).strip('-') + def e(*args, frame_index=1, **kvargs): - frame = sys._getframe(frame_index) + frame = sys._getframe(frame_index) - vals = {} + vals = {} - vals.update(frame.f_globals) - vals.update(frame.f_locals) - vals.update(kvargs) + vals.update(frame.f_globals) + vals.update(frame.f_locals) + vals.update(kvargs) + + return string.Formatter().vformat(' '.join(args), args, vals) - return string.Formatter().vformat(' '.join(args), args, vals) def fformat(s): - return e(s, frame_index=3) + return e(s, frame_index=3) -def cprint(*args, color=Fore.RESET, char='*', sep=' ', end='\n', frame_index=1, file=sys.stdout, printmsg=True, verbosity=0, **kvargs): - if printmsg and verbosity > config['verbose']: - return '' - 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, +def cprint(*args, color=Fore.RESET, char='*', sep=' ', end='\n', frame_index=1, file=sys.stdout, printmsg=True, + verbosity=0, **kvargs): + if printmsg and verbosity > config['verbose']: + return '' + frame = sys._getframe(frame_index) - 'green': Fore.GREEN, - 'red': Fore.RED, - 'blue': Fore.BLUE, - 'yellow': Fore.YELLOW, - 'magenta': Fore.MAGENTA, + 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, - 'bright': Style.BRIGHT, - 'srst': Style.NORMAL, - 'crst': Fore.RESET, - 'rst': Style.NORMAL + Fore.RESET - } + 'green': Fore.GREEN, + 'red': Fore.RED, + 'blue': Fore.BLUE, + 'yellow': Fore.YELLOW, + 'magenta': Fore.MAGENTA, - if config['accessible']: - vals = {'bgreen':'', 'bred':'', 'bblue':'', 'byellow':'', 'bmagenta':'', 'green':'', 'red':'', 'blue':'', 'yellow':'', 'magenta':'', 'bright':'', 'srst':'', 'crst':'', 'rst':''} + 'bright': Style.BRIGHT, + 'srst': Style.NORMAL, + 'crst': Fore.RESET, + 'rst': Style.NORMAL + Fore.RESET + } - vals.update(frame.f_globals) - vals.update(frame.f_locals) - vals.update(kvargs) + if config['accessible']: + vals = {'bgreen': '', 'bred': '', 'bblue': '', 'byellow': '', 'bmagenta': '', 'green': '', 'red': '', + 'blue': '', 'yellow': '', 'magenta': '', 'bright': '', 'srst': '', 'crst': '', 'rst': ''} - unfmt = '' - if char is not None and not config['accessible']: - unfmt += color + '[' + Style.BRIGHT + char + Style.NORMAL + ']' + Fore.RESET + sep - unfmt += sep.join(args) + vals.update(frame.f_globals) + vals.update(frame.f_locals) + vals.update(kvargs) - fmted = unfmt + unfmt = '' + if char is not None and not config['accessible']: + unfmt += color + '[' + Style.BRIGHT + char + Style.NORMAL + ']' + Fore.RESET + sep + unfmt += sep.join(args) - 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 + '}}') + 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 - 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 config['verbose'] >= 2: - if config['accessible']: - args = ('Debug:',) + args - cprint(*args, color=color, char='-', sep=sep, end=end, file=file, frame_index=2, **kvargs) + if config['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) + 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 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) + 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) + 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, plugin=None): - self.stream = stream - self.target = target - self.tag = tag - self.lines = [] - self.plugin = plugin - self.patterns = patterns or [] - self.outfile = outfile - self.ended = False + def __init__(self, stream, target, tag, patterns=None, outfile=None, plugin=None): + self.stream = stream + self.target = target + self.tag = tag + self.lines = [] + self.plugin = plugin + self.patterns = patterns or [] + self.outfile = outfile + self.ended = False - # Empty files that already exist. - if self.outfile != None: - with open(self.outfile, 'w'): pass + # Empty files that already exist. + if self.outfile != None: + with open(self.outfile, 'w'): pass - # 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 + # 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 line != '': - info('{bright}[{yellow}' + self.target.address + '{crst}/{bgreen}' + self.tag + '{crst}]{rst} ' + line.replace('{', '{{').replace('}', '}}'), verbosity=3) + if line != '': + info( + '{bright}[{yellow}' + self.target.address + '{crst}/{bgreen}' + self.tag + '{crst}]{rst} ' + line.replace( + '{', '{{').replace('}', '}}'), verbosity=3) - # 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: - info('{bright}[{yellow}' + self.target.address + '{crst}/{bgreen}' + self.tag + '{crst}]{rst} {bmagenta}' + p.description.replace('{match}', match) + '{rst}', verbosity=2) - file.writelines(p.description.replace('{match}', match) + '\n\n') - else: - 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') - 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) + # 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: + info( + '{bright}[{yellow}' + self.target.address + '{crst}/{bgreen}' + self.tag + '{crst}]{rst} {bmagenta}' + p.description.replace( + '{match}', match) + '{rst}', verbosity=2) + file.writelines(p.description.replace('{match}', match) + '\n\n') + else: + 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') + next_plugins_to_run = p.get_next_service_scan_plugins(self.target.autorecon) + for next_plugin in next_plugins_to_run: + # ugly way to get the service details somehow + for service, details in self.target.scans.get('services', {}).items(): + for key, value in details.items(): + if value.get('plugin') == self.plugin: + self.target.autorecon.queue_new_service_scan(next_plugin, service) + if self.outfile is not None: + with open(self.outfile, 'a') as writer: + writer.write(line + '\n') + self.lines.append(line) + self.ended = True - 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 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 + # 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 diff --git a/autorecon/plugins.py b/autorecon/plugins.py index e422567..1b4efbb 100644 --- a/autorecon/plugins.py +++ b/autorecon/plugins.py @@ -53,7 +53,7 @@ async def get_semaphore(autorecon): 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: + if not plugin.run_standalone and not run_from_service_scan: return semaphore = service.target.autorecon.service_scan_semaphore @@ -159,13 +159,23 @@ async def service_scan(plugin, service, run_from_service_scan=False): class Pattern: - def __init__(self, pattern, description=None, plugins=None): + def __init__(self, pattern, description=None, plugin_names=None): self.pattern = pattern self.description = description - if not plugins: - self.plugins = [] + if not plugin_names: + self.plugin_names = [] else: - self.plugins = plugins + self.plugin_names = plugin_names + + def get_next_service_scan_plugins(self, autorecon): + next_plugins = [] + if not self.plugin_names: + return next_plugins + for service_plugin in autorecon.plugin_types['service']: + for next_plugin in self.plugin_names: + if next_plugin == service_plugin.name: + next_plugins.append(service_plugin) + return next_plugins class Plugin(object): @@ -238,13 +248,13 @@ class Plugin(object): return self.get_global_option(name, default) @final - def add_pattern(self, pattern, description=None, plugins=None): + def add_pattern(self, pattern, description=None, plugin_names=None): try: compiled = re.compile(pattern) if description: - self.patterns.append(Pattern(compiled, description=description, plugins=plugins)) + self.patterns.append(Pattern(compiled, description=description, plugin_names=plugin_names)) else: - self.patterns.append(Pattern(compiled, plugins=plugins)) + self.patterns.append(Pattern(compiled, plugin_names=plugin_names)) except re.error: fail('Error: The pattern "' + pattern + '" in the plugin "' + self.name + '" is invalid regex.') @@ -265,6 +275,7 @@ class ServiceScan(Plugin): def __init__(self): super().__init__() self.ports = {'tcp': [], 'udp': []} + self.run_standalone = True self.ignore_ports = {'tcp': [], 'udp': []} self.services = [] self.service_names = [] @@ -543,21 +554,6 @@ class AutoRecon(object): return process, cout, cerr - def get_plugin_by_name(self, name): - for key, value in self.plugins.items(): - if value.name == name: - return self.plugins[key] - - 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))) diff --git a/autorecon/test-plugins/http_server2.py b/autorecon/test-plugins/http_server2.py index 7b7b3a8..0913306 100644 --- a/autorecon/test-plugins/http_server2.py +++ b/autorecon/test-plugins/http_server2.py @@ -3,36 +3,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 __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('

Directory listing for', description='Directory Listing enabled', - plugin_names=["Directory Listing Verify"]) + def configure(self): + self.match_service_name('^http') + self.match_service_name('^nacn_http$', negative_match=True) + self.add_pattern('

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}') + 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'] + """ + this is a useless plugin that is only run, if directory listing was found. + """ - def configure(self): - self.match_service_name('^http') - self.match_service_name('^nacn_http$', negative_match=True) + def __init__(self): + super().__init__() + self.run_standalone = False + self.name = "Directory Listing Verify" + self.tags = ['default', 'safe', 'http', 'test'] - async def run(self, service): - await service.execute('curl {http_scheme}://{addressv6}:{port}/?id=1') + def configure(self): + self.match_service_name('^http') + self.match_service_name('^nacn_http$', negative_match=True) - def get_previous_plugin_names(self): - return ["Directory Listing"] + async def run(self, service): + await service.execute('curl {http_scheme}://{addressv6}:{port}/?id=1') + + def get_previous_plugin_names(self): + return ["Directory Listing"]