Added max plugin instance control.
-mpti / --max-plugin-target-instances can be used to control the number of instances of a plugin that can run concurrently per target. -mpgi / --max-plugin-global-instances can be used to control the number of instances of a plugin that can run concurrently (globally).
This commit is contained in:
parent
f8d89966da
commit
906493da39
|
|
@ -26,6 +26,8 @@ configurable_keys = [
|
||||||
'disable_sanity_checks',
|
'disable_sanity_checks',
|
||||||
'disable_keyboard_control',
|
'disable_keyboard_control',
|
||||||
'force_services',
|
'force_services',
|
||||||
|
'max_plugin_target_instances',
|
||||||
|
'max_plugin_global_instances',
|
||||||
'accessible',
|
'accessible',
|
||||||
'verbose'
|
'verbose'
|
||||||
]
|
]
|
||||||
|
|
@ -66,6 +68,8 @@ config = {
|
||||||
'disable_sanity_checks': False,
|
'disable_sanity_checks': False,
|
||||||
'disable_keyboard_control': False,
|
'disable_keyboard_control': False,
|
||||||
'force_services': None,
|
'force_services': None,
|
||||||
|
'max_plugin_target_instances': None,
|
||||||
|
'max_plugin_global_instances': None,
|
||||||
'accessible': False,
|
'accessible': False,
|
||||||
'verbose': 0
|
'verbose': 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -270,6 +270,42 @@ async def service_scan(plugin, service):
|
||||||
if not config['force_services']:
|
if not config['force_services']:
|
||||||
semaphore = await get_semaphore(service.target.autorecon)
|
semaphore = await get_semaphore(service.target.autorecon)
|
||||||
|
|
||||||
|
plugin_pending = True
|
||||||
|
|
||||||
|
while plugin_pending:
|
||||||
|
global_plugin_count = 0
|
||||||
|
target_plugin_count = 0
|
||||||
|
|
||||||
|
if plugin.max_global_instances and plugin.max_global_instances > 0:
|
||||||
|
async with service.target.autorecon.lock:
|
||||||
|
# Count currently running plugin instances.
|
||||||
|
for target in service.target.autorecon.scanning_targets:
|
||||||
|
for task in target.running_tasks.values():
|
||||||
|
if plugin == task['plugin']:
|
||||||
|
global_plugin_count += 1
|
||||||
|
if global_plugin_count >= plugin.max_global_instances:
|
||||||
|
break
|
||||||
|
if global_plugin_count >= plugin.max_global_instances:
|
||||||
|
break
|
||||||
|
if global_plugin_count >= plugin.max_global_instances:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if plugin.max_target_instances and plugin.max_target_instances > 0:
|
||||||
|
async with service.target.lock:
|
||||||
|
# Count currently running plugin instances.
|
||||||
|
for task in service.target.running_tasks.values():
|
||||||
|
if plugin == task['plugin']:
|
||||||
|
target_plugin_count += 1
|
||||||
|
if target_plugin_count >= plugin.max_target_instances:
|
||||||
|
break
|
||||||
|
if target_plugin_count >= plugin.max_target_instances:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If we get here, we can run the plugin.
|
||||||
|
plugin_pending = False
|
||||||
|
|
||||||
async with semaphore:
|
async with semaphore:
|
||||||
# Create variables for fformat references.
|
# Create variables for fformat references.
|
||||||
address = service.target.address
|
address = service.target.address
|
||||||
|
|
@ -803,6 +839,8 @@ async def run():
|
||||||
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('--disable-keyboard-control', action='store_true', help='Disables keyboard control ([s]tatus, Up, Down) if you are in SSH or Docker.')
|
parser.add_argument('--disable-keyboard-control', action='store_true', help='Disables keyboard control ([s]tatus, Up, Down) if you are in SSH or Docker.')
|
||||||
parser.add_argument('--force-services', action='store', nargs='+', metavar='SERVICE', help='A space separated list of services in the following style: tcp/80/http tcp/443/https/secure')
|
parser.add_argument('--force-services', action='store', nargs='+', metavar='SERVICE', help='A space separated list of services in the following style: tcp/80/http tcp/443/https/secure')
|
||||||
|
parser.add_argument('-mpti', '--max-plugin-target-instances', action='store', nargs='+', metavar='PLUGIN:NUMBER', help='A space separated list of plugin slugs with the max number of instances (per target) in the following style: nmap-http:2 dirbuster:1. Default: %(default)s')
|
||||||
|
parser.add_argument('-mpgi', '--max-plugin-global-instances', action='store', nargs='+', metavar='PLUGIN:NUMBER', help='A space separated list of plugin slugs with the max number of global instances in the following style: nmap-http:2 dirbuster:1. 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('--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.')
|
||||||
|
|
@ -1049,13 +1087,51 @@ async def run():
|
||||||
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']:
|
if type in ['plugin', 'plugins', 'report', 'reports', 'reporting']:
|
||||||
for p in autorecon.plugin_types['report']:
|
for p in autorecon.plugin_types['report']:
|
||||||
print('Report: ' + p.name + ' (' + p.slug + ')' + (' - ' + p.description if p.description else ''))
|
print('Report: ' + p.name + ' (' + p.slug + ')' + (' - ' + p.description if p.description else ''))
|
||||||
|
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
max_plugin_target_instances = {}
|
||||||
|
if config['max_plugin_target_instances']:
|
||||||
|
for plugin_instance in config['max_plugin_target_instances']:
|
||||||
|
plugin_instance = plugin_instance.split(':', 1)
|
||||||
|
if len(plugin_instance) == 2:
|
||||||
|
if plugin_instance[0] not in autorecon.plugins:
|
||||||
|
error('Invalid plugin slug (' + plugin_instance[0] + ':' + plugin_instance[1] + ') provided to --max-plugin-target-instances.')
|
||||||
|
errors = True
|
||||||
|
elif not plugin_instance[1].isdigit() or int(plugin_instance[1]) == 0:
|
||||||
|
error('Invalid number of instances (' + plugin_instance[0] + ':' + plugin_instance[1] + ') provided to --max-plugin-target-instances. Must be a non-zero positive integer.')
|
||||||
|
errors = True
|
||||||
|
else:
|
||||||
|
max_plugin_target_instances[plugin_instance[0]] = int(plugin_instance[1])
|
||||||
|
else:
|
||||||
|
error('Invalid value provided to --max-plugin-target-instances. Values must be in the format PLUGIN:NUMBER.')
|
||||||
|
|
||||||
|
max_plugin_global_instances = {}
|
||||||
|
if config['max_plugin_global_instances']:
|
||||||
|
for plugin_instance in config['max_plugin_global_instances']:
|
||||||
|
plugin_instance = plugin_instance.split(':', 1)
|
||||||
|
if len(plugin_instance) == 2:
|
||||||
|
if plugin_instance[0] not in autorecon.plugins:
|
||||||
|
error('Invalid plugin slug (' + plugin_instance[0] + ':' + plugin_instance[1] + ') provided to --max-plugin-global-instances.')
|
||||||
|
errors = True
|
||||||
|
elif not plugin_instance[1].isdigit() or int(plugin_instance[1]) == 0:
|
||||||
|
error('Invalid number of instances (' + plugin_instance[0] + ':' + plugin_instance[1] + ') provided to --max-plugin-global-instances. Must be a non-zero positive integer.')
|
||||||
|
errors = True
|
||||||
|
else:
|
||||||
|
max_plugin_global_instances[plugin_instance[0]] = int(plugin_instance[1])
|
||||||
|
else:
|
||||||
|
error('Invalid value provided to --max-plugin-global-instances. Values must be in the format PLUGIN:NUMBER.')
|
||||||
|
|
||||||
for plugin in autorecon.plugins.values():
|
for plugin in autorecon.plugins.values():
|
||||||
|
if hasattr(plugin, 'max_target_instances') and plugin.slug in max_plugin_target_instances:
|
||||||
|
plugin.max_target_instances = max_plugin_target_instances[plugin.slug]
|
||||||
|
|
||||||
|
if hasattr(plugin, 'max_global_instances') and plugin.slug in max_plugin_global_instances:
|
||||||
|
plugin.max_global_instances = max_plugin_global_instances[plugin.slug]
|
||||||
|
|
||||||
for member_name, _ in inspect.getmembers(plugin, predicate=inspect.ismethod):
|
for member_name, _ in inspect.getmembers(plugin, predicate=inspect.ismethod):
|
||||||
if member_name == 'check':
|
if member_name == 'check':
|
||||||
plugin.check()
|
plugin.check()
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,8 @@ class ServiceScan(Plugin):
|
||||||
self.ignore_service_names = []
|
self.ignore_service_names = []
|
||||||
self.run_once_boolean = False
|
self.run_once_boolean = False
|
||||||
self.require_ssl_boolean = False
|
self.require_ssl_boolean = False
|
||||||
|
self.max_target_instances = 0
|
||||||
|
self.max_global_instances = 0
|
||||||
|
|
||||||
@final
|
@final
|
||||||
def match_service(self, protocol, port, name, negative_match=False):
|
def match_service(self, protocol, port, name, negative_match=False):
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue