diff --git a/autorecon.py b/autorecon.py index de289ed..ea2a304 100644 --- a/autorecon.py +++ b/autorecon.py @@ -17,6 +17,7 @@ import re import socket import string import sys +import time import toml verbose = 0 @@ -33,6 +34,9 @@ password_wordlist = '/usr/share/seclists/Passwords/darkweb2017-top100.txt' rootdir = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) +single_target = False +only_scans_dir = False + def e(*args, frame_index=1, **kvargs): frame = sys._getframe(frame_index) @@ -104,22 +108,61 @@ def fail(*args, sep=' ', end='\n', file=sys.stderr, **kvargs): cprint(*args, color=Fore.RED, char='!', sep=sep, end=end, file=file, frame_index=2, **kvargs) exit(-1) +def merge_toml(a, b, path=None): + if path is None: path = [] + for key in b: + if key in a: + if isinstance(a[key], dict) and isinstance(b[key], dict): + merge_toml(a[key], b[key], path + [str(key)]) + elif a[key] == b[key]: + pass + else: + a[key] = b[key] + else: + a[key] = b[key] + return a + port_scan_profiles_config_file = 'port-scan-profiles.toml' -with open(os.path.join(rootdir, 'config', port_scan_profiles_config_file), 'r') as p: - try: - port_scan_profiles_config = toml.load(p) +try: + with open(os.path.join(rootdir, 'config', port_scan_profiles_config_file), 'r') as p: + try: + port_scan_profiles_config = toml.load(p) - if len(port_scan_profiles_config) == 0: - fail('There do not appear to be any port scan profiles configured in the {port_scan_profiles_config_file} config file.') + custom_port_scan_profiles_config_file = os.path.join(rootdir, 'config', 'custom-port-scan-profiles.toml') + if os.path.isfile(custom_port_scan_profiles_config_file): + try: + with open(custom_port_scan_profiles_config_file, 'r') as c: + custom_port_scan_profiles_config = toml.load(c) + merge_toml(port_scan_profiles_config, custom_port_scan_profiles_config) + except IOError: + fail('Error: Couldn\'t load custom-port-scan-profiles.toml config file. Check the file exists and has the correct permissions.') - except toml.decoder.TomlDecodeError as e: - fail('Error: Couldn\'t parse {port_scan_profiles_config_file} config file. Check syntax and duplicate tags.') + if len(port_scan_profiles_config) == 0: + fail('There do not appear to be any port scan profiles configured in the {port_scan_profiles_config_file} config file.') -with open(os.path.join(rootdir, 'config', 'service-scans.toml'), 'r') as c: - try: - service_scans_config = toml.load(c) - except toml.decoder.TomlDecodeError as e: - fail('Error: Couldn\'t parse service-scans.toml config file. Check syntax and duplicate tags.') + except toml.decoder.TomlDecodeError as e: + fail('Error: Couldn\'t parse {port_scan_profiles_config_file} or custom-port-scan-profiles.toml config file. Check syntax and duplicate tags.') +except IOError: + fail('Error: Couldn\'t load {port_scan_profiles_config_file} config file. Check the file exists and has the correct permissions.') + +try: + with open(os.path.join(rootdir, 'config', 'service-scans.toml'), 'r') as s: + try: + service_scans_config = toml.load(s) + + custom_service_scans_config_file = os.path.join(rootdir, 'config', 'custom-service-scans.toml') + if os.path.isfile(custom_service_scans_config_file): + try: + with open(custom_service_scans_config_file, 'r') as c: + custom_service_scans_config = toml.load(c) + merge_toml(service_scans_config, custom_service_scans_config) + except IOError: + fail('Error: Couldn\'t load custom-service-scans.toml config file. Check the file exists and has the correct permissions.') + + except toml.decoder.TomlDecodeError as e: + fail('Error: Couldn\'t parse service-scans.toml or custom-service-scans.toml config file. Check syntax and duplicate tags.') +except IOError: + fail('Error: Couldn\'t load service-scans.toml config file. Check the file exists and has the correct permissions.') with open(os.path.join(rootdir, 'config', 'global-patterns.toml'), 'r') as p: try: @@ -401,8 +444,9 @@ async def scan_services(loop, semaphore, target): info('Found {bmagenta}{service}{rst} on {bmagenta}{protocol}/{port}{rst} on target {byellow}{address}{rst}') - with open(os.path.join(target.reportdir, 'notes.txt'), 'a') as file: - file.writelines(e('[*] {service} found on {protocol}/{port}.\n\n\n\n')) + if not only_scans_dir: + with open(os.path.join(target.reportdir, 'notes.txt'), 'a') as file: + file.writelines(e('[*] {service} found on {protocol}/{port}.\n\n\n\n')) if protocol == 'udp': nmap_extra = nmap + " -sU" @@ -514,24 +558,32 @@ async def scan_services(loop, semaphore, target): pending.add(asyncio.ensure_future(run_cmd(semaphore, e(command), target, tag=tag, patterns=patterns))) def scan_host(target, concurrent_scans): + start_time = time.time() info('Scanning target {byellow}{target.address}{rst}') - basedir = os.path.abspath(os.path.join(outdir, target.address + srvname)) + if single_target: + basedir = os.path.abspath(outdir) + else: + basedir = os.path.abspath(os.path.join(outdir, target.address + srvname)) target.basedir = basedir os.makedirs(basedir, exist_ok=True) - exploitdir = os.path.abspath(os.path.join(basedir, 'exploit')) - os.makedirs(exploitdir, exist_ok=True) + if not only_scans_dir: + exploitdir = os.path.abspath(os.path.join(basedir, 'exploit')) + os.makedirs(exploitdir, exist_ok=True) - lootdir = os.path.abspath(os.path.join(basedir, 'loot')) - os.makedirs(lootdir, exist_ok=True) + lootdir = os.path.abspath(os.path.join(basedir, 'loot')) + os.makedirs(lootdir, exist_ok=True) - reportdir = os.path.abspath(os.path.join(basedir, 'report')) - target.reportdir = reportdir - os.makedirs(reportdir, exist_ok=True) + reportdir = os.path.abspath(os.path.join(basedir, 'report')) + target.reportdir = reportdir + os.makedirs(reportdir, exist_ok=True) - screenshotdir = os.path.abspath(os.path.join(reportdir, 'screenshots')) - os.makedirs(screenshotdir, 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() + + screenshotdir = os.path.abspath(os.path.join(reportdir, 'screenshots')) + os.makedirs(screenshotdir, exist_ok=True) scandir = os.path.abspath(os.path.join(basedir, 'scans')) target.scandir = scandir @@ -539,9 +591,6 @@ def scan_host(target, concurrent_scans): os.makedirs(os.path.abspath(os.path.join(scandir, 'xml')), 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() - # Use a lock when writing to specific files that may be written to by other asynchronous functions. target.lock = asyncio.Lock() @@ -553,6 +602,15 @@ def scan_host(target, concurrent_scans): try: loop.run_until_complete(scan_services(loop, semaphore, target)) + elapsed_seconds = time.time() - start_time + + m, s = divmod(elapsed_seconds, 60) + h, m = divmod(m, 60) + + elapsed_time = '' + if h > 0: + elapsed_time += h + ' hour' + info('Finished scanning target {byellow}{target.address}{rst}') except KeyboardInterrupt: sys.exit(1) @@ -575,6 +633,8 @@ if __name__ == '__main__': parser.add_argument('--profile', action='store', default='default', help='The port scanning profile to use (defined in port-scan-profiles.toml). Default: %(default)s') parser.add_argument('-o', '--output', action='store', default='results', help='The output directory for results. Default: %(default)s') nmap_group = parser.add_mutually_exclusive_group() + parser.add_argument('--single-target', action='store_true', default=False, 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', default=False, help='Only create the "scans" directory for results. Other directories (e.g. exploit, loot, report) will not be created.') 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('-v', '--verbose', action='count', default=0, help='Enable verbose output. Repeat for more verbosity.') @@ -582,6 +642,9 @@ if __name__ == '__main__': parser.error = lambda s: fail(s[0].upper() + s[1:]) args = parser.parse_args() + single_target = args.single_target + only_scans_dir = args.only_scans_dir + errors = False if args.concurrent_targets <= 0: @@ -651,6 +714,10 @@ if __name__ == '__main__': error('You must specify at least one target to scan!') errors = True + if single_target and len(args.targets) != 1: + error('You cannot provide more than one target when scanning in single-target mode.') + sys.exit(1) + targets = [] for target in args.targets: