initial version of dependent service scans.

This commit is contained in:
blockomat2100 2021-11-08 07:31:14 +01:00
parent 52ba61e6eb
commit 42e2620f76
3 changed files with 198 additions and 191 deletions

View File

@ -2,182 +2,191 @@ import asyncio, colorama, os, re, string, sys, unidecode
from colorama import Fore, Style from colorama import Fore, Style
from autorecon.config import config from autorecon.config import config
def slugify(name): 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): 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_globals)
vals.update(frame.f_locals) vals.update(frame.f_locals)
vals.update(kvargs) vals.update(kvargs)
return string.Formatter().vformat(' '.join(args), args, vals)
return string.Formatter().vformat(' '.join(args), args, vals)
def fformat(s): 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 = { def cprint(*args, color=Fore.RESET, char='*', sep=' ', end='\n', frame_index=1, file=sys.stdout, printmsg=True,
'bgreen': Fore.GREEN + Style.BRIGHT, verbosity=0, **kvargs):
'bred': Fore.RED + Style.BRIGHT, if printmsg and verbosity > config['verbose']:
'bblue': Fore.BLUE + Style.BRIGHT, return ''
'byellow': Fore.YELLOW + Style.BRIGHT, frame = sys._getframe(frame_index)
'bmagenta': Fore.MAGENTA + Style.BRIGHT,
'green': Fore.GREEN, vals = {
'red': Fore.RED, 'bgreen': Fore.GREEN + Style.BRIGHT,
'blue': Fore.BLUE, 'bred': Fore.RED + Style.BRIGHT,
'yellow': Fore.YELLOW, 'bblue': Fore.BLUE + Style.BRIGHT,
'magenta': Fore.MAGENTA, 'byellow': Fore.YELLOW + Style.BRIGHT,
'bmagenta': Fore.MAGENTA + Style.BRIGHT,
'bright': Style.BRIGHT, 'green': Fore.GREEN,
'srst': Style.NORMAL, 'red': Fore.RED,
'crst': Fore.RESET, 'blue': Fore.BLUE,
'rst': Style.NORMAL + Fore.RESET 'yellow': Fore.YELLOW,
} 'magenta': Fore.MAGENTA,
if config['accessible']: 'bright': Style.BRIGHT,
vals = {'bgreen':'', 'bred':'', 'bblue':'', 'byellow':'', 'bmagenta':'', 'green':'', 'red':'', 'blue':'', 'yellow':'', 'magenta':'', 'bright':'', 'srst':'', 'crst':'', 'rst':''} 'srst': Style.NORMAL,
'crst': Fore.RESET,
'rst': Style.NORMAL + Fore.RESET
}
vals.update(frame.f_globals) if config['accessible']:
vals.update(frame.f_locals) vals = {'bgreen': '', 'bred': '', 'bblue': '', 'byellow': '', 'bmagenta': '', 'green': '', 'red': '',
vals.update(kvargs) 'blue': '', 'yellow': '', 'magenta': '', 'bright': '', 'srst': '', 'crst': '', 'rst': ''}
unfmt = '' vals.update(frame.f_globals)
if char is not None and not config['accessible']: vals.update(frame.f_locals)
unfmt += color + '[' + Style.BRIGHT + char + Style.NORMAL + ']' + Fore.RESET + sep vals.update(kvargs)
unfmt += sep.join(args)
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): fmted = unfmt
try:
fmted = string.Formatter().vformat(unfmt, args, vals) for attempt in range(10):
break try:
except KeyError as err: fmted = string.Formatter().vformat(unfmt, args, vals)
key = err.args[0] break
unfmt = unfmt.replace('{' + key + '}', '{{' + key + '}}') 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): def debug(*args, color=Fore.GREEN, sep=' ', end='\n', file=sys.stdout, **kvargs):
if config['verbose'] >= 2: if config['verbose'] >= 2:
if config['accessible']: if config['accessible']:
args = ('Debug:',) + args args = ('Debug:',) + args
cprint(*args, color=color, char='-', sep=sep, end=end, file=file, frame_index=2, **kvargs) 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): 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): def error(*args, sep=' ', end='\n', file=sys.stderr, **kvargs):
if config['accessible']: if config['accessible']:
args = ('Error:',) + args args = ('Error:',) + args
cprint(*args, color=Fore.RED, char='!', sep=sep, end=end, file=file, frame_index=2, **kvargs) 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): def fail(*args, sep=' ', end='\n', file=sys.stderr, **kvargs):
if config['accessible']: if config['accessible']:
args = ('Failure:',) + args args = ('Failure:',) + args
cprint(*args, color=Fore.RED, char='!', sep=sep, end=end, file=file, frame_index=2, **kvargs) cprint(*args, color=Fore.RED, char='!', sep=sep, end=end, file=file, frame_index=2, **kvargs)
exit(-1) exit(-1)
class CommandStreamReader(object): class CommandStreamReader(object):
def __init__(self, stream, target, tag, patterns=None, outfile=None, plugin=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.plugin = plugin
self.patterns = patterns or [] self.patterns = patterns or []
self.outfile = outfile self.outfile = outfile
self.ended = False self.ended = False
# Empty files that already exist. # Empty files that already exist.
if self.outfile != None: if self.outfile != None:
with open(self.outfile, 'w'): pass with open(self.outfile, 'w'): pass
# Read lines from the stream until it ends. # Read lines from the stream until it ends.
async def _read(self): async def _read(self):
while True: while True:
if self.stream.at_eof(): if self.stream.at_eof():
break break
try: try:
line = (await self.stream.readline()).decode('utf8').rstrip() line = (await self.stream.readline()).decode('utf8').rstrip()
except ValueError: 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.') error(
continue '{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 != '': if line != '':
info('{bright}[{yellow}' + self.target.address + '{crst}/{bgreen}' + self.tag + '{crst}]{rst} ' + line.replace('{', '{{').replace('}', '}}'), verbosity=3) info(
'{bright}[{yellow}' + self.target.address + '{crst}/{bgreen}' + self.tag + '{crst}]{rst} ' + line.replace(
'{', '{{').replace('}', '}}'), verbosity=3)
# Check lines for pattern matches. # Check lines for pattern matches.
for p in self.patterns: for p in self.patterns:
matches = p.pattern.findall(line) matches = p.pattern.findall(line)
for match in matches: for match in matches:
async with self.target.lock: async with self.target.lock:
with open(os.path.join(self.target.scandir, '_patterns.log'), 'a') as file: with open(os.path.join(self.target.scandir, '_patterns.log'), 'a') as file:
if p.description: if p.description:
info('{bright}[{yellow}' + self.target.address + '{crst}/{bgreen}' + self.tag + '{crst}]{rst} {bmagenta}' + p.description.replace('{match}', match) + '{rst}', verbosity=2) info(
file.writelines(p.description.replace('{match}', match) + '\n\n') '{bright}[{yellow}' + self.target.address + '{crst}/{bgreen}' + self.tag + '{crst}]{rst} {bmagenta}' + p.description.replace(
else: '{match}', 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(p.description.replace('{match}', match) + '\n\n')
file.writelines('Matched Pattern: ' + match + '\n\n') else:
debug(str(self.plugin.__dict__)) info(
next_plugins = self.target.autorecon.get_next_service_scan_plugins(self.plugin) '{bright}[{yellow}' + self.target.address + '{crst}/{bgreen}' + self.tag + '{crst}]{rst} {bmagenta}Matched Pattern: ' + match + '{rst}',
info(str(next_plugins)) verbosity=2)
for next_plugin in next_plugins: file.writelines('Matched Pattern: ' + match + '\n\n')
info("Dict: %s" % str(self.target.__dict__)) next_plugins_to_run = p.get_next_service_scan_plugins(self.target.autorecon)
for service, details in self.target.scans.get('services', {}).items(): for next_plugin in next_plugins_to_run:
for key, value in details.items(): # ugly way to get the service details somehow
if value.get('plugin') == self.plugin: for service, details in self.target.scans.get('services', {}).items():
info("Value: %s" % str(value)) for key, value in details.items():
# info("Value: %s" % str(value)) if value.get('plugin') == self.plugin:
#info("Service Details: %s" % str(details)) self.target.autorecon.queue_new_service_scan(next_plugin, service)
#new_service = Service() if self.outfile is not None:
self.target.autorecon.queue_new_service_scan(next_plugin, service) with open(self.outfile, 'a') as writer:
#for next_plugin in next_plugins: writer.write(line + '\n')
# async def service_scan(plugin, service, run_from_service_scan=False): self.lines.append(line)
#autorecon_queue_service_scan(next_plugin, run_fr) self.ended = True
if self.outfile is not None: # Read a line from the stream cache.
with open(self.outfile, 'a') as writer: async def readline(self):
writer.write(line + '\n') while True:
self.lines.append(line) try:
self.ended = True 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. # Read all lines from the stream cache.
async def readline(self): async def readlines(self):
while True: lines = []
try: while True:
return self.lines.pop(0) line = await self.readline()
except IndexError: if line is not None:
if self.ended: lines.append(line)
return None else:
else: break
await asyncio.sleep(0.1) 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

View File

@ -53,7 +53,7 @@ async def get_semaphore(autorecon):
async def service_scan(plugin, service, run_from_service_scan=False): 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 # 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 return
semaphore = service.target.autorecon.service_scan_semaphore semaphore = service.target.autorecon.service_scan_semaphore
@ -159,13 +159,23 @@ async def service_scan(plugin, service, run_from_service_scan=False):
class Pattern: class Pattern:
def __init__(self, pattern, description=None, plugins=None): def __init__(self, pattern, description=None, plugin_names=None):
self.pattern = pattern self.pattern = pattern
self.description = description self.description = description
if not plugins: if not plugin_names:
self.plugins = [] self.plugin_names = []
else: 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): class Plugin(object):
@ -238,13 +248,13 @@ class Plugin(object):
return self.get_global_option(name, default) return self.get_global_option(name, default)
@final @final
def add_pattern(self, pattern, description=None, plugins=None): def add_pattern(self, pattern, description=None, plugin_names=None):
try: try:
compiled = re.compile(pattern) compiled = re.compile(pattern)
if description: if description:
self.patterns.append(Pattern(compiled, description=description, plugins=plugins)) self.patterns.append(Pattern(compiled, description=description, plugin_names=plugin_names))
else: else:
self.patterns.append(Pattern(compiled, plugins=plugins)) self.patterns.append(Pattern(compiled, plugin_names=plugin_names))
except re.error: except re.error:
fail('Error: The pattern "' + pattern + '" in the plugin "' + self.name + '" is invalid regex.') fail('Error: The pattern "' + pattern + '" in the plugin "' + self.name + '" is invalid regex.')
@ -265,6 +275,7 @@ class ServiceScan(Plugin):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.ports = {'tcp': [], 'udp': []} self.ports = {'tcp': [], 'udp': []}
self.run_standalone = True
self.ignore_ports = {'tcp': [], 'udp': []} self.ignore_ports = {'tcp': [], 'udp': []}
self.services = [] self.services = []
self.service_names = [] self.service_names = []
@ -543,21 +554,6 @@ class AutoRecon(object):
return process, cout, cerr 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): def queue_new_service_scan(self, plugin, service):
# try using append. in the main method "pending" is sometimes set() and sometimes list() # 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))) self.pending.append(asyncio.create_task(service_scan(plugin, service, run_from_service_scan=True)))

View File

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