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',
'exclude_tags',
'plugins_dir',
'add_plugins-dir',
'outdir',
'single_target',
'only_scans_dir',
@ -491,6 +492,7 @@ class AutoRecon(object):
'tags': 'default',
'exclude_tags': None,
'plugins_dir': os.path.dirname(os.path.abspath(__file__)) + '/plugins',
'add_plugins_dir': None,
'outdir': 'results',
'single_target': 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('--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('--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('-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')
@ -1370,54 +1373,68 @@ async def main():
try:
config_toml = toml.load(c)
for key, val in config_toml.items():
key = slugify(key)
if key == 'global-file':
autorecon.config['global_file'] = val
elif key == 'plugins-dir':
autorecon.config['plugins_dir'] = val
elif key == 'add-plugins-dir':
autorecon.config['add_plugins_dir'] = val
except toml.decoder.TomlDecodeError:
fail('Error: Couldn\'t parse ' + args.config_file + ' config file. Check syntax.')
args_dict = vars(args)
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']
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']
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']):
fail('Error: Specified plugins directory "' + autorecon.config['plugins_dir'] + '" does not exist.')
for plugin_file in os.listdir(autorecon.config['plugins_dir']):
if not plugin_file.startswith('_') and plugin_file.endswith('.py'):
if autorecon.config['add_plugins_dir'] and not os.path.isdir(autorecon.config['add_plugins_dir']):
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))
dirname = os.path.abspath(dirname)
plugins_dirs = [autorecon.config['plugins_dir']]
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.
sys.path.insert(1, dirname)
for plugins_dir in plugins_dirs:
for plugin_file in os.listdir(plugins_dir):
if not plugin_file.startswith('_') and plugin_file.endswith('.py'):
try:
plugin = importlib.import_module(filename[:-3])
# 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
dirname, filename = os.path.split(os.path.join(plugins_dir, plugin_file))
dirname = os.path.abspath(dirname)
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)
# Temporarily insert the plugin directory into the sys.path so importing plugins works.
sys.path.insert(1, dirname)
# 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)
try:
plugin = importlib.import_module(filename[:-3])
# 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']:
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():
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