New Features

Added ability to add an additional plugins directory instead of overriding the original. Useful for plugin dev.
Also added a new non-default port scan which guesses services based on open ports.
This commit is contained in:
Tib3rius 2021-09-01 13:18:09 -04:00
parent f5415f9f03
commit 0b37730304
2 changed files with 92 additions and 28 deletions

View File

@ -466,6 +466,7 @@ class AutoRecon(object):
'tags', 'tags',
'exclude_tags', 'exclude_tags',
'plugins_dir', 'plugins_dir',
'add_plugins-dir',
'outdir', 'outdir',
'single_target', 'single_target',
'only_scans_dir', 'only_scans_dir',
@ -491,6 +492,7 @@ class AutoRecon(object):
'tags': 'default', 'tags': 'default',
'exclude_tags': None, 'exclude_tags': None,
'plugins_dir': os.path.dirname(os.path.abspath(__file__)) + '/plugins', 'plugins_dir': os.path.dirname(os.path.abspath(__file__)) + '/plugins',
'add_plugins_dir': None,
'outdir': 'results', 'outdir': 'results',
'single_target': False, 'single_target': False,
'only_scans_dir': False, 'only_scans_dir': False,
@ -1334,6 +1336,7 @@ async def main():
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('--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('--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('--plugins-dir', action='store', type=str, help='The location of the plugins directory. Default: %(default)s')
parser.add_argument('--add-plugins-dir', action='store', type=str, help='The location of an additional plugins directory to add to the main one. Default: %(default)s')
parser.add_argument('-l', '--list', action='store', nargs='?', const='plugins', help='List all plugins or plugins of a specific type. e.g. --list, --list port, --list service') parser.add_argument('-l', '--list', action='store', nargs='?', const='plugins', help='List all plugins or plugins of a specific type. e.g. --list, --list port, --list service')
parser.add_argument('-o', '--output', action='store', dest='outdir', help='The output directory for results. 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('--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')
@ -1370,54 +1373,68 @@ async def main():
try: try:
config_toml = toml.load(c) config_toml = toml.load(c)
for key, val in config_toml.items(): for key, val in config_toml.items():
key = slugify(key)
if key == 'global-file': if key == 'global-file':
autorecon.config['global_file'] = val autorecon.config['global_file'] = val
elif key == 'plugins-dir': elif key == 'plugins-dir':
autorecon.config['plugins_dir'] = val autorecon.config['plugins_dir'] = val
elif key == 'add-plugins-dir':
autorecon.config['add_plugins_dir'] = val
except toml.decoder.TomlDecodeError: except toml.decoder.TomlDecodeError:
fail('Error: Couldn\'t parse ' + args.config_file + ' config file. Check syntax.') fail('Error: Couldn\'t parse ' + args.config_file + ' config file. Check syntax.')
args_dict = vars(args) args_dict = vars(args)
for key in args_dict: for key in args_dict:
if key == 'global_file' and args_dict['global_file'] is not None: key = slugify(key)
if key == 'global-file' and args_dict['global_file'] is not None:
autorecon.config['global_file'] = args_dict['global_file'] autorecon.config['global_file'] = args_dict['global_file']
elif key == 'plugins_dir' and args_dict['plugins_dir'] is not None: elif key == 'plugins-dir' and args_dict['plugins_dir'] is not None:
autorecon.config['plugins_dir'] = args_dict['plugins_dir'] autorecon.config['plugins_dir'] = args_dict['plugins_dir']
elif key == 'add-plugins-dir' and args_dict['add_plugins_dir'] is not None:
autorecon.config['add_plugins_dir'] = args_dict['add_plugins_dir']
if not os.path.isdir(autorecon.config['plugins_dir']): if not os.path.isdir(autorecon.config['plugins_dir']):
fail('Error: Specified plugins directory "' + autorecon.config['plugins_dir'] + '" does not exist.') fail('Error: Specified plugins directory "' + autorecon.config['plugins_dir'] + '" does not exist.')
for plugin_file in os.listdir(autorecon.config['plugins_dir']): if autorecon.config['add_plugins_dir'] and not os.path.isdir(autorecon.config['add_plugins_dir']):
if not plugin_file.startswith('_') and plugin_file.endswith('.py'): fail('Error: Specified additional plugins directory "' + autorecon.config['add_plugins_dir'] + '" does not exist.')
dirname, filename = os.path.split(os.path.join(autorecon.config['plugins_dir'], plugin_file)) plugins_dirs = [autorecon.config['plugins_dir']]
dirname = os.path.abspath(dirname) if autorecon.config['add_plugins_dir']:
plugins_dirs.append(autorecon.config['add_plugins_dir'])
# Temporarily insert the plugin directory into the sys.path so importing plugins works. for plugins_dir in plugins_dirs:
sys.path.insert(1, dirname) for plugin_file in os.listdir(plugins_dir):
if not plugin_file.startswith('_') and plugin_file.endswith('.py'):
try: dirname, filename = os.path.split(os.path.join(plugins_dir, plugin_file))
plugin = importlib.import_module(filename[:-3]) dirname = os.path.abspath(dirname)
# Remove the plugin directory from the path after import.
sys.path.pop(1)
clsmembers = inspect.getmembers(plugin, predicate=inspect.isclass)
for (_, c) in clsmembers:
if c.__module__ == 'autorecon':
continue
if c.__name__.lower() in autorecon.config['protected_classes']: # Temporarily insert the plugin directory into the sys.path so importing plugins works.
print('Plugin "' + c.__name__ + '" in ' + filename + ' is using a protected class name. Please change it.') sys.path.insert(1, dirname)
sys.exit(1)
# Only add classes that are a sub class of either PortScan or ServiceScan try:
if issubclass(c, PortScan) or issubclass(c, ServiceScan): plugin = importlib.import_module(filename[:-3])
autorecon.register(c(), filename) # Remove the plugin directory from the path after import.
else: sys.path.pop(1)
print('Plugin "' + c.__name__ + '" in ' + filename + ' is not a subclass of either PortScan or ServiceScan.') clsmembers = inspect.getmembers(plugin, predicate=inspect.isclass)
except (ImportError, SyntaxError) as ex: for (_, c) in clsmembers:
print('cannot import ' + filename + ' plugin') if c.__module__ == 'autorecon':
print(ex) continue
sys.exit(1)
if c.__name__.lower() in autorecon.config['protected_classes']:
print('Plugin "' + c.__name__ + '" in ' + filename + ' is using a protected class name. Please change it.')
sys.exit(1)
# Only add classes that are a sub class of either PortScan or ServiceScan
if issubclass(c, PortScan) or issubclass(c, ServiceScan):
autorecon.register(c(), filename)
else:
print('Plugin "' + c.__name__ + '" in ' + filename + ' is not a subclass of either PortScan or ServiceScan.')
except (ImportError, SyntaxError) as ex:
print('cannot import ' + filename + ' plugin')
print(ex)
sys.exit(1)
for plugin in autorecon.plugins.values(): for plugin in autorecon.plugins.values():
if plugin.slug in autorecon.taglist: if plugin.slug in autorecon.taglist:

View File

@ -0,0 +1,47 @@
from autorecon import PortScan, Service
import re
class GuesPortScan(PortScan):
def __init__(self):
super().__init__()
self.name = 'Guess TCP Ports'
self.type = 'tcp'
self.description = 'Performs an Nmap scan of the all TCP ports but guesses services based off the port found. Can be quicker. Proper service matching is performed at the end of the scan.'
self.tags = ['guess-port-scan', 'long']
self.priority = 0
async def run(self, target):
if target.ports:
if target.ports['tcp']:
process, stdout, stderr = await target.execute('nmap {nmap_extra} -A --osscan-guess --version-all -p ' + target.ports['tcp'] + ' -oN "{scandir}/_custom_ports_tcp_nmap.txt" -oX "{scandir}/xml/_custom_ports_tcp_nmap.xml" {address}', blocking=False)
else:
return []
else:
process, stdout, stderr = await target.execute('nmap {nmap_extra} -A --osscan-guess --version-all -p- -oN "{scandir}/_quick_tcp_nmap.txt" -oX "{scandir}/xml/_quick_tcp_nmap.xml" {address}', blocking=False)
insecure_ports = {
'20':'ftp', '21':'ftp', '22':'ssh', '23':'telnet', '25':'smtp', '53':'domain', '69':'tftp', '79':'finger', '80':'http', '88':'kerberos', '109':'pop3', '110':'pop3', '111':'rpcbind', '119':'nntp', '135':'msrpc', '139':'netbios-ssn', '143':'imap', '161':'snmp', '220':'imap', '389':'ldap', '433':'nntp', '445':'smb', '587':'smtp', '631':'ipp', '873':'rsync', '1098':'java-rmi', '1099':'java-rmi', '1433':'mssql', '1521':'oracle', '2049':'nfs', '2483':'oracle', '3020':'smb', '3306':'mysql', '3389':'rdp', '3632':'distccd', '5060':'asterisk', '5500':'vnc', '5900':'vnc', '5985':'wsman', '6379':'redis', '8080':'http-proxy', '27017':'mongod', '27018':'mongod', '27019':'mongod'
}
secure_ports = {
'443':'https', '465':'smtp', '563':'nntp', '585':'imaps', '593':'msrpc', '636':'ldap', '989':'ftp', '990':'ftp', '992':'telnet', '993':'imaps', '995':'pop3s', '2484':'oracle', '5061':'asterisk', '5986':'wsman'
}
services = []
while True:
line = await stdout.readline()
if line is not None:
match = re.match('^Discovered open port ([0-9]+)/tcp', line)
if match:
if match.group(1) in insecure_ports.keys():
await target.add_service(Service('tcp', match.group(1), insecure_ports[match.group(1)]))
elif match.group(1) in secure_ports.keys():
await target.add_service(Service('tcp', match.group(1), secure_ports[match.group(1)], True))
service = target.extract_service(line)
if service is not None:
services.append(service)
else:
break
await process.wait()
return services