Update autorecon.py

Added ability to force service scans.
This commit is contained in:
Tib3rius 2021-08-27 03:01:35 -04:00
parent d1863a1cd3
commit 98037302f7
1 changed files with 112 additions and 79 deletions

View File

@ -412,6 +412,7 @@ class AutoRecon(object):
'nmap', 'nmap',
'nmap_append', 'nmap_append',
'disable_sanity_checks', 'disable_sanity_checks',
'force_services',
'accessible', 'accessible',
'verbose' 'verbose'
] ]
@ -434,6 +435,7 @@ class AutoRecon(object):
'nmap': '-vv --reason -Pn', 'nmap': '-vv --reason -Pn',
'nmap_append': '', 'nmap_append': '',
'disable_sanity_checks': False, 'disable_sanity_checks': False,
'force_services': None,
'accessible': False, 'accessible': False,
'verbose': 0 'verbose': 0
} }
@ -787,36 +789,37 @@ async def service_scan(plugin, service):
from autorecon import PortScan from autorecon import PortScan
semaphore = service.target.autorecon.service_scan_semaphore semaphore = service.target.autorecon.service_scan_semaphore
# If service scan semaphore is locked, see if we can use port scan semaphore. if not service.target.autorecon.config['force_services']:
while True: # If service scan semaphore is locked, see if we can use port scan semaphore.
if semaphore.locked(): while True:
if semaphore != service.target.autorecon.port_scan_semaphore: # This will be true unless user sets max_scans == max_port_scans 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 port_scan_task_count = 0
for targ in service.target.autorecon.scanning_targets: for targ in service.target.autorecon.scanning_targets:
for process_list in targ.running_tasks.values(): for process_list in targ.running_tasks.values():
if issubclass(process_list['plugin'].__class__, PortScan): if issubclass(process_list['plugin'].__class__, PortScan):
port_scan_task_count += 1 port_scan_task_count += 1
if not service.target.autorecon.pending_targets and (service.target.autorecon.config['max_port_scans'] - port_scan_task_count) >= 1: # If no more targets, and we have room, use port scan semaphore. if not service.target.autorecon.pending_targets and (service.target.autorecon.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 (service.target.autorecon.config['max_port_scans'] - (port_scan_task_count + (len(service.target.autorecon.pending_targets) * service.target.autorecon.config['port_scan_plugin_count']))) >= 1:
if service.target.autorecon.port_scan_semaphore.locked(): if service.target.autorecon.port_scan_semaphore.locked():
await asyncio.sleep(1) await asyncio.sleep(1)
continue continue
semaphore = service.target.autorecon.port_scan_semaphore semaphore = service.target.autorecon.port_scan_semaphore
break break
else: else: # Do some math to see if we can use the port scan semaphore.
await asyncio.sleep(1) if (service.target.autorecon.config['max_port_scans'] - (port_scan_task_count + (len(service.target.autorecon.pending_targets) * service.target.autorecon.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: else:
break break
else:
break
async with semaphore: async with semaphore:
# Create variables for fformat references. # Create variables for fformat references.
@ -917,23 +920,41 @@ async def scan_target(target):
heartbeat = asyncio.create_task(start_heartbeat(target, period=target.autorecon.config['heartbeat'])) heartbeat = asyncio.create_task(start_heartbeat(target, period=target.autorecon.config['heartbeat']))
for plugin in target.autorecon.plugin_types['port']: services = []
plugin_tag_set = set(plugin.tags) if autorecon.config['force_services']:
forced_services = [x.strip().lower() for x in autorecon.config['force_services']]
matching_tags = False for forced_service in forced_services:
for tag_group in target.autorecon.tags: match = re.search('(?P<protocol>(tcp|udp))\/(?P<port>\d+)\/(?P<service>[\w\-\/]+)\/(?P<secure>secure|insecure)', forced_service)
if set(tag_group).issubset(plugin_tag_set): if match:
matching_tags = True protocol = match.group('protocol')
break port = int(match.group('port'))
service = match.group('service')
secure = True if match.group('secure') == 'secure' else False
service = Service(protocol, port, service, secure)
service.target = target
services.append(service)
excluded_tags = False if services:
for tag_group in target.autorecon.excluded_tags: pending.append(asyncio.create_task(asyncio.sleep(0)))
if set(tag_group).issubset(plugin_tag_set): else:
excluded_tags = True for plugin in target.autorecon.plugin_types['port']:
break plugin_tag_set = set(plugin.tags)
if matching_tags and not excluded_tags: matching_tags = False
pending.append(asyncio.create_task(port_scan(plugin, target))) 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.append(asyncio.create_task(port_scan(plugin, target)))
async with autorecon.lock: async with autorecon.lock:
autorecon.scanning_targets.append(target) autorecon.scanning_targets.append(target)
@ -953,23 +974,25 @@ async def scan_target(target):
timed_out = True timed_out = True
break break
# Extract Services if not autorecon.config['force_services']:
services = [] # Extract Services
async with target.lock: services = []
while target.pending_services:
services.append(target.pending_services.pop(0))
for task in done: async with target.lock:
try: while target.pending_services:
if task.exception(): services.append(target.pending_services.pop(0))
print(task.exception())
continue
except asyncio.InvalidStateError:
pass
if task.result()['type'] == 'port': for task in done:
for service in (task.result()['result'] or []): try:
services.append(service) if task.exception():
print(task.exception())
continue
except asyncio.InvalidStateError:
pass
if task.result()['type'] == 'port':
for service in (task.result()['result'] or []):
services.append(service)
for service in services: for service in services:
if service.full_tag() not in target.services: if service.full_tag() not in target.services:
@ -1150,6 +1173,7 @@ async def main():
nmap_group.add_argument('--nmap', action='store', help='Override the {nmap_extra} variable in scans. Default: %(default)s') 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') 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('--disable-sanity-checks', action='store_true', help='Disable sanity checks that would otherwise prevent the scans from running. Default: %(default)s')
parser.add_argument('--force-services', action='store', nargs='+', help='A space separated list of services in the following style: tcp/80/http/insecure tcp/443/https/secure')
parser.add_argument('--accessible', action='store_true', help='Attempts to make AutoRecon output more accessible to screenreaders. 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('-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.add_argument('--version', action='store_true', help='Prints the AutoRecon version and exits.')
@ -1161,7 +1185,7 @@ async def main():
autorecon.argparse = parser autorecon.argparse = parser
if args.version: if args.version:
print('AutoRecon v2.0-beta1') print('AutoRecon v2.0-beta2')
sys.exit(0) sys.exit(0)
# Parse config file and args for global.toml first. # Parse config file and args for global.toml first.
@ -1332,7 +1356,6 @@ async def main():
if key in autorecon.configurable_boolean_keys and autorecon.config[key]: if key in autorecon.configurable_boolean_keys and autorecon.config[key]:
continue continue
autorecon.config[key] = args_dict[key] autorecon.config[key] = args_dict[key]
autorecon.args = args autorecon.args = args
if autorecon.config['max_scans'] <= 0: if autorecon.config['max_scans'] <= 0:
@ -1367,12 +1390,15 @@ async def main():
errors = True errors = True
if not errors: if not errors:
autorecon.port_scan_semaphore = asyncio.Semaphore(autorecon.config['max_port_scans']) if autorecon.config['force_services']:
# If max scans and max port scans is the same, the service scan semaphore and port scan semaphore should be the same object autorecon.service_scan_semaphore = asyncio.Semaphore(autorecon.config['max_scans'])
if autorecon.config['max_scans'] == autorecon.config['max_port_scans']:
autorecon.service_scan_semaphore = autorecon.port_scan_semaphore
else: else:
autorecon.service_scan_semaphore = asyncio.Semaphore(autorecon.config['max_scans'] - autorecon.config['max_port_scans']) autorecon.port_scan_semaphore = asyncio.Semaphore(autorecon.config['max_port_scans'])
# If max scans and max port scans is the same, the service scan semaphore and port scan semaphore should be the same object
if autorecon.config['max_scans'] == autorecon.config['max_port_scans']:
autorecon.service_scan_semaphore = autorecon.port_scan_semaphore
else:
autorecon.service_scan_semaphore = asyncio.Semaphore(autorecon.config['max_scans'] - autorecon.config['max_port_scans'])
tags = [] tags = []
for tag_group in list(set(filter(None, args.tags.lower().split(',')))): for tag_group in list(set(filter(None, args.tags.lower().split(',')))):
@ -1450,26 +1476,29 @@ async def main():
error('A total of ' + str(len(autorecon.pending_targets)) + ' targets would be scanned. If this is correct, re-run with the --disable-sanity-checks option to suppress this check.') error('A total of ' + str(len(autorecon.pending_targets)) + ' targets would be scanned. If this is correct, re-run with the --disable-sanity-checks option to suppress this check.')
errors = True errors = True
port_scan_plugin_count = 0 if not autorecon.config['force_services']:
for plugin in autorecon.plugin_types['port']: port_scan_plugin_count = 0
matching_tags = False for plugin in autorecon.plugin_types['port']:
for tag_group in autorecon.tags: matching_tags = False
if set(tag_group).issubset(set(plugin.tags)): for tag_group in autorecon.tags:
matching_tags = True if set(tag_group).issubset(set(plugin.tags)):
break matching_tags = True
break
excluded_tags = False excluded_tags = False
for tag_group in autorecon.excluded_tags: for tag_group in autorecon.excluded_tags:
if set(tag_group).issubset(set(plugin.tags)): if set(tag_group).issubset(set(plugin.tags)):
excluded_tags = True excluded_tags = True
break break
if matching_tags and not excluded_tags: if matching_tags and not excluded_tags:
port_scan_plugin_count += 1 port_scan_plugin_count += 1
if port_scan_plugin_count == 0: if port_scan_plugin_count == 0:
error('There are no port scan plugins that match the tags specified.') error('There are no port scan plugins that match the tags specified.')
errors = True errors = True
else:
port_scan_plugin_count = autorecon.config['max_port_scans'] / 5
if errors: if errors:
sys.exit(1) sys.exit(1)
@ -1487,13 +1516,13 @@ async def main():
i+=1 i+=1
if i >= num_initial_targets: if i >= num_initial_targets:
break break
# This makes it possible to capture keypresses without <enter> and without displaying them. # This makes it possible to capture keypresses without <enter> and without displaying them.
tty.setcbreak(sys.stdin.fileno()) tty.setcbreak(sys.stdin.fileno())
verbosity_monitor = keyboard.Listener(on_press=change_verbosity) verbosity_monitor = keyboard.Listener(on_press=change_verbosity)
verbosity_monitor.start() verbosity_monitor.start()
timed_out = False timed_out = False
while pending: while pending:
done, pending = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED, timeout=1) done, pending = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED, timeout=1)
@ -1514,8 +1543,12 @@ async def main():
port_scan_task_count = 0 port_scan_task_count = 0
for targ in autorecon.scanning_targets: for targ in autorecon.scanning_targets:
for process_list in targ.running_tasks.values(): for process_list in targ.running_tasks.values():
if issubclass(process_list['plugin'].__class__, PortScan): if autorecon.config['force_services']:
port_scan_task_count += 1 if issubclass(process_list['plugin'].__class__, ServiceScan):
port_scan_task_count += 1
else:
if issubclass(process_list['plugin'].__class__, PortScan):
port_scan_task_count += 1
num_new_targets = math.ceil((autorecon.config['max_port_scans'] - port_scan_task_count) / port_scan_plugin_count) num_new_targets = math.ceil((autorecon.config['max_port_scans'] - port_scan_task_count) / port_scan_plugin_count)
if num_new_targets > 0: if num_new_targets > 0: