Update autorecon.py

Added --create-port-dirs option which will create port directories (e.g. tcp80, udp53) in the scans directory and put all relevant scans there.
Now most command line options can be set in the config.toml file as well.
This commit is contained in:
Tib3rius 2021-08-17 19:03:29 -04:00
parent a1ca13ecbe
commit 6843c0450f
1 changed files with 83 additions and 40 deletions

View File

@ -58,10 +58,10 @@ class Target:
info('Port scan {bblue}' + plugin.name + ' (' + tag + '){rst} is running the following command against {byellow}' + address + '{rst}: ' + cmd)
if outfile is not None:
outfile = os.path.abspath(os.path.join(target.scandir, e(outfile)))
outfile = os.path.join(target.scandir, e(outfile))
if errfile is not None:
errfile = os.path.abspath(os.path.join(target.scandir, e(errfile)))
errfile = os.path.join(target.scandir, e(errfile))
async with target.lock:
with open(os.path.join(target.scandir, '_commands.log'), 'a') as file:
@ -121,6 +121,11 @@ class Service:
port = self.port
name = self.name
if target.autorecon.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'
@ -141,10 +146,10 @@ class Service:
info('Service scan {bblue}' + plugin.name + ' (' + tag + '){rst} is running the following command against {byellow}' + address + '{rst}: ' + cmd)
if outfile is not None:
outfile = os.path.abspath(os.path.join(target.scandir, e(outfile)))
outfile = os.path.join(scandir, e(outfile))
if errfile is not None:
errfile = os.path.abspath(os.path.join(target.scandir, e(errfile)))
errfile = os.path.join(scandir, e(errfile))
async with target.lock:
with open(os.path.join(target.scandir, '_commands.log'), 'a') as file:
@ -376,18 +381,44 @@ class AutoRecon(object):
self.tags = []
self.excluded_tags = []
self.patterns = []
self.configurable_keys = ['max_scans', 'max_port_scans', 'single_target', 'outdir', 'only_scans_dir', 'heartbeat', 'timeout', 'target_timeout', 'accessible', 'verbose']
self.configurable_keys = [
'max_scans',
'max_port_scans',
'tags',
'exclude_tags',
'plugins_dir',
'outdir',
'single_target',
'only_scans_dir',
'create_port_dirs',
'heartbeat',
'timeout',
'target_timeout',
'nmap',
'nmap_append',
'disable_sanity_checks',
'accessible',
'verbose'
]
self.configurable_boolean_keys = ['single_target', 'only_scans_dir', 'create_port_dirs', 'disable_sanity_checks', 'accessible']
self.config = {
'protected_classes': ['autorecon', 'target', 'service', 'commandstreamreader', 'plugin', 'portscan', 'servicescan', 'global', 'pattern'],
'global_file': os.path.dirname(os.path.realpath(__file__)) + '/global.toml',
'max_scans': 50,
'max_port_scans': None,
'single_target': False,
'tags': 'default',
'exclude_tags': None,
'plugins_dir': os.path.dirname(os.path.abspath(__file__)) + '/plugins',
'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,
'accessible': False,
'verbose': 0
}
@ -806,27 +837,27 @@ async def scan_target(target):
os.makedirs(basedir, exist_ok=True)
if not target.autorecon.config['only_scans_dir']:
exploitdir = os.path.abspath(os.path.join(basedir, 'exploit'))
exploitdir = os.path.join(basedir, 'exploit')
os.makedirs(exploitdir, exist_ok=True)
lootdir = os.path.abspath(os.path.join(basedir, 'loot'))
lootdir = os.path.join(basedir, 'loot')
os.makedirs(lootdir, exist_ok=True)
reportdir = os.path.abspath(os.path.join(basedir, 'report'))
reportdir = os.path.join(basedir, 'report')
target.reportdir = reportdir
os.makedirs(reportdir, exist_ok=True)
open(os.path.abspath(os.path.join(reportdir, 'local.txt')), 'a').close()
open(os.path.abspath(os.path.join(reportdir, 'proof.txt')), 'a').close()
open(os.path.join(reportdir, 'local.txt'), 'a').close()
open(os.path.join(reportdir, 'proof.txt'), 'a').close()
screenshotdir = os.path.abspath(os.path.join(reportdir, 'screenshots'))
screenshotdir = os.path.join(reportdir, 'screenshots')
os.makedirs(screenshotdir, exist_ok=True)
scandir = os.path.abspath(os.path.join(basedir, 'scans'))
scandir = os.path.join(basedir, 'scans')
target.scandir = scandir
os.makedirs(scandir, exist_ok=True)
os.makedirs(os.path.abspath(os.path.join(scandir, 'xml')), exist_ok=True)
os.makedirs(os.path.join(scandir, 'xml'), exist_ok=True)
pending = []
@ -1047,24 +1078,25 @@ async def main():
parser = argparse.ArgumentParser(add_help=False, description='Network reconnaissance tool to port scan and automatically enumerate services found on multiple targets.')
parser.add_argument('targets', action='store', help='IP addresses (e.g. 10.0.0.1), CIDR notation (e.g. 10.0.0.1/24), or resolvable hostnames (e.g. foo.bar) to scan.', nargs='*')
parser.add_argument('-t', '--targets', action='store', type=str, default='', dest='target_file', help='Read targets from file.')
parser.add_argument('-m', '--max-scans', action='store', type=int, help='The maximum number of concurrent scans to run. Default: 50')
parser.add_argument('-m', '--max-scans', action='store', type=int, help='The maximum number of concurrent scans to run. Default: %(default)s')
parser.add_argument('-mp', '--max-port-scans', action='store', type=int, help='The maximum number of concurrent port scans to run. Default: 10 (approx 20%% of max-scans unless specified)')
parser.add_argument('-c', '--config', action='store', type=str, default=os.path.dirname(os.path.realpath(__file__)) + '/config.toml', dest='config_file', help='Location of AutoRecon\'s config file. Default: %(default)s')
parser.add_argument('-g', '--global-file', action='store', type=str, dest='global_file', help='Location of AutoRecon\'s global file. Default: ' + os.path.dirname(os.path.realpath(__file__)) + '/global.toml')
parser.add_argument('--tags', action='store', type=str, default='default', help='Tags to determine which plugins should be included. Separate tags by a plus symbol (+) to group tags together. Separate groups with a comma (,) to create multiple groups. For a plugin to be included, it must have all the tags specified in at least one group.')
parser.add_argument('--exclude-tags', action='store', type=str, default='', help='Tags to determine which plugins should be excluded. Separate tags by a plus symbol (+) to group tags together. Separate groups with a comma (,) to create multiple groups. For a plugin to be excluded, it must have all the tags specified in at least one group.')
parser.add_argument('--plugins-dir', action='store', type=str, default=os.path.dirname(os.path.abspath(__file__)) + '/plugins', help='')
parser.add_argument('-o', '--output', action='store', dest='outdir', help='The output directory for results. Default: results')
parser.add_argument('--single-target', action='store_true', help='Only scan a single target. A directory named after the target will not be created. Instead, the directory structure will be created within the output directory. Default: false')
parser.add_argument('--only-scans-dir', action='store_true', help='Only create the "scans" directory for results. Other directories (e.g. exploit, loot, report) will not be created. Default: false')
parser.add_argument('--heartbeat', action='store', type=int, help='Specifies the heartbeat interval (in seconds) for scan status messages. Default: 60')
parser.add_argument('--timeout', action='store', type=int, help='Specifies the maximum amount of time in minutes that AutoRecon should run for. Default: no timeout')
parser.add_argument('--target-timeout', action='store', type=int, help='Specifies the maximum amount of time in minutes that a target should be scanned for before abandoning it and moving on. Default: no timeout')
parser.add_argument('--tags', action='store', type=str, default='default', help='Tags to determine which plugins should be included. Separate tags by a plus symbol (+) to group tags together. Separate groups with a comma (,) to create multiple groups. For a plugin to be included, it must have all the tags specified in at least one group. Default: %(default)s')
parser.add_argument('--exclude-tags', action='store', type=str, default='', help='Tags to determine which plugins should be excluded. Separate tags by a plus symbol (+) to group tags together. Separate groups with a comma (,) to create multiple groups. For a plugin to be excluded, it must have all the tags specified in at least one group. Default: %(default)s')
parser.add_argument('--plugins-dir', action='store', type=str, help='The location of the plugins directory. Default: %(default)s')
parser.add_argument('-o', '--output', action='store', dest='outdir', help='The output directory for results. Default: %(default)s')
parser.add_argument('--single-target', action='store_true', help='Only scan a single target. A directory named after the target will not be created. Instead, the directory structure will be created within the output directory. Default: %(default)s')
parser.add_argument('--only-scans-dir', action='store_true', help='Only create the "scans" directory for results. Other directories (e.g. exploit, loot, report) will not be created. Default: %(default)s')
parser.add_argument('--create-port-dirs', action='store_true', help='Create directories for ports within the "scans" directory (e.g. scans/tcp80, scans/udp53) and store results in these directories. Default: %(default)s')
parser.add_argument('--heartbeat', action='store', type=int, help='Specifies the heartbeat interval (in seconds) for scan status messages. Default: %(default)s')
parser.add_argument('--timeout', action='store', type=int, help='Specifies the maximum amount of time in minutes that AutoRecon should run for. Default: %(default)s')
parser.add_argument('--target-timeout', action='store', type=int, help='Specifies the maximum amount of time in minutes that a target should be scanned for before abandoning it and moving on. Default: %(default)s')
nmap_group = parser.add_mutually_exclusive_group()
nmap_group.add_argument('--nmap', action='store', default='-vv --reason -Pn', help='Override the {nmap_extra} variable in scans. Default: %(default)s')
nmap_group.add_argument('--nmap-append', action='store', default='', help='Append to the default {nmap_extra} variable in scans.')
parser.add_argument('--disable-sanity-checks', action='store_true', default=False, help='Disable sanity checks that would otherwise prevent the scans from running. Default: false')
parser.add_argument('--accessible', action='store_true', help='Attempts to make AutoRecon output more accessible to screenreaders.')
nmap_group.add_argument('--nmap', action='store', help='Override the {nmap_extra} variable in scans. Default: %(default)s')
nmap_group.add_argument('--nmap-append', action='store', help='Append to the default {nmap_extra} variable in scans. Default: %(default)s')
parser.add_argument('--disable-sanity-checks', action='store_true', help='Disable sanity checks that would otherwise prevent the scans from running. Default: %(default)s')
parser.add_argument('--accessible', action='store_true', help='Attempts to make AutoRecon output more accessible to screenreaders. Default: %(default)s')
parser.add_argument('-v', '--verbose', action='count', help='Enable verbose output. Repeat for more verbosity.')
parser.add_argument('--version', action='store_true', help='Prints the AutoRecon version and exits.')
parser.error = lambda s: fail(s[0].upper() + s[1:])
@ -1086,9 +1118,10 @@ async def main():
try:
config_toml = toml.load(c)
for key, val in config_toml.items():
if key.replace('-', '_') == 'global_file':
if key == 'global-file':
autorecon.config['global_file'] = val
break
elif key == 'plugins-dir':
autorecon.config['plugins_dir'] = val
except toml.decoder.TomlDecodeError:
fail('Error: Couldn\'t parse ' + args.config_file + ' config file. Check syntax.')
@ -1096,19 +1129,20 @@ async def main():
for key in args_dict:
if key == 'global_file' and args_dict['global_file'] is not None:
autorecon.config['global_file'] = args_dict['global_file']
break
elif key == 'plugins_dir' and args_dict['plugins_dir'] is not None:
autorecon.config['plugins_dir'] = args_dict['plugins_dir']
if not os.path.isdir(args.plugins_dir):
fail('Error: Specified plugins directory "' + args.plugins_dir + '" does not exist.')
if not os.path.isdir(autorecon.config['plugins_dir']):
fail('Error: Specified plugins directory "' + autorecon.config['plugins_dir'] + '" does not exist.')
for plugin_file in os.listdir(args.plugins_dir):
for plugin_file in os.listdir(autorecon.config['plugins_dir']):
if not plugin_file.startswith('_') and plugin_file.endswith('.py'):
dirname, filename = os.path.split(plugin_file)
dirname = os.path.abspath(dirname)
try:
plugin = importlib.import_module('.' + filename[:-3], os.path.basename(args.plugins_dir))
plugin = importlib.import_module('.' + filename[:-3], os.path.basename(autorecon.config['plugins_dir']))
clsmembers = inspect.getmembers(plugin, predicate=inspect.isclass)
for (_, c) in clsmembers:
if c.__module__ == 'autorecon':
@ -1129,7 +1163,7 @@ async def main():
sys.exit(1)
if len(autorecon.plugin_types['port']) == 0:
fail('Error: There are no valid PortScan plugins in the plugins directory "' + args.plugins_dir + '".')
fail('Error: There are no valid PortScan plugins in the plugins directory "' + autorecon.config['plugins_dir'] + '".')
# Sort plugins by priority.
autorecon.plugin_types['port'].sort(key=lambda x: x.priority)
@ -1192,6 +1226,7 @@ async def main():
except toml.decoder.TomlDecodeError:
fail('Error: Couldn\'t parse ' + g.name + ' file. Check syntax.')
other_options = []
for key, val in config_toml.items():
if key == 'global' and isinstance(val, dict): # Process global plugin options.
for gkey, gval in config_toml['global'].items():
@ -1223,9 +1258,15 @@ async def main():
if autorecon.argparse.get_default(slugify(key).replace('-', '_') + '.' + slugify(pkey).replace('-', '_')):
autorecon.argparse.set_defaults(**{slugify(key).replace('-', '_') + '.' + slugify(pkey).replace('-', '_'): pval})
else: # Process potential other options.
if key.replace('-', '_') in autorecon.configurable_keys:
autorecon.config[key.replace('-', '_')] = val
autorecon.argparse.set_defaults(**{key.replace('-', '_'): val})
key = key.replace('-', '_')
if key in autorecon.configurable_keys:
other_options.append(key)
autorecon.config[key] = val
autorecon.argparse.set_defaults(**{key: val})
for key, val in autorecon.config.items():
if key not in other_options:
autorecon.argparse.set_defaults(**{key: val})
parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS, help='Show this help message and exit.')
args = parser.parse_args()
@ -1234,7 +1275,7 @@ async def main():
for key in args_dict:
if key in autorecon.configurable_keys and args_dict[key] is not None:
# Special case for booleans
if key in ['accessible', 'single_target', 'only_scans_dir'] and autorecon.config[key]:
if key in autorecon.configurable_boolean_keys and autorecon.config[key]:
continue
autorecon.config[key] = args_dict[key]
@ -1287,6 +1328,8 @@ async def main():
[autorecon.tags.append(t) for t in tags if t not in autorecon.tags]
excluded_tags = []
if args.exclude_tags is None:
args.exclude_tags = ''
if args.exclude_tags != '':
for tag_group in list(set(filter(None, args.exclude_tags.lower().split(',')))):
excluded_tags.append(list(set(filter(None, tag_group.split('+')))))