Added Report Plugin functionality.

Moved http.py to http_server.py to avoid import clashes with Python's http library.
Report plugins can take a list of targets and create reports based on the scans and files.
This commit is contained in:
Tib3rius 2021-09-08 00:27:16 -04:00
parent 149372c9d4
commit e22bc55dd6
6 changed files with 275 additions and 70 deletions

View File

@ -12,7 +12,7 @@ colorama.init()
from autorecon.config import config, configurable_keys, configurable_boolean_keys from autorecon.config import config, configurable_keys, configurable_boolean_keys
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, AutoRecon from autorecon.plugins import Pattern, PortScan, ServiceScan, Report, AutoRecon
from autorecon.targets import Target, Service from autorecon.targets import Target, Service
# Save current terminal settings so we can restore them. # Save current terminal settings so we can restore them.
@ -141,6 +141,40 @@ 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']:
@ -207,40 +241,10 @@ async def port_scan(plugin, target):
return {'type':'port', 'plugin':plugin, 'result':result} return {'type':'port', 'plugin':plugin, 'result':result}
async def service_scan(plugin, service): async def service_scan(plugin, service):
#from autorecon import PortScan
semaphore = service.target.autorecon.service_scan_semaphore semaphore = service.target.autorecon.service_scan_semaphore
if not config['force_services']: if not config['force_services']:
# If service scan semaphore is locked, see if we can use port scan semaphore. semaphore = await get_semaphore(service.target.autorecon)
while True:
if semaphore.locked():
if semaphore != service.target.autorecon.port_scan_semaphore: # This will be true unless user sets max_scans == max_port_scans
port_scan_task_count = 0
for targ in service.target.autorecon.scanning_targets:
for process_list in targ.running_tasks.values():
if issubclass(process_list['plugin'].__class__, PortScan):
port_scan_task_count += 1
if not service.target.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 service.target.autorecon.port_scan_semaphore.locked():
await asyncio.sleep(1)
continue
semaphore = service.target.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(service.target.autorecon.pending_targets) * config['port_scan_plugin_count']))) >= 1:
if service.target.autorecon.port_scan_semaphore.locked():
await asyncio.sleep(1)
continue
semaphore = service.target.autorecon.port_scan_semaphore
break
else:
await asyncio.sleep(1)
else:
break
else:
break
async with semaphore: async with semaphore:
# Create variables for fformat references. # Create variables for fformat references.
@ -253,6 +257,11 @@ async def service_scan(plugin, service):
port = service.port port = service.port
name = service.name 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. # Special cases for HTTP.
http_scheme = 'https' if 'https' in service.name or service.secure is True else 'http' http_scheme = 'https' if 'https' in service.name or service.secure is True else 'http'
@ -319,6 +328,20 @@ async def service_scan(plugin, service):
info('Service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} against {byellow}' + service.target.address + '{rst} finished in ' + elapsed_time) info('Service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} against {byellow}' + service.target.address + '{rst} finished in ' + elapsed_time)
return {'type':'service', 'plugin':plugin, 'result':result} return {'type':'service', 'plugin':plugin, 'result':result}
async def generate_report(plugin, targets):
semaphore = autorecon.service_scan_semaphore
if not config['force_services']:
semaphore = await get_semaphore(autorecon)
async with semaphore:
try:
result = await plugin.run(targets)
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: Report plugin {bblue}' + plugin.name + ' {green}(' + plugin.slug + '){rst} produced an exception:\n\n' + error_text, color=Fore.RED, char='!', printmsg=False))
async def scan_target(target): async def scan_target(target):
os.makedirs(os.path.abspath(config['outdir']), exist_ok=True) os.makedirs(os.path.abspath(config['outdir']), exist_ok=True)
@ -330,6 +353,12 @@ async def scan_target(target):
target.basedir = basedir target.basedir = basedir
scandir = os.path.join(basedir, 'scans')
target.scandir = scandir
os.makedirs(scandir, exist_ok=True)
os.makedirs(os.path.join(scandir, 'xml'), exist_ok=True)
if not config['only_scans_dir']: if not config['only_scans_dir']:
exploitdir = os.path.join(basedir, 'exploit') exploitdir = os.path.join(basedir, 'exploit')
os.makedirs(exploitdir, exist_ok=True) os.makedirs(exploitdir, exist_ok=True)
@ -338,7 +367,6 @@ async def scan_target(target):
os.makedirs(lootdir, exist_ok=True) os.makedirs(lootdir, exist_ok=True)
reportdir = os.path.join(basedir, 'report') reportdir = os.path.join(basedir, 'report')
target.reportdir = reportdir
os.makedirs(reportdir, exist_ok=True) os.makedirs(reportdir, exist_ok=True)
open(os.path.join(reportdir, 'local.txt'), 'a').close() open(os.path.join(reportdir, 'local.txt'), 'a').close()
@ -346,12 +374,10 @@ async def scan_target(target):
screenshotdir = os.path.join(reportdir, 'screenshots') screenshotdir = os.path.join(reportdir, 'screenshots')
os.makedirs(screenshotdir, exist_ok=True) os.makedirs(screenshotdir, exist_ok=True)
else:
reportdir = scandir
scandir = os.path.join(basedir, 'scans') target.reportdir = reportdir
target.scandir = scandir
os.makedirs(scandir, exist_ok=True)
os.makedirs(os.path.join(scandir, 'xml'), exist_ok=True)
pending = [] pending = []
@ -397,6 +423,7 @@ async def scan_target(target):
break break
if matching_tags and not excluded_tags: if matching_tags and not excluded_tags:
target.scans['ports'][plugin.slug] = {'plugin':plugin, 'commands':[]}
pending.append(asyncio.create_task(port_scan(plugin, target))) pending.append(asyncio.create_task(port_scan(plugin, target)))
async with autorecon.lock: async with autorecon.lock:
@ -528,9 +555,15 @@ async def scan_target(target):
if plugin_is_runnable and matching_tags and not excluded_tags: if plugin_is_runnable and matching_tags and not excluded_tags:
# Skip plugin if run_once_boolean and plugin already in target scans # Skip plugin if run_once_boolean and plugin already in target scans
if plugin.run_once_boolean and (plugin.slug,) in target.scans: if plugin.run_once_boolean:
warn('{byellow}[' + plugin_tag + ' against ' + target.address + ']{srst} Plugin should only be run once and it appears to have already been queued. Skipping.{rst}') plugin_queued = False
break for s in target.scans['services']:
if plugin.slug in target.scans['services'][s]:
plugin_queued = True
warn('{byellow}[' + plugin_tag + ' against ' + target.address + ']{srst} Plugin should only be run once and it appears to have already been queued. Skipping.{rst}')
break
if plugin_queued:
break
# Skip plugin if require_ssl_boolean and port is not secure # Skip plugin if require_ssl_boolean and port is not secure
if plugin.require_ssl_boolean and not service.secure: if plugin.require_ssl_boolean and not service.secure:
@ -562,16 +595,22 @@ async def scan_target(target):
if member_name == 'manual': if member_name == 'manual':
plugin.manual(service, plugin_was_run) plugin.manual(service, plugin_was_run)
if service.manual_commands and (not plugin.run_once_boolean or (plugin.run_once_boolean and (plugin.slug,) not in target.scans)): if service.manual_commands:
with open(os.path.join(scandir, '_manual_commands.txt'), 'a') as file: plugin_run = False
if not heading: for s in target.scans['services']:
file.write(e('[*] {service.name} on {service.protocol}/{service.port}\n\n')) if plugin.slug in target.scans['services'][s]:
heading = True plugin_run = True
for description, commands in service.manual_commands.items(): break
file.write('\t[-] ' + e(description) + '\n\n') if not plugin.run_once_boolean or (plugin.run_once_boolean and not plugin_run):
for command in commands: with open(os.path.join(scandir, '_manual_commands.txt'), 'a') as file:
file.write('\t\t' + e(command) + '\n\n') if not heading:
file.flush() file.write(e('[*] {service.name} on {service.protocol}/{service.port}\n\n'))
heading = True
for description, commands in service.manual_commands.items():
file.write('\t[-] ' + e(description) + '\n\n')
for command in commands:
file.write('\t\t' + e(command) + '\n\n')
file.flush()
service.manual_commands = {} service.manual_commands = {}
break break
@ -584,15 +623,23 @@ async def scan_target(target):
for plugin in matching_plugins: for plugin in matching_plugins:
plugin_tag = service.tag() + '/' + plugin.slug plugin_tag = service.tag() + '/' + plugin.slug
scan_tuple = (service.protocol, service.port, service.name, plugin.slug)
if plugin.run_once_boolean: if plugin.run_once_boolean:
scan_tuple = (plugin.slug,) plugin_tag = plugin.slug
if scan_tuple in target.scans: plugin_queued = False
warn('{byellow}[' + plugin_tag + ' against ' + target.address + ']{srst} Plugin appears to have already been queued, but it is not marked as run_once. Possible duplicate service tag? Skipping.{rst}') if service in target.scans['services']:
for s in target.scans['services']:
if plugin_tag in target.scans['services'][s]:
plugin_queued = True
warn('{byellow}[' + plugin_tag + ' against ' + target.address + ']{srst} Plugin appears to have already been queued, but it is not marked as run_once. Possible duplicate service tag? Skipping.{rst}')
break
if plugin_queued:
continue continue
else: else:
target.scans.append(scan_tuple) if service not in target.scans['services']:
target.scans['services'][service] = {}
target.scans['services'][service][plugin_tag] = {'plugin':plugin, 'commands':[]}
pending.add(asyncio.create_task(service_scan(plugin, service))) pending.add(asyncio.create_task(service_scan(plugin, service)))
@ -601,6 +648,27 @@ async def scan_target(target):
if service.full_tag() not in target.autorecon.missing_services: if service.full_tag() not in target.autorecon.missing_services:
target.autorecon.missing_services.append(service.full_tag()) target.autorecon.missing_services.append(service.full_tag())
for plugin in target.autorecon.plugin_types['report']:
plugin_tag_set = set(plugin.tags)
matching_tags = False
for tag_group in target.autorecon.tags:
if set(tag_group).issubset(plugin_tag_set):
matching_tags = True
break
excluded_tags = False
for tag_group in target.autorecon.excluded_tags:
if set(tag_group).issubset(plugin_tag_set):
excluded_tags = True
break
if matching_tags and not excluded_tags:
pending.add(asyncio.create_task(generate_report(plugin, [target])))
while pending:
done, pending = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED, timeout=1)
heartbeat.cancel() heartbeat.cancel()
elapsed_time = calculate_elapsed_time(start_time) elapsed_time = calculate_elapsed_time(start_time)
@ -621,6 +689,7 @@ async def scan_target(target):
info('Finished scanning target {byellow}' + target.address + '{rst} in ' + elapsed_time) info('Finished scanning target {byellow}' + target.address + '{rst} in ' + elapsed_time)
async with autorecon.lock: async with autorecon.lock:
autorecon.completed_targets.append(target)
autorecon.scanning_targets.remove(target) autorecon.scanning_targets.remove(target)
async def main(): async def main():
@ -726,11 +795,11 @@ async def main():
print('Plugin "' + c.__name__ + '" in ' + filename + ' is using a protected class name. Please change it.') print('Plugin "' + c.__name__ + '" in ' + filename + ' is using a protected class name. Please change it.')
sys.exit(1) sys.exit(1)
# Only add classes that are a sub class of either PortScan or ServiceScan # Only add classes that are a sub class of either PortScan, ServiceScan, or Report
if issubclass(c, PortScan) or issubclass(c, ServiceScan): if issubclass(c, PortScan) or issubclass(c, ServiceScan) or issubclass(c, Report):
autorecon.register(c(), filename) autorecon.register(c(), filename)
else: else:
print('Plugin "' + c.__name__ + '" in ' + filename + ' is not a subclass of either PortScan or ServiceScan.') print('Plugin "' + c.__name__ + '" in ' + filename + ' is not a subclass of either PortScan, ServiceScan, or Report.')
except (ImportError, SyntaxError) as ex: except (ImportError, SyntaxError) as ex:
print('cannot import ' + filename + ' plugin') print('cannot import ' + filename + ' plugin')
print(ex) print(ex)
@ -748,6 +817,7 @@ async def main():
# Sort plugins by priority. # Sort plugins by priority.
autorecon.plugin_types['port'].sort(key=lambda x: x.priority) autorecon.plugin_types['port'].sort(key=lambda x: x.priority)
autorecon.plugin_types['service'].sort(key=lambda x: x.priority) autorecon.plugin_types['service'].sort(key=lambda x: x.priority)
autorecon.plugin_types['report'].sort(key=lambda x: x.priority)
if not os.path.isfile(config['global_file']): if not os.path.isfile(config['global_file']):
fail('Error: Specified global file "' + config['global_file'] + '" does not exist.') fail('Error: Specified global file "' + config['global_file'] + '" does not exist.')
@ -867,6 +937,9 @@ async def main():
if type in ['plugin', 'plugins', 'service', 'services', 'servicescan', 'servicescans']: if type in ['plugin', 'plugins', 'service', 'services', 'servicescan', 'servicescans']:
for p in autorecon.plugin_types['service']: for p in autorecon.plugin_types['service']:
print('ServiceScan: ' + p.name + ' (' + p.slug + ')' + (' - ' + p.description if p.description else '')) print('ServiceScan: ' + p.name + ' (' + p.slug + ')' + (' - ' + p.description if p.description else ''))
if type in ['plugin', 'plugins', 'report', 'reporting']:
for p in autorecon.plugin_types['report']:
print('Report: ' + p.name + ' (' + p.slug + ')' + (' - ' + p.description if p.description else ''))
sys.exit(0) sys.exit(0)
@ -1216,6 +1289,27 @@ async def main():
keyboard_monitor.cancel() keyboard_monitor.cancel()
for plugin in autorecon.plugin_types['report']:
plugin_tag_set = set(plugin.tags)
matching_tags = False
for tag_group in autorecon.tags:
if set(tag_group).issubset(plugin_tag_set):
matching_tags = True
break
excluded_tags = False
for tag_group in autorecon.excluded_tags:
if set(tag_group).issubset(plugin_tag_set):
excluded_tags = True
break
if matching_tags and not excluded_tags:
pending.add(asyncio.create_task(generate_report(plugin, autorecon.completed_targets)))
while pending:
done, pending = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED, timeout=1)
if timed_out: if timed_out:
cancel_all_tasks(None, None) cancel_all_tasks(None, None)

View File

@ -104,6 +104,10 @@ class CommandStreamReader(object):
self.outfile = outfile self.outfile = outfile
self.ended = False self.ended = False
# Empty files that already exist.
if self.outfile != None:
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:

View File

@ -191,14 +191,20 @@ class ServiceScan(Plugin):
def match_all_service_names(self, boolean): def match_all_service_names(self, boolean):
self.match_all_service_names_boolean = boolean self.match_all_service_names_boolean = boolean
class Report(Plugin):
def __init__(self):
super().__init__()
class AutoRecon(object): class AutoRecon(object):
def __init__(self): def __init__(self):
self.pending_targets = [] self.pending_targets = []
self.scanning_targets = [] self.scanning_targets = []
self.completed_targets = []
self.plugins = {} self.plugins = {}
self.__slug_regex = re.compile('^[a-z0-9\-]+$') self.__slug_regex = re.compile('^[a-z0-9\-]+$')
self.plugin_types = {'port':[], 'service':[]} self.plugin_types = {'port':[], 'service':[], 'report':[]}
self.port_scan_semaphore = None self.port_scan_semaphore = None
self.service_scan_semaphore = None self.service_scan_semaphore = None
self.argparse = None self.argparse = None
@ -259,6 +265,9 @@ class AutoRecon(object):
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.')
for _, loaded_plugin in self.plugins.items(): for _, loaded_plugin in self.plugins.items():
if plugin.name == loaded_plugin.name: if plugin.name == loaded_plugin.name:
fail('Error: Duplicate plugin name "' + plugin.name + '" detected in ' + filename + '.', file=sys.stderr) fail('Error: Duplicate plugin name "' + plugin.name + '" detected in ' + filename + '.', file=sys.stderr)
@ -296,13 +305,14 @@ class AutoRecon(object):
if not run_coroutine_found and not manual_function_found: 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) 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): if issubclass(plugin.__class__, PortScan):
self.plugin_types["port"].append(plugin) self.plugin_types["port"].append(plugin)
elif issubclass(plugin.__class__, ServiceScan): elif issubclass(plugin.__class__, ServiceScan):
self.plugin_types["service"].append(plugin) self.plugin_types["service"].append(plugin)
elif issubclass(plugin.__class__, Report):
self.plugin_types["report"].append(plugin)
else: else:
fail('Plugin "' + plugin.name + '" in ' + filename + ' is neither a PortScan nor a ServiceScan.', file=sys.stderr) 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] plugin.tags = [tag.lower() for tag in plugin.tags]

View File

@ -18,7 +18,7 @@ class Target:
self.ports = None self.ports = None
self.pending_services = [] self.pending_services = []
self.services = [] self.services = []
self.scans = [] self.scans = {'ports':{}, 'services':{}}
self.running_tasks = {} self.running_tasks = {}
async def add_service(self, service): async def add_service(self, service):
@ -31,7 +31,7 @@ class Target:
async def extract_services(self, stream, regex=None): async def extract_services(self, stream, regex=None):
return await self.autorecon.extract_services(stream, regex) return await self.autorecon.extract_services(stream, regex)
async def execute(self, cmd, blocking=True, outfile=None, errfile=None): async def execute(self, cmd, blocking=True, outfile=None, errfile=None, future_outfile=None):
target = self target = self
# Create variables for command references. # Create variables for command references.
@ -55,9 +55,7 @@ class Target:
nmap_extra += ' -sT' nmap_extra += ' -sT'
plugin = inspect.currentframe().f_back.f_locals['self'] plugin = inspect.currentframe().f_back.f_locals['self']
cmd = e(cmd) cmd = e(cmd)
tag = plugin.slug tag = plugin.slug
if config['verbose'] >= 1: if config['verbose'] >= 1:
@ -69,6 +67,11 @@ class Target:
if errfile is not None: if errfile is not None:
errfile = os.path.join(target.scandir, e(errfile)) errfile = os.path.join(target.scandir, e(errfile))
if future_outfile is not None:
future_outfile = os.path.join(target.scandir, e(future_outfile))
target.scans['ports'][tag]['commands'].append([cmd, outfile if outfile is not None else future_outfile, errfile])
async with target.lock: async with target.lock:
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')
@ -118,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): async def execute(self, cmd, blocking=True, outfile=None, errfile=None, future_outfile=None):
target = self.target target = self.target
# Create variables for command references. # Create variables for command references.
@ -156,10 +159,11 @@ class Service:
nmap_extra += ' -sT' nmap_extra += ' -sT'
plugin = inspect.currentframe().f_back.f_locals['self'] plugin = inspect.currentframe().f_back.f_locals['self']
cmd = e(cmd) cmd = e(cmd)
tag = self.tag() + '/' + plugin.slug tag = self.tag() + '/' + plugin.slug
plugin_tag = tag
if plugin.run_once_boolean:
plugin_tag = plugin.slug
if config['verbose'] >= 1: if config['verbose'] >= 1:
info('Service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} is running the following command against {byellow}' + address + '{rst}: ' + cmd) info('Service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} is running the following command against {byellow}' + address + '{rst}: ' + cmd)
@ -170,6 +174,11 @@ class Service:
if errfile is not None: if errfile is not None:
errfile = os.path.join(scandir, e(errfile)) errfile = os.path.join(scandir, e(errfile))
if future_outfile is not None:
future_outfile = os.path.join(scandir, e(future_outfile))
target.scans['services'][self][plugin_tag]['commands'].append([cmd, outfile if outfile is not None else future_outfile, errfile])
async with target.lock: async with target.lock:
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')

View File

@ -68,7 +68,7 @@ class CurlRobots(ServiceScan):
async def run(self, service): async def run(self, service):
if service.protocol == 'tcp': if service.protocol == 'tcp':
_, stdout, _ = await service.execute('curl -sSikf {http_scheme}://{addressv6}:{port}/robots.txt') _, stdout, _ = await service.execute('curl -sSikf {http_scheme}://{addressv6}:{port}/robots.txt', future_outfile='{protocol}_{port}_{http_scheme}_curl-robots.txt')
lines = await stdout.readlines() lines = await stdout.readlines()
if lines: if lines:

88
plugins/reporting.py Normal file
View File

@ -0,0 +1,88 @@
from autorecon.plugins import Report
from autorecon.config import config
from xml.sax.saxutils import escape
import os, glob
class CherryTree(Report):
def __init__(self):
super().__init__()
self.name = 'CherryTree'
async def run(self, targets):
if len(targets) > 1:
report = os.path.join(config['outdir'], 'cherrytree.xml.ctd')
elif len(targets) == 1:
report = os.path.join(targets[0].reportdir, 'cherrytree.xml.ctd')
else:
return
with open(report, 'w') as output:
output.writelines('<?xml version="1.0" encoding="UTF-8"?>\n<cherrytree>\n')
for target in targets:
output.writelines('<node name="' + escape(target.address) + '" is_bold="1" custom_icon_id="1">\n')
files = [os.path.abspath(filename) for filename in glob.iglob(os.path.join(target.scandir, '**/*'), recursive=True) if os.path.isfile(filename) and filename.endswith(('.txt', '.html'))]
if target.scans['ports']:
output.writelines('<node name="Port Scans" custom_icon_id="2">\n')
for scan in target.scans['ports'].keys():
output.writelines('<node name="PortScan: ' + escape(scan) + '" custom_icon_id="21">\n')
for command in target.scans['ports'][scan]['commands']:
output.writelines('<rich_text>' + escape(command[0]))
for filename in files:
if filename in command[0] or (command[1] is not None and filename == command[1]) or (command[2] is not None and filename == command[2]):
output.writelines('\n\n' + escape(filename) + ':\n\n')
with open(filename, 'r') as file:
output.writelines(escape(file.read()) + '\n')
output.writelines('</rich_text>\n')
output.writelines('</node>\n')
output.writelines('</node>\n')
if target.scans['services']:
output.writelines('<node name="Services" custom_icon_id="2">\n')
for service in target.scans['services'].keys():
output.writelines('<node name="Service: ' + escape(service.tag()) + '" custom_icon_id="3">\n')
for plugin in target.scans['services'][service].keys():
output.writelines('<node name="' + escape(target.scans['services'][service][plugin]['plugin'].slug) + '" custom_icon_id="21">\n')
for command in target.scans['services'][service][plugin]['commands']:
output.writelines('<rich_text>' + escape(command[0]))
for filename in files:
if filename in command[0] or (command[1] is not None and filename == command[1]) or (command[2] is not None and filename == command[2]):
output.writelines('\n\n' + escape(filename) + ':\n\n')
with open(filename, 'r') as file:
output.writelines(escape(file.read()) + '\n')
output.writelines('</rich_text>\n')
output.writelines('</node>\n')
output.writelines('</node>\n')
output.writelines('</node>\n')
manual_commands = os.path.join(target.scandir, '_manual_commands.txt')
if os.path.isfile(manual_commands):
output.writelines('<node name="Manual Commands" custom_icon_id="22">\n')
with open(manual_commands, 'r') as file:
output.writelines('<rich_text>' + escape(file.read()) + '</rich_text>\n')
output.writelines('</node>\n')
patterns = os.path.join(target.scandir, '_patterns.log')
if os.path.isfile(patterns):
output.writelines('<node name="Patterns" custom_icon_id="10">\n')
with open(patterns, 'r') as file:
output.writelines('<rich_text>' + escape(file.read()) + '</rich_text>\n')
output.writelines('</node>\n')
commands = os.path.join(target.scandir, '_commands.log')
if os.path.isfile(commands):
output.writelines('<node name="Commands" custom_icon_id="21">\n')
with open(commands, 'r') as file:
output.writelines('<rich_text>' + escape(file.read()) + '</rich_text>\n')
output.writelines('</node>\n')
errors = os.path.join(target.scandir, '_errors.log')
if os.path.isfile(errors):
output.writelines('<node name="Errors" custom_icon_id="57">\n')
with open(errors, 'r') as file:
output.writelines('<rich_text>' + escape(file.read()) + '</rich_text>\n')
output.writelines('</node>\n')
output.writelines('</node>\n')
output.writelines('</cherrytree>')