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:
parent
f5415f9f03
commit
0b37730304
73
autorecon.py
73
autorecon.py
|
@ -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:
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue