initial version of dependent plugins
This commit is contained in:
parent
3b553e1933
commit
52ba61e6eb
|
@ -0,0 +1,40 @@
|
|||
import time
|
||||
|
||||
|
||||
def calculate_elapsed_time(start_time, short=False):
|
||||
elapsed_seconds = round(time.time() - start_time)
|
||||
|
||||
m, s = divmod(elapsed_seconds, 60)
|
||||
h, m = divmod(m, 60)
|
||||
|
||||
elapsed_time = []
|
||||
if short:
|
||||
elapsed_time.append(str(h).zfill(2))
|
||||
else:
|
||||
if h == 1:
|
||||
elapsed_time.append(str(h) + ' hour')
|
||||
elif h > 1:
|
||||
elapsed_time.append(str(h) + ' hours')
|
||||
|
||||
if short:
|
||||
elapsed_time.append(str(m).zfill(2))
|
||||
else:
|
||||
if m == 1:
|
||||
elapsed_time.append(str(m) + ' minute')
|
||||
elif m > 1:
|
||||
elapsed_time.append(str(m) + ' minutes')
|
||||
|
||||
if short:
|
||||
elapsed_time.append(str(s).zfill(2))
|
||||
else:
|
||||
if s == 1:
|
||||
elapsed_time.append(str(s) + ' second')
|
||||
elif s > 1:
|
||||
elapsed_time.append(str(s) + ' seconds')
|
||||
else:
|
||||
elapsed_time.append('less than a second')
|
||||
|
||||
if short:
|
||||
return ':'.join(elapsed_time)
|
||||
else:
|
||||
return ', '.join(elapsed_time)
|
|
@ -97,11 +97,12 @@ def fail(*args, sep=' ', end='\n', file=sys.stderr, **kvargs):
|
|||
|
||||
class CommandStreamReader(object):
|
||||
|
||||
def __init__(self, stream, target, tag, patterns=None, outfile=None):
|
||||
def __init__(self, stream, target, tag, patterns=None, outfile=None, plugin=None):
|
||||
self.stream = stream
|
||||
self.target = target
|
||||
self.tag = tag
|
||||
self.lines = []
|
||||
self.plugin = plugin
|
||||
self.patterns = patterns or []
|
||||
self.outfile = outfile
|
||||
self.ended = False
|
||||
|
@ -136,6 +137,22 @@ class CommandStreamReader(object):
|
|||
else:
|
||||
info('{bright}[{yellow}' + self.target.address + '{crst}/{bgreen}' + self.tag + '{crst}]{rst} {bmagenta}Matched Pattern: ' + match + '{rst}', verbosity=2)
|
||||
file.writelines('Matched Pattern: ' + match + '\n\n')
|
||||
debug(str(self.plugin.__dict__))
|
||||
next_plugins = self.target.autorecon.get_next_service_scan_plugins(self.plugin)
|
||||
info(str(next_plugins))
|
||||
for next_plugin in next_plugins:
|
||||
info("Dict: %s" % str(self.target.__dict__))
|
||||
for service, details in self.target.scans.get('services', {}).items():
|
||||
for key, value in details.items():
|
||||
if value.get('plugin') == self.plugin:
|
||||
info("Value: %s" % str(value))
|
||||
# info("Value: %s" % str(value))
|
||||
#info("Service Details: %s" % str(details))
|
||||
#new_service = Service()
|
||||
self.target.autorecon.queue_new_service_scan(next_plugin, service)
|
||||
#for next_plugin in next_plugins:
|
||||
# async def service_scan(plugin, service, run_from_service_scan=False):
|
||||
#autorecon_queue_service_scan(next_plugin, run_fr)
|
||||
|
||||
if self.outfile is not None:
|
||||
with open(self.outfile, 'a') as writer:
|
||||
|
|
|
@ -13,8 +13,9 @@ except ModuleNotFoundError:
|
|||
colorama.init()
|
||||
|
||||
from autorecon.config import config, configurable_keys, configurable_boolean_keys
|
||||
from autorecon.helper.scan import calculate_elapsed_time
|
||||
from autorecon.io import slugify, e, fformat, cprint, debug, info, warn, error, fail, CommandStreamReader
|
||||
from autorecon.plugins import Pattern, PortScan, ServiceScan, Report, AutoRecon
|
||||
from autorecon.plugins import Pattern, PortScan, ServiceScan, Report, AutoRecon, service_scan, get_semaphore
|
||||
from autorecon.targets import Target, Service
|
||||
|
||||
VERSION = "2.0.5"
|
||||
|
@ -41,43 +42,6 @@ terminal_settings = termios.tcgetattr(sys.stdin.fileno())
|
|||
|
||||
autorecon = AutoRecon()
|
||||
|
||||
def calculate_elapsed_time(start_time, short=False):
|
||||
elapsed_seconds = round(time.time() - start_time)
|
||||
|
||||
m, s = divmod(elapsed_seconds, 60)
|
||||
h, m = divmod(m, 60)
|
||||
|
||||
elapsed_time = []
|
||||
if short:
|
||||
elapsed_time.append(str(h).zfill(2))
|
||||
else:
|
||||
if h == 1:
|
||||
elapsed_time.append(str(h) + ' hour')
|
||||
elif h > 1:
|
||||
elapsed_time.append(str(h) + ' hours')
|
||||
|
||||
if short:
|
||||
elapsed_time.append(str(m).zfill(2))
|
||||
else:
|
||||
if m == 1:
|
||||
elapsed_time.append(str(m) + ' minute')
|
||||
elif m > 1:
|
||||
elapsed_time.append(str(m) + ' minutes')
|
||||
|
||||
if short:
|
||||
elapsed_time.append(str(s).zfill(2))
|
||||
else:
|
||||
if s == 1:
|
||||
elapsed_time.append(str(s) + ' second')
|
||||
elif s > 1:
|
||||
elapsed_time.append(str(s) + ' seconds')
|
||||
else:
|
||||
elapsed_time.append('less than a second')
|
||||
|
||||
if short:
|
||||
return ':'.join(elapsed_time)
|
||||
else:
|
||||
return ', '.join(elapsed_time)
|
||||
|
||||
def cancel_all_tasks(signal, frame):
|
||||
for task in asyncio.all_tasks():
|
||||
|
@ -162,40 +126,6 @@ async def keyboard():
|
|||
input = input[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):
|
||||
if config['ports']:
|
||||
if config['ports']['tcp'] or config['ports']['udp']:
|
||||
|
@ -261,93 +191,6 @@ async def port_scan(plugin, target):
|
|||
info('Port scan {bblue}' + plugin.name + ' {green}(' + plugin.slug + '){rst} against {byellow}' + target.address + '{rst} finished in ' + elapsed_time, verbosity=2)
|
||||
return {'type':'port', 'plugin':plugin, 'result':result}
|
||||
|
||||
async def service_scan(plugin, service):
|
||||
semaphore = service.target.autorecon.service_scan_semaphore
|
||||
|
||||
if not config['force_services']:
|
||||
semaphore = await get_semaphore(service.target.autorecon)
|
||||
|
||||
async with semaphore:
|
||||
# Create variables for fformat references.
|
||||
address = service.target.address
|
||||
addressv6 = service.target.address
|
||||
ipaddress = service.target.ip
|
||||
ipaddressv6 = service.target.ip
|
||||
scandir = service.target.scandir
|
||||
protocol = service.protocol
|
||||
port = service.port
|
||||
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.
|
||||
http_scheme = 'https' if 'https' in service.name or service.secure is True else 'http'
|
||||
|
||||
nmap_extra = service.target.autorecon.args.nmap
|
||||
if service.target.autorecon.args.nmap_append:
|
||||
nmap_extra += ' ' + service.target.autorecon.args.nmap_append
|
||||
|
||||
if protocol == 'udp':
|
||||
nmap_extra += ' -sU'
|
||||
|
||||
if service.target.ipversion == 'IPv6':
|
||||
nmap_extra += ' -6'
|
||||
if addressv6 == service.target.ip:
|
||||
addressv6 = '[' + addressv6 + ']'
|
||||
ipaddressv6 = '[' + ipaddressv6 + ']'
|
||||
|
||||
if config['proxychains'] and protocol == 'tcp':
|
||||
nmap_extra += ' -sT'
|
||||
|
||||
tag = service.tag() + '/' + plugin.slug
|
||||
|
||||
info('Service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} running against {byellow}' + service.target.address + '{rst}', verbosity=1)
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
async with service.target.lock:
|
||||
service.target.running_tasks[tag] = {'plugin': plugin, 'processes': [], 'start': start_time}
|
||||
|
||||
try:
|
||||
result = await plugin.run(service)
|
||||
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: Service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} running against {byellow}' + service.target.address + '{rst} produced an exception:\n\n' + error_text, color=Fore.RED, char='!', printmsg=False))
|
||||
|
||||
for process_dict in service.target.running_tasks[tag]['processes']:
|
||||
if process_dict['process'].returncode is None:
|
||||
warn('A process was left running after service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} against {byellow}' + service.target.address + '{rst} finished. Please ensure non-blocking processes are awaited before the run coroutine finishes. Awaiting now.', verbosity=2)
|
||||
await process_dict['process'].wait()
|
||||
|
||||
if process_dict['process'].returncode != 0:
|
||||
errors = []
|
||||
while True:
|
||||
line = await process_dict['stderr'].readline()
|
||||
if line is not None:
|
||||
errors.append(line + '\n')
|
||||
else:
|
||||
break
|
||||
error('Service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} ran a command against {byellow}' + service.target.address + '{rst} which returned a non-zero exit code (' + str(process_dict['process'].returncode) + '). Check ' + service.target.scandir + '/_errors.log for more details.', verbosity=2)
|
||||
async with service.target.lock:
|
||||
with open(os.path.join(service.target.scandir, '_errors.log'), 'a') as file:
|
||||
file.writelines('[*] Service scan ' + plugin.name + ' (' + tag + ') ran a command which returned a non-zero exit code (' + str(process_dict['process'].returncode) + ').\n')
|
||||
file.writelines('[-] Command: ' + process_dict['cmd'] + '\n')
|
||||
if errors:
|
||||
file.writelines(['[-] Error Output:\n'] + errors + ['\n'])
|
||||
else:
|
||||
file.writelines('\n')
|
||||
|
||||
elapsed_time = calculate_elapsed_time(start_time)
|
||||
|
||||
async with service.target.lock:
|
||||
service.target.running_tasks.pop(tag, None)
|
||||
|
||||
info('Service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} against {byellow}' + service.target.address + '{rst} finished in ' + elapsed_time, verbosity=2)
|
||||
return {'type':'service', 'plugin':plugin, 'result':result}
|
||||
|
||||
async def generate_report(plugin, targets):
|
||||
semaphore = autorecon.service_scan_semaphore
|
||||
|
@ -400,7 +243,8 @@ async def scan_target(target):
|
|||
|
||||
target.reportdir = reportdir
|
||||
|
||||
pending = []
|
||||
# pending = []
|
||||
autorecon = target.autorecon
|
||||
|
||||
heartbeat = asyncio.create_task(start_heartbeat(target, period=config['heartbeat']))
|
||||
|
||||
|
@ -423,7 +267,7 @@ async def scan_target(target):
|
|||
services.append(service)
|
||||
|
||||
if services:
|
||||
pending.append(asyncio.create_task(asyncio.sleep(0)))
|
||||
autorecon.pending.append(asyncio.create_task(asyncio.sleep(0)))
|
||||
else:
|
||||
error('No services were defined. Please check your service syntax: [tcp|udp]/<port>/<service-name>/[secure|insecure]')
|
||||
heartbeat.cancel()
|
||||
|
@ -454,7 +298,7 @@ async def scan_target(target):
|
|||
|
||||
if matching_tags and not excluded_tags:
|
||||
target.scans['ports'][plugin.slug] = {'plugin':plugin, 'commands':[]}
|
||||
pending.append(asyncio.create_task(port_scan(plugin, target)))
|
||||
autorecon.pending.append(asyncio.create_task(port_scan(plugin, target)))
|
||||
|
||||
async with autorecon.lock:
|
||||
autorecon.scanning_targets.append(target)
|
||||
|
@ -463,8 +307,9 @@ async def scan_target(target):
|
|||
info('Scanning target {byellow}' + target.address + '{rst}')
|
||||
|
||||
timed_out = False
|
||||
while pending:
|
||||
done, pending = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED, timeout=1)
|
||||
while autorecon.pending:
|
||||
done, autorecon.pending = await asyncio.wait(autorecon.pending, return_when=asyncio.FIRST_COMPLETED, timeout=1)
|
||||
autorecon.pending = list(autorecon.pending)
|
||||
|
||||
# Check if global timeout has occurred.
|
||||
if config['target_timeout'] is not None:
|
||||
|
@ -690,7 +535,7 @@ async def scan_target(target):
|
|||
target.scans['services'][service] = {}
|
||||
target.scans['services'][service][plugin_tag] = {'plugin':plugin, 'commands':[]}
|
||||
|
||||
pending.add(asyncio.create_task(service_scan(plugin, service)))
|
||||
autorecon.pending.append(asyncio.create_task(service_scan(plugin, service)))
|
||||
|
||||
if not service_match:
|
||||
warn('{byellow}[' + target.address + ']{srst} Service ' + service.full_tag() + ' did not match any plugins based on the service name.{rst}', verbosity=2)
|
||||
|
@ -717,17 +562,17 @@ async def scan_target(target):
|
|||
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)
|
||||
autorecon.pending.append(asyncio.create_task(generate_report(plugin, [target])))
|
||||
|
||||
while autorecon.pending:
|
||||
done, autorecon.pending = await asyncio.wait(autorecon.pending, return_when=asyncio.FIRST_COMPLETED, timeout=1)
|
||||
autorecon.pending = list(autorecon.pending)
|
||||
heartbeat.cancel()
|
||||
elapsed_time = calculate_elapsed_time(start_time)
|
||||
|
||||
if timed_out:
|
||||
|
||||
for task in pending:
|
||||
for task in autorecon.pending:
|
||||
task.cancel()
|
||||
|
||||
for process_list in target.running_tasks.values():
|
||||
|
|
|
@ -1,355 +1,563 @@
|
|||
import asyncio, inspect, os, re, sys
|
||||
import asyncio, inspect, re
|
||||
from typing import final
|
||||
from autorecon.config import config
|
||||
from autorecon.io import slugify, error, fail, CommandStreamReader
|
||||
from autorecon.io import slugify, fail, CommandStreamReader
|
||||
from autorecon.targets import Service
|
||||
from autorecon.config import config
|
||||
from autorecon.io import info, warn, error, cprint
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
from colorama import Fore
|
||||
from autorecon.helper.scan import calculate_elapsed_time
|
||||
|
||||
|
||||
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():
|
||||
info(str(process_list['plugin'].__dict__))
|
||||
info(type(process_list['plugin']))
|
||||
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 service_scan(plugin, service, run_from_service_scan=False):
|
||||
# skip running service scan plugins that are meant to be run for specific services
|
||||
if plugin.has_previous_plugins() and not run_from_service_scan:
|
||||
return
|
||||
|
||||
semaphore = service.target.autorecon.service_scan_semaphore
|
||||
|
||||
if not config['force_services']:
|
||||
semaphore = await get_semaphore(service.target.autorecon)
|
||||
|
||||
async with semaphore:
|
||||
# Create variables for fformat references.
|
||||
address = service.target.address
|
||||
addressv6 = service.target.address
|
||||
ipaddress = service.target.ip
|
||||
ipaddressv6 = service.target.ip
|
||||
scandir = service.target.scandir
|
||||
protocol = service.protocol
|
||||
port = service.port
|
||||
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.
|
||||
http_scheme = 'https' if 'https' in service.name or service.secure is True else 'http'
|
||||
|
||||
nmap_extra = service.target.autorecon.args.nmap
|
||||
if service.target.autorecon.args.nmap_append:
|
||||
nmap_extra += ' ' + service.target.autorecon.args.nmap_append
|
||||
|
||||
if protocol == 'udp':
|
||||
nmap_extra += ' -sU'
|
||||
|
||||
if service.target.ipversion == 'IPv6':
|
||||
nmap_extra += ' -6'
|
||||
if addressv6 == service.target.ip:
|
||||
addressv6 = '[' + addressv6 + ']'
|
||||
ipaddressv6 = '[' + ipaddressv6 + ']'
|
||||
|
||||
if config['proxychains'] and protocol == 'tcp':
|
||||
nmap_extra += ' -sT'
|
||||
|
||||
tag = service.tag() + '/' + plugin.slug
|
||||
|
||||
info(
|
||||
'Service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} running against {byellow}' + service.target.address + '{rst}',
|
||||
verbosity=1)
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
async with service.target.lock:
|
||||
service.target.running_tasks[tag] = {'plugin': plugin, 'processes': [], 'start': start_time}
|
||||
|
||||
try:
|
||||
result = await plugin.run(service)
|
||||
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: Service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} running against {byellow}' + service.target.address + '{rst} produced an exception:\n\n' + error_text,
|
||||
color=Fore.RED, char='!', printmsg=False))
|
||||
|
||||
for process_dict in service.target.running_tasks[tag]['processes']:
|
||||
if process_dict['process'].returncode is None:
|
||||
warn(
|
||||
'A process was left running after service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} against {byellow}' + service.target.address + '{rst} finished. Please ensure non-blocking processes are awaited before the run coroutine finishes. Awaiting now.',
|
||||
verbosity=2)
|
||||
await process_dict['process'].wait()
|
||||
|
||||
if process_dict['process'].returncode != 0:
|
||||
errors = []
|
||||
while True:
|
||||
line = await process_dict['stderr'].readline()
|
||||
if line is not None:
|
||||
errors.append(line + '\n')
|
||||
else:
|
||||
break
|
||||
error(
|
||||
'Service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} ran a command against {byellow}' + service.target.address + '{rst} which returned a non-zero exit code (' + str(
|
||||
process_dict[
|
||||
'process'].returncode) + '). Check ' + service.target.scandir + '/_errors.log for more details.',
|
||||
verbosity=2)
|
||||
async with service.target.lock:
|
||||
with open(os.path.join(service.target.scandir, '_errors.log'), 'a') as file:
|
||||
file.writelines(
|
||||
'[*] Service scan ' + plugin.name + ' (' + tag + ') ran a command which returned a non-zero exit code (' + str(
|
||||
process_dict['process'].returncode) + ').\n')
|
||||
file.writelines('[-] Command: ' + process_dict['cmd'] + '\n')
|
||||
if errors:
|
||||
file.writelines(['[-] Error Output:\n'] + errors + ['\n'])
|
||||
else:
|
||||
file.writelines('\n')
|
||||
|
||||
elapsed_time = calculate_elapsed_time(start_time)
|
||||
|
||||
async with service.target.lock:
|
||||
service.target.running_tasks.pop(tag, None)
|
||||
|
||||
info(
|
||||
'Service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} against {byellow}' + service.target.address + '{rst} finished in ' + elapsed_time,
|
||||
verbosity=2)
|
||||
return {'type': 'service', 'plugin': plugin, 'result': result}
|
||||
|
||||
|
||||
class Pattern:
|
||||
def __init__(self, pattern, description=None, plugins=None):
|
||||
self.pattern = pattern
|
||||
self.description = description
|
||||
if not plugins:
|
||||
self.plugins = []
|
||||
else:
|
||||
self.plugins = plugins
|
||||
|
||||
def __init__(self, pattern, description=None):
|
||||
self.pattern = pattern
|
||||
self.description = description
|
||||
|
||||
class Plugin(object):
|
||||
|
||||
def __init__(self):
|
||||
self.name = None
|
||||
self.slug = None
|
||||
self.description = None
|
||||
self.tags = ['default']
|
||||
self.priority = 1
|
||||
self.patterns = []
|
||||
self.autorecon = None
|
||||
self.disabled = False
|
||||
def __init__(self):
|
||||
self.name = None
|
||||
self.slug = None
|
||||
self.description = None
|
||||
self.tags = ['default']
|
||||
self.priority = 1
|
||||
self.patterns = []
|
||||
self.autorecon = None
|
||||
self.disabled = False
|
||||
|
||||
@final
|
||||
def add_option(self, name, default=None, help=None):
|
||||
self.autorecon.add_argument(self, name, metavar='VALUE', default=default, help=help)
|
||||
@final
|
||||
def add_option(self, name, default=None, help=None):
|
||||
self.autorecon.add_argument(self, name, metavar='VALUE', default=default, help=help)
|
||||
|
||||
@final
|
||||
def add_constant_option(self, name, const, default=None, help=None):
|
||||
self.autorecon.add_argument(self, name, action='store_const', const=const, default=default, help=help)
|
||||
@final
|
||||
def add_constant_option(self, name, const, default=None, help=None):
|
||||
self.autorecon.add_argument(self, name, action='store_const', const=const, default=default, help=help)
|
||||
|
||||
@final
|
||||
def add_true_option(self, name, help=None):
|
||||
self.autorecon.add_argument(self, name, action='store_true', help=help)
|
||||
@final
|
||||
def add_true_option(self, name, help=None):
|
||||
self.autorecon.add_argument(self, name, action='store_true', help=help)
|
||||
|
||||
@final
|
||||
def add_false_option(self, name, help=None):
|
||||
self.autorecon.add_argument(self, name, action='store_false', help=help)
|
||||
@final
|
||||
def add_false_option(self, name, help=None):
|
||||
self.autorecon.add_argument(self, name, action='store_false', help=help)
|
||||
|
||||
@final
|
||||
def add_list_option(self, name, default=None, help=None):
|
||||
self.autorecon.add_argument(self, name, nargs='+', metavar='VALUE', default=default, help=help)
|
||||
@final
|
||||
def add_list_option(self, name, default=None, help=None):
|
||||
self.autorecon.add_argument(self, name, nargs='+', metavar='VALUE', default=default, help=help)
|
||||
|
||||
@final
|
||||
def add_choice_option(self, name, choices, default=None, help=None):
|
||||
if not isinstance(choices, list):
|
||||
fail('The choices argument for ' + self.name + '\'s ' + name + ' choice option should be a list.')
|
||||
self.autorecon.add_argument(self, name, choices=choices, default=default, help=help)
|
||||
@final
|
||||
def add_choice_option(self, name, choices, default=None, help=None):
|
||||
if not isinstance(choices, list):
|
||||
fail('The choices argument for ' + self.name + '\'s ' + name + ' choice option should be a list.')
|
||||
self.autorecon.add_argument(self, name, choices=choices, default=default, help=help)
|
||||
|
||||
@final
|
||||
def get_option(self, name):
|
||||
# TODO: make sure name is simple.
|
||||
name = self.slug.replace('-', '_') + '.' + slugify(name).replace('-', '_')
|
||||
@final
|
||||
def get_option(self, name):
|
||||
# TODO: make sure name is simple.
|
||||
name = self.slug.replace('-', '_') + '.' + slugify(name).replace('-', '_')
|
||||
|
||||
if name in vars(self.autorecon.args):
|
||||
return vars(self.autorecon.args)[name]
|
||||
else:
|
||||
return None
|
||||
if name in vars(self.autorecon.args):
|
||||
return vars(self.autorecon.args)[name]
|
||||
else:
|
||||
return None
|
||||
|
||||
@final
|
||||
def get_global_option(self, name, default=None):
|
||||
name = 'global.' + slugify(name).replace('-', '_')
|
||||
@final
|
||||
def get_global_option(self, name, default=None):
|
||||
name = 'global.' + slugify(name).replace('-', '_')
|
||||
|
||||
if name in vars(self.autorecon.args):
|
||||
if vars(self.autorecon.args)[name] is None:
|
||||
if default:
|
||||
return default
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
return vars(self.autorecon.args)[name]
|
||||
else:
|
||||
if default:
|
||||
return default
|
||||
return None
|
||||
if name in vars(self.autorecon.args):
|
||||
if vars(self.autorecon.args)[name] is None:
|
||||
if default:
|
||||
return default
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
return vars(self.autorecon.args)[name]
|
||||
else:
|
||||
if default:
|
||||
return default
|
||||
return None
|
||||
|
||||
@final
|
||||
def get_global(self, name, default=None):
|
||||
return self.get_global_option(name, default)
|
||||
@final
|
||||
def get_global(self, name, default=None):
|
||||
return self.get_global_option(name, default)
|
||||
|
||||
@final
|
||||
def add_pattern(self, pattern, description=None, plugins=None):
|
||||
try:
|
||||
compiled = re.compile(pattern)
|
||||
if description:
|
||||
self.patterns.append(Pattern(compiled, description=description, plugins=plugins))
|
||||
else:
|
||||
self.patterns.append(Pattern(compiled, plugins=plugins))
|
||||
except re.error:
|
||||
fail('Error: The pattern "' + pattern + '" in the plugin "' + self.name + '" is invalid regex.')
|
||||
|
||||
@final
|
||||
def add_pattern(self, pattern, description=None):
|
||||
try:
|
||||
compiled = re.compile(pattern)
|
||||
if description:
|
||||
self.patterns.append(Pattern(compiled, description=description))
|
||||
else:
|
||||
self.patterns.append(Pattern(compiled))
|
||||
except re.error:
|
||||
fail('Error: The pattern "' + pattern + '" in the plugin "' + self.name + '" is invalid regex.')
|
||||
|
||||
class PortScan(Plugin):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.type = None
|
||||
self.specific_ports = False
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.type = None
|
||||
self.specific_ports = False
|
||||
|
||||
async def run(self, target):
|
||||
raise NotImplementedError
|
||||
|
||||
async def run(self, target):
|
||||
raise NotImplementedError
|
||||
|
||||
class ServiceScan(Plugin):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.ports = {'tcp':[], 'udp':[]}
|
||||
self.ignore_ports = {'tcp':[], 'udp':[]}
|
||||
self.services = []
|
||||
self.service_names = []
|
||||
self.ignore_service_names = []
|
||||
self.run_once_boolean = False
|
||||
self.require_ssl_boolean = False
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.ports = {'tcp': [], 'udp': []}
|
||||
self.ignore_ports = {'tcp': [], 'udp': []}
|
||||
self.services = []
|
||||
self.service_names = []
|
||||
self.ignore_service_names = []
|
||||
self.run_once_boolean = False
|
||||
self.require_ssl_boolean = False
|
||||
|
||||
@final
|
||||
def match_service(self, protocol, port, name, negative_match=False):
|
||||
protocol = protocol.lower()
|
||||
if protocol not in ['tcp', 'udp']:
|
||||
print('Invalid protocol.')
|
||||
sys.exit(1)
|
||||
@final
|
||||
def match_service(self, protocol, port, name, negative_match=False):
|
||||
protocol = protocol.lower()
|
||||
if protocol not in ['tcp', 'udp']:
|
||||
print('Invalid protocol.')
|
||||
sys.exit(1)
|
||||
|
||||
if not isinstance(port, list):
|
||||
port = [port]
|
||||
if not isinstance(port, list):
|
||||
port = [port]
|
||||
|
||||
port = list(map(int, port))
|
||||
port = list(map(int, port))
|
||||
|
||||
if not isinstance(name, list):
|
||||
name = [name]
|
||||
if not isinstance(name, list):
|
||||
name = [name]
|
||||
|
||||
valid_regex = True
|
||||
for r in name:
|
||||
try:
|
||||
re.compile(r)
|
||||
except re.error:
|
||||
print('Invalid regex: ' + r)
|
||||
valid_regex = False
|
||||
valid_regex = True
|
||||
for r in name:
|
||||
try:
|
||||
re.compile(r)
|
||||
except re.error:
|
||||
print('Invalid regex: ' + r)
|
||||
valid_regex = False
|
||||
|
||||
if not valid_regex:
|
||||
sys.exit(1)
|
||||
if not valid_regex:
|
||||
sys.exit(1)
|
||||
|
||||
service = {'protocol': protocol, 'port': port, 'name': name, 'negative_match': negative_match}
|
||||
self.services.append(service)
|
||||
service = {'protocol': protocol, 'port': port, 'name': name, 'negative_match': negative_match}
|
||||
self.services.append(service)
|
||||
|
||||
@final
|
||||
def match_port(self, protocol, port, negative_match=False):
|
||||
protocol = protocol.lower()
|
||||
if protocol not in ['tcp', 'udp']:
|
||||
print('Invalid protocol.')
|
||||
sys.exit(1)
|
||||
else:
|
||||
if not isinstance(port, list):
|
||||
port = [port]
|
||||
@final
|
||||
def match_port(self, protocol, port, negative_match=False):
|
||||
protocol = protocol.lower()
|
||||
if protocol not in ['tcp', 'udp']:
|
||||
print('Invalid protocol.')
|
||||
sys.exit(1)
|
||||
else:
|
||||
if not isinstance(port, list):
|
||||
port = [port]
|
||||
|
||||
port = list(map(int, port))
|
||||
port = list(map(int, port))
|
||||
|
||||
if negative_match:
|
||||
self.ignore_ports[protocol] = list(set(self.ignore_ports[protocol] + port))
|
||||
else:
|
||||
self.ports[protocol] = list(set(self.ports[protocol] + port))
|
||||
if negative_match:
|
||||
self.ignore_ports[protocol] = list(set(self.ignore_ports[protocol] + port))
|
||||
else:
|
||||
self.ports[protocol] = list(set(self.ports[protocol] + port))
|
||||
|
||||
@final
|
||||
def match_service_name(self, name, negative_match=False):
|
||||
if not isinstance(name, list):
|
||||
name = [name]
|
||||
@final
|
||||
def match_service_name(self, name, negative_match=False):
|
||||
if not isinstance(name, list):
|
||||
name = [name]
|
||||
|
||||
valid_regex = True
|
||||
for r in name:
|
||||
try:
|
||||
re.compile(r)
|
||||
except re.error:
|
||||
print('Invalid regex: ' + r)
|
||||
valid_regex = False
|
||||
valid_regex = True
|
||||
for r in name:
|
||||
try:
|
||||
re.compile(r)
|
||||
except re.error:
|
||||
print('Invalid regex: ' + r)
|
||||
valid_regex = False
|
||||
|
||||
if valid_regex:
|
||||
if negative_match:
|
||||
self.ignore_service_names = list(set(self.ignore_service_names + name))
|
||||
else:
|
||||
self.service_names = list(set(self.service_names + name))
|
||||
else:
|
||||
sys.exit(1)
|
||||
if valid_regex:
|
||||
if negative_match:
|
||||
self.ignore_service_names = list(set(self.ignore_service_names + name))
|
||||
else:
|
||||
self.service_names = list(set(self.service_names + name))
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
@final
|
||||
def require_ssl(self, boolean):
|
||||
self.require_ssl_boolean = boolean
|
||||
@final
|
||||
def require_ssl(self, boolean):
|
||||
self.require_ssl_boolean = boolean
|
||||
|
||||
@final
|
||||
def run_once(self, boolean):
|
||||
self.run_once_boolean = boolean
|
||||
@final
|
||||
def run_once(self, boolean):
|
||||
self.run_once_boolean = boolean
|
||||
|
||||
@final
|
||||
def match_all_service_names(self, boolean):
|
||||
if boolean:
|
||||
# Add a "match all" service name.
|
||||
self.match_service_name('.*')
|
||||
|
||||
def get_previous_plugin_names(self):
|
||||
return []
|
||||
|
||||
@final
|
||||
def has_previous_plugins(self):
|
||||
if self.get_previous_plugin_names():
|
||||
return True
|
||||
return False
|
||||
|
||||
@final
|
||||
def match_all_service_names(self, boolean):
|
||||
if boolean:
|
||||
# Add a "match all" service name.
|
||||
self.match_service_name('.*')
|
||||
|
||||
class Report(Plugin):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
|
||||
class AutoRecon(object):
|
||||
|
||||
def __init__(self):
|
||||
self.pending_targets = []
|
||||
self.scanning_targets = []
|
||||
self.completed_targets = []
|
||||
self.plugins = {}
|
||||
self.__slug_regex = re.compile('^[a-z0-9\-]+$')
|
||||
self.plugin_types = {'port':[], 'service':[], 'report':[]}
|
||||
self.port_scan_semaphore = None
|
||||
self.service_scan_semaphore = None
|
||||
self.argparse = None
|
||||
self.argparse_group = None
|
||||
self.args = None
|
||||
self.missing_services = []
|
||||
self.taglist = []
|
||||
self.tags = []
|
||||
self.excluded_tags = []
|
||||
self.patterns = []
|
||||
self.errors = False
|
||||
self.lock = asyncio.Lock()
|
||||
self.load_slug = None
|
||||
self.load_module = None
|
||||
def __init__(self):
|
||||
self.pending = []
|
||||
self.pending_targets = []
|
||||
self.scanning_targets = []
|
||||
self.completed_targets = []
|
||||
self.plugins = {}
|
||||
self.__slug_regex = re.compile('^[a-z0-9\-]+$')
|
||||
self.plugin_types = {'port': [], 'service': [], 'report': []}
|
||||
self.port_scan_semaphore = None
|
||||
self.service_scan_semaphore = None
|
||||
self.argparse = None
|
||||
self.argparse_group = None
|
||||
self.args = None
|
||||
self.missing_services = []
|
||||
self.taglist = []
|
||||
self.tags = []
|
||||
self.excluded_tags = []
|
||||
self.patterns = []
|
||||
self.errors = False
|
||||
self.lock = asyncio.Lock()
|
||||
self.load_slug = None
|
||||
self.load_module = None
|
||||
|
||||
def add_argument(self, plugin, name, **kwargs):
|
||||
# TODO: make sure name is simple.
|
||||
name = '--' + plugin.slug + '.' + slugify(name)
|
||||
def add_argument(self, plugin, name, **kwargs):
|
||||
# TODO: make sure name is simple.
|
||||
name = '--' + plugin.slug + '.' + slugify(name)
|
||||
|
||||
if self.argparse_group is None:
|
||||
self.argparse_group = self.argparse.add_argument_group('plugin arguments', description='These are optional arguments for certain plugins.')
|
||||
self.argparse_group.add_argument(name, **kwargs)
|
||||
if self.argparse_group is None:
|
||||
self.argparse_group = self.argparse.add_argument_group('plugin arguments',
|
||||
description='These are optional arguments for certain plugins.')
|
||||
self.argparse_group.add_argument(name, **kwargs)
|
||||
|
||||
def extract_service(self, line, regex):
|
||||
if regex is None:
|
||||
regex = '^(?P<port>\d+)\/(?P<protocol>(tcp|udp))(.*)open(\s*)(?P<service>[\w\-\/]+)(\s*)(.*)$'
|
||||
match = re.search(regex, line)
|
||||
if match:
|
||||
protocol = match.group('protocol').lower()
|
||||
port = int(match.group('port'))
|
||||
service = match.group('service')
|
||||
secure = True if 'ssl' in service or 'tls' in service else False
|
||||
def extract_service(self, line, regex):
|
||||
if regex is None:
|
||||
regex = '^(?P<port>\d+)\/(?P<protocol>(tcp|udp))(.*)open(\s*)(?P<service>[\w\-\/]+)(\s*)(.*)$'
|
||||
match = re.search(regex, line)
|
||||
if match:
|
||||
protocol = match.group('protocol').lower()
|
||||
port = int(match.group('port'))
|
||||
service = match.group('service')
|
||||
secure = True if 'ssl' in service or 'tls' in service else False
|
||||
|
||||
if service.startswith('ssl/') or service.startswith('tls/'):
|
||||
service = service[4:]
|
||||
if service.startswith('ssl/') or service.startswith('tls/'):
|
||||
service = service[4:]
|
||||
|
||||
return Service(protocol, port, service, secure)
|
||||
else:
|
||||
return None
|
||||
return Service(protocol, port, service, secure)
|
||||
else:
|
||||
return None
|
||||
|
||||
async def extract_services(self, stream, regex):
|
||||
if not isinstance(stream, CommandStreamReader):
|
||||
print('Error: extract_services must be passed an instance of a CommandStreamReader.')
|
||||
sys.exit(1)
|
||||
async def extract_services(self, stream, regex):
|
||||
if not isinstance(stream, CommandStreamReader):
|
||||
print('Error: extract_services must be passed an instance of a CommandStreamReader.')
|
||||
sys.exit(1)
|
||||
|
||||
services = []
|
||||
while True:
|
||||
line = await stream.readline()
|
||||
if line is not None:
|
||||
service = self.extract_service(line, regex)
|
||||
if service:
|
||||
services.append(service)
|
||||
else:
|
||||
break
|
||||
return services
|
||||
services = []
|
||||
while True:
|
||||
line = await stream.readline()
|
||||
if line is not None:
|
||||
service = self.extract_service(line, regex)
|
||||
if service:
|
||||
services.append(service)
|
||||
else:
|
||||
break
|
||||
return services
|
||||
|
||||
def register(self, plugin, filename):
|
||||
if plugin.disabled:
|
||||
return
|
||||
def register(self, plugin, filename):
|
||||
if plugin.disabled:
|
||||
return
|
||||
if plugin.name is None:
|
||||
fail(
|
||||
'Error: Plugin with class name "' + plugin.__class__.__name__ + '" in ' + filename + ' does not have a name.')
|
||||
|
||||
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():
|
||||
if plugin.name == loaded_plugin.name:
|
||||
fail('Error: Duplicate plugin name "' + plugin.name + '" detected in ' + filename + '.',
|
||||
file=sys.stderr)
|
||||
|
||||
for _, loaded_plugin in self.plugins.items():
|
||||
if plugin.name == loaded_plugin.name:
|
||||
fail('Error: Duplicate plugin name "' + plugin.name + '" detected in ' + filename + '.', file=sys.stderr)
|
||||
if plugin.slug is None:
|
||||
plugin.slug = slugify(plugin.name)
|
||||
elif not self.__slug_regex.match(plugin.slug):
|
||||
fail(
|
||||
'Error: provided slug "' + plugin.slug + '" in ' + filename + ' is not valid (must only contain lowercase letters, numbers, and hyphens).',
|
||||
file=sys.stderr)
|
||||
|
||||
if plugin.slug is None:
|
||||
plugin.slug = slugify(plugin.name)
|
||||
elif not self.__slug_regex.match(plugin.slug):
|
||||
fail('Error: provided slug "' + plugin.slug + '" in ' + filename + ' is not valid (must only contain lowercase letters, numbers, and hyphens).', file=sys.stderr)
|
||||
if plugin.slug in config['protected_classes']:
|
||||
fail('Error: plugin slug "' + plugin.slug + '" in ' + filename + ' is a protected string. Please change.')
|
||||
|
||||
if plugin.slug in config['protected_classes']:
|
||||
fail('Error: plugin slug "' + plugin.slug + '" in ' + filename + ' is a protected string. Please change.')
|
||||
if plugin.slug not in self.plugins:
|
||||
|
||||
if plugin.slug not in self.plugins:
|
||||
for _, loaded_plugin in self.plugins.items():
|
||||
if plugin is loaded_plugin:
|
||||
fail(
|
||||
'Error: plugin "' + plugin.name + '" in ' + filename + ' already loaded as "' + loaded_plugin.name + '" (' + str(
|
||||
loaded_plugin) + ')', file=sys.stderr)
|
||||
|
||||
for _, loaded_plugin in self.plugins.items():
|
||||
if plugin is loaded_plugin:
|
||||
fail('Error: plugin "' + plugin.name + '" in ' + filename + ' already loaded as "' + loaded_plugin.name + '" (' + str(loaded_plugin) + ')', file=sys.stderr)
|
||||
configure_function_found = False
|
||||
run_coroutine_found = False
|
||||
manual_function_found = False
|
||||
|
||||
configure_function_found = False
|
||||
run_coroutine_found = False
|
||||
manual_function_found = False
|
||||
for member_name, member_value in inspect.getmembers(plugin, predicate=inspect.ismethod):
|
||||
if member_name == 'configure':
|
||||
configure_function_found = True
|
||||
elif member_name == 'run' and inspect.iscoroutinefunction(member_value):
|
||||
if len(inspect.getfullargspec(member_value).args) != 2:
|
||||
fail(
|
||||
'Error: the "run" coroutine in the plugin "' + plugin.name + '" in ' + filename + ' should have two arguments.',
|
||||
file=sys.stderr)
|
||||
run_coroutine_found = True
|
||||
elif member_name == 'manual':
|
||||
if len(inspect.getfullargspec(member_value).args) != 3:
|
||||
fail(
|
||||
'Error: the "manual" function in the plugin "' + plugin.name + '" in ' + filename + ' should have three arguments.',
|
||||
file=sys.stderr)
|
||||
manual_function_found = True
|
||||
|
||||
for member_name, member_value in inspect.getmembers(plugin, predicate=inspect.ismethod):
|
||||
if member_name == 'configure':
|
||||
configure_function_found = True
|
||||
elif member_name == 'run' and inspect.iscoroutinefunction(member_value):
|
||||
if len(inspect.getfullargspec(member_value).args) != 2:
|
||||
fail('Error: the "run" coroutine in the plugin "' + plugin.name + '" in ' + filename + ' should have two arguments.', file=sys.stderr)
|
||||
run_coroutine_found = True
|
||||
elif member_name == 'manual':
|
||||
if len(inspect.getfullargspec(member_value).args) != 3:
|
||||
fail('Error: the "manual" function in the plugin "' + plugin.name + '" in ' + filename + ' should have three arguments.', file=sys.stderr)
|
||||
manual_function_found = True
|
||||
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)
|
||||
|
||||
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)
|
||||
if issubclass(plugin.__class__, PortScan):
|
||||
if plugin.type is None:
|
||||
fail(
|
||||
'Error: the PortScan plugin "' + plugin.name + '" in ' + filename + ' requires a type (either tcp or udp).')
|
||||
else:
|
||||
plugin.type = plugin.type.lower()
|
||||
if plugin.type not in ['tcp', 'udp']:
|
||||
fail(
|
||||
'Error: the PortScan plugin "' + plugin.name + '" in ' + filename + ' has an invalid type (should be tcp or udp).')
|
||||
self.plugin_types["port"].append(plugin)
|
||||
elif issubclass(plugin.__class__, ServiceScan):
|
||||
self.plugin_types["service"].append(plugin)
|
||||
elif issubclass(plugin.__class__, Report):
|
||||
self.plugin_types["report"].append(plugin)
|
||||
else:
|
||||
fail(
|
||||
'Plugin "' + plugin.name + '" in ' + filename + ' is neither a PortScan, ServiceScan, nor a Report.',
|
||||
file=sys.stderr)
|
||||
|
||||
if issubclass(plugin.__class__, PortScan):
|
||||
if plugin.type is None:
|
||||
fail('Error: the PortScan plugin "' + plugin.name + '" in ' + filename + ' requires a type (either tcp or udp).')
|
||||
else:
|
||||
plugin.type = plugin.type.lower()
|
||||
if plugin.type not in ['tcp', 'udp']:
|
||||
fail('Error: the PortScan plugin "' + plugin.name + '" in ' + filename + ' has an invalid type (should be tcp or udp).')
|
||||
self.plugin_types["port"].append(plugin)
|
||||
elif issubclass(plugin.__class__, ServiceScan):
|
||||
self.plugin_types["service"].append(plugin)
|
||||
elif issubclass(plugin.__class__, Report):
|
||||
self.plugin_types["report"].append(plugin)
|
||||
else:
|
||||
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]
|
||||
# Add plugin tags to tag list.
|
||||
[self.taglist.append(t) for t in plugin.tags if t not in self.tags]
|
||||
|
||||
# Add plugin tags to tag list.
|
||||
[self.taglist.append(t) for t in plugin.tags if t not in self.tags]
|
||||
plugin.autorecon = self
|
||||
if configure_function_found:
|
||||
plugin.configure()
|
||||
self.plugins[plugin.slug] = plugin
|
||||
else:
|
||||
fail('Error: plugin slug "' + plugin.slug + '" in ' + filename + ' is already assigned.', file=sys.stderr)
|
||||
|
||||
plugin.autorecon = self
|
||||
if configure_function_found:
|
||||
plugin.configure()
|
||||
self.plugins[plugin.slug] = plugin
|
||||
else:
|
||||
fail('Error: plugin slug "' + plugin.slug + '" in ' + filename + ' is already assigned.', file=sys.stderr)
|
||||
async def execute(self, cmd, target, tag, patterns=None, outfile=None, errfile=None, plugin=None):
|
||||
if patterns:
|
||||
combined_patterns = self.patterns + patterns
|
||||
else:
|
||||
combined_patterns = self.patterns
|
||||
|
||||
async def execute(self, cmd, target, tag, patterns=None, outfile=None, errfile=None):
|
||||
if patterns:
|
||||
combined_patterns = self.patterns + patterns
|
||||
else:
|
||||
combined_patterns = self.patterns
|
||||
process = await asyncio.create_subprocess_shell(
|
||||
cmd,
|
||||
stdin=open('/dev/null'),
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE)
|
||||
cout = CommandStreamReader(process.stdout, target, tag, patterns=combined_patterns, outfile=outfile,
|
||||
plugin=plugin)
|
||||
cerr = CommandStreamReader(process.stderr, target, tag, patterns=combined_patterns, outfile=errfile,
|
||||
plugin=plugin)
|
||||
|
||||
process = await asyncio.create_subprocess_shell(
|
||||
cmd,
|
||||
stdin=open('/dev/null'),
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE)
|
||||
asyncio.create_task(cout._read())
|
||||
asyncio.create_task(cerr._read())
|
||||
|
||||
cout = CommandStreamReader(process.stdout, target, tag, patterns=combined_patterns, outfile=outfile)
|
||||
cerr = CommandStreamReader(process.stderr, target, tag, patterns=combined_patterns, outfile=errfile)
|
||||
return process, cout, cerr
|
||||
|
||||
asyncio.create_task(cout._read())
|
||||
asyncio.create_task(cerr._read())
|
||||
def get_plugin_by_name(self, name):
|
||||
for key, value in self.plugins.items():
|
||||
if value.name == name:
|
||||
return self.plugins[key]
|
||||
|
||||
return process, cout, cerr
|
||||
def get_next_service_scan_plugins(self, current_plugin):
|
||||
next_plugins = []
|
||||
for plugin in self.plugin_types['service']:
|
||||
if not plugin.has_previous_plugins():
|
||||
continue
|
||||
for previous_plugin_name in plugin.get_previous_plugin_names():
|
||||
if current_plugin.name == previous_plugin_name:
|
||||
next_plugins.append(plugin)
|
||||
return next_plugins
|
||||
|
||||
def queue_new_service_scan(self, plugin, service):
|
||||
# try using append. in the main method "pending" is sometimes set() and sometimes list()
|
||||
self.pending.append(asyncio.create_task(service_scan(plugin, service, run_from_service_scan=True)))
|
||||
|
|
|
@ -121,7 +121,7 @@ class Service:
|
|||
self.add_manual_commands(description, command)
|
||||
|
||||
@final
|
||||
async def execute(self, cmd, blocking=True, outfile=None, errfile=None, future_outfile=None):
|
||||
async def execute(self, cmd, blocking=True, outfile=None, errfile=None, future_outfile=None, plugin=None):
|
||||
target = self.target
|
||||
|
||||
# Create variables for command references.
|
||||
|
@ -182,7 +182,7 @@ class Service:
|
|||
with open(os.path.join(target.scandir, '_commands.log'), 'a') as file:
|
||||
file.writelines(cmd + '\n\n')
|
||||
|
||||
process, stdout, stderr = await target.autorecon.execute(cmd, target, tag, patterns=plugin.patterns, outfile=outfile, errfile=errfile)
|
||||
process, stdout, stderr = await target.autorecon.execute(cmd, target, tag, patterns=plugin.patterns, outfile=outfile, errfile=errfile, plugin=plugin)
|
||||
|
||||
target.running_tasks[tag]['processes'].append({'process': process, 'stderr': stderr, 'cmd': cmd})
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
from autorecon.plugins import ServiceScan
|
||||
|
||||
|
||||
class DirectoryListing(ServiceScan):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.name = "Directory Listing"
|
||||
self.tags = ['default', 'safe', 'http', 'test']
|
||||
|
||||
def configure(self):
|
||||
self.match_service_name('^http')
|
||||
self.match_service_name('^nacn_http$', negative_match=True)
|
||||
self.add_pattern('<h1>Directory listing for', description='Directory Listing enabled',
|
||||
plugin_names=["Directory Listing Verify"])
|
||||
|
||||
async def run(self, service):
|
||||
await service.execute('curl {http_scheme}://{addressv6}:{port}')
|
||||
|
||||
|
||||
class DirectoryListingVerify(ServiceScan):
|
||||
"""
|
||||
this is a useless plugin that is only run, if directory listing was found.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.name = "Directory Listing verify"
|
||||
self.tags = ['default', 'safe', 'http', 'test']
|
||||
|
||||
def configure(self):
|
||||
self.match_service_name('^http')
|
||||
self.match_service_name('^nacn_http$', negative_match=True)
|
||||
|
||||
async def run(self, service):
|
||||
await service.execute('curl {http_scheme}://{addressv6}:{port}/?id=1')
|
||||
|
||||
def get_previous_plugin_names(self):
|
||||
return ["Directory Listing"]
|
Loading…
Reference in New Issue