From 4912a235ad8b1537cfa999f5a4f2c2de65765cc5 Mon Sep 17 00:00:00 2001 From: Tib3rius <48113936+Tib3rius@users.noreply.github.com> Date: Thu, 13 Jan 2022 18:33:45 -0500 Subject: [PATCH] I/O Updates, New Subdomain Enumeration Plugin Plugins can now call info(), warn(), and error() functions from their service / target object which will properly prefix the plugin name, etc. A new subdomain enumeration plugin using gobuster has been developed. --- autorecon/default-plugins/databases.py | 5 +- .../default-plugins/default-port-scan.py | 7 +- autorecon/default-plugins/dns.py | 110 +++++++++++------- autorecon/default-plugins/http_server.py | 90 +++++++------- autorecon/default-plugins/redis.py | 3 +- autorecon/default-plugins/rpc.py | 3 +- autorecon/main.py | 2 +- autorecon/plugins.py | 14 ++- autorecon/targets.py | 32 ++++- pyproject.toml | 2 +- 10 files changed, 170 insertions(+), 98 deletions(-) diff --git a/autorecon/default-plugins/databases.py b/autorecon/default-plugins/databases.py index 4e5286b..0c8ca47 100644 --- a/autorecon/default-plugins/databases.py +++ b/autorecon/default-plugins/databases.py @@ -1,5 +1,4 @@ from autorecon.plugins import ServiceScan -from autorecon.io import error from shutil import which class NmapMongoDB(ServiceScan): @@ -77,7 +76,7 @@ class OracleTNScmd(ServiceScan): def check(self): if which('tnscmd10g') is None: - error('The tnscmd10g program could not be found. Make sure it is installed. (On Kali, run: sudo apt install tnscmd10g)') + self.error('The tnscmd10g program could not be found. Make sure it is installed. (On Kali, run: sudo apt install tnscmd10g)') async def run(self, service): if service.target.ipversion == 'IPv4': @@ -96,7 +95,7 @@ class OracleScanner(ServiceScan): def check(self): if which('oscanner') is None: - error('The oscanner program could not be found. Make sure it is installed. (On Kali, run: sudo apt install oscanner)') + self.error('The oscanner program could not be found. Make sure it is installed. (On Kali, run: sudo apt install oscanner)') async def run(self, service): await service.execute('oscanner -v -s {address} -P {port} 2>&1', outfile='{protocol}_{port}_oracle_scanner.txt') diff --git a/autorecon/default-plugins/default-port-scan.py b/autorecon/default-plugins/default-port-scan.py index 8a4036d..1ca99a7 100644 --- a/autorecon/default-plugins/default-port-scan.py +++ b/autorecon/default-plugins/default-port-scan.py @@ -1,5 +1,4 @@ from autorecon.plugins import PortScan -from autorecon.io import info, error from autorecon.config import config import os, re @@ -56,7 +55,7 @@ class AllTCPPortScan(PortScan): if line is not None: match = re.search('^Discovered open port ([0-9]+)/tcp', line) if match: - info('Discovered open port {bmagenta}tcp/' + match.group(1) + '{rst} on {byellow}' + target.address + '{rst}', verbosity=1) + target.info('Discovered open port {bmagenta}tcp/' + match.group(1) + '{rst} on {byellow}' + target.address + '{rst}', verbosity=1) service = target.extract_service(line) if service: services.append(service) @@ -91,7 +90,7 @@ class Top100UDPPortScan(PortScan): if line is not None: match = re.search('^Discovered open port ([0-9]+)/udp', line) if match: - info('Discovered open port {bmagenta}udp/' + match.group(1) + '{rst} on {byellow}' + target.address + '{rst}', verbosity=1) + target.info('Discovered open port {bmagenta}udp/' + match.group(1) + '{rst} on {byellow}' + target.address + '{rst}', verbosity=1) service = target.extract_service(line) if service: services.append(service) @@ -100,4 +99,4 @@ class Top100UDPPortScan(PortScan): await process.wait() return services else: - error('UDP scan requires AutoRecon be run with root privileges.') + target.error('UDP scan requires AutoRecon be run with root privileges.') diff --git a/autorecon/default-plugins/dns.py b/autorecon/default-plugins/dns.py index f9e65f9..cc72915 100644 --- a/autorecon/default-plugins/dns.py +++ b/autorecon/default-plugins/dns.py @@ -1,6 +1,6 @@ from autorecon.plugins import ServiceScan -from autorecon.io import error from shutil import which +import os class NmapDNS(ServiceScan): @@ -61,51 +61,83 @@ class NmapMulticastDNS(ServiceScan): class DnsReconDefault(ServiceScan): - def __init__(self): - super().__init__() - self.name = "DnsRecon Default Scan" - self.slug = 'dnsrecon' - self.priority = 0 - self.tags = ['default', 'safe', 'dns'] + def __init__(self): + super().__init__() + self.name = "DnsRecon Default Scan" + self.slug = 'dnsrecon' + self.priority = 0 + self.tags = ['default', 'safe', 'dns'] - def configure(self): - self.match_service_name('^domain') + def configure(self): + self.match_service_name('^domain') - def check(self): - if which('dnsrecon') is None: - error('The program dnsrecon could not be found. Make sure it is installed. (On Kali, run: sudo apt install dnsrecon)') + def check(self): + if which('dnsrecon') is None: + self.error('The program dnsrecon could not be found. Make sure it is installed. (On Kali, run: sudo apt install dnsrecon)') - def manual(self, service, plugin_was_run): - service.add_manual_command('Use dnsrecon to automatically query data from the DNS server. You must specify the target domain name.', [ - 'dnsrecon -n {address} -d 2>&1 | tee {scandir}/{protocol}_{port}_dnsrecon_default_manual.txt' - ]) + def manual(self, service, plugin_was_run): + service.add_manual_command('Use dnsrecon to automatically query data from the DNS server. You must specify the target domain name.', [ + 'dnsrecon -n {address} -d 2>&1 | tee {scandir}/{protocol}_{port}_dnsrecon_default_manual.txt' + ]) - async def run(self, service): - if self.get_global('domain'): - await service.execute('dnsrecon -n {address} -d ' + self.get_global('domain') + ' 2>&1', outfile='{protocol}_{port}_dnsrecon_default.txt') - else: - error('A domain name was not specified in the command line options (--global.domain). If you know the domain name, look in the _manual_commands.txt file for the dnsrecon command.') + async def run(self, service): + if self.get_global('domain'): + await service.execute('dnsrecon -n {address} -d ' + self.get_global('domain') + ' 2>&1', outfile='{protocol}_{port}_dnsrecon_default.txt') + else: + service.error('A domain name was not specified in the command line options (--global.domain). If you know the domain name, look in the _manual_commands.txt file for the dnsrecon command.') class DnsReconSubdomainBruteforce(ServiceScan): - def __init__(self): - super().__init__() - self.name = "DnsRecon Bruteforce Subdomains" - self.slug = 'dnsrecon-brute' - self.priority = 0 - self.tags = ['default', 'safe', 'long', 'dns'] + def __init__(self): + super().__init__() + self.name = "DnsRecon Bruteforce Subdomains" + self.slug = 'dnsrecon-brute' + self.priority = 0 + self.tags = ['default', 'safe', 'long', 'dns'] - def configure(self): - self.match_service_name('^domain') + def configure(self): + self.match_service_name('^domain') - def check(self): - if which('dnsrecon') is None: - error('The program dnsrecon could not be found. Make sure it is installed. (On Kali, run: sudo apt install dnsrecon)') + def check(self): + if which('dnsrecon') is None: + self.error('The program dnsrecon could not be found. Make sure it is installed. (On Kali, run: sudo apt install dnsrecon)') - def manual(self, service, plugin_was_run): - domain_name = '' - if self.get_global('domain'): - domain_name = self.get_global('domain') - service.add_manual_command('Use dnsrecon to bruteforce subdomains of a DNS domain.', [ - 'dnsrecon -n {address} -d ' + domain_name + ' -D /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -t brt 2>&1 | tee {scandir}/{protocol}_{port}_dnsrecon_subdomain_bruteforce.txt', - ]) + def manual(self, service, plugin_was_run): + domain_name = '' + if self.get_global('domain'): + domain_name = self.get_global('domain') + service.add_manual_command('Use dnsrecon to bruteforce subdomains of a DNS domain.', [ + 'dnsrecon -n {address} -d ' + domain_name + ' -D /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -t brt 2>&1 | tee {scandir}/{protocol}_{port}_dnsrecon_subdomain_bruteforce.txt', + ]) + +class SubdomainEnumeration(ServiceScan): + + def __init__(self): + super().__init__() + self.name = "Subdomain Enumeration" + self.slug = "subdomain-enum" + self.tags = ['default', 'safe', 'long', 'dns'] + + def configure(self): + self.add_option('domain', help='The domain to use as the base domain (e.g. example.com) for subdomain enumeration. Default: %(default)s') + self.add_list_option('wordlist', default=['/usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt'], help='The wordlist(s) to use when enumerating subdomains. Separate multiple wordlists with spaces. Default: %(default)s') + self.add_option('threads', default=10, help='The number of threads to use when enumerating subdomains. Default: %(default)s') + self.match_service_name('^domain') + + async def run(self, service): + domains = [] + + if self.get_option('domain'): + domains.append(self.get_option('domain')) + if service.target.type == 'hostname' and service.target.address not in domains: + domains.append(service.target.address) + if self.get_global('domain') and self.get_global('domain') not in domains: + domains.append(self.get_global('domain')) + + if len(domains) > 0: + for wordlist in self.get_option('wordlist'): + name = os.path.splitext(os.path.basename(wordlist))[0] + for domain in domains: + await service.execute('gobuster dns -d ' + domain + ' -r {addressv6} -w ' + wordlist + ' -o "{scandir}/{protocol}_{port}_' + domain + '_subdomains_' + name + '.txt"') + else: + service.info('The target was not a domain, nor was a domain provided as an option. Skipping subdomain enumeration.') diff --git a/autorecon/default-plugins/http_server.py b/autorecon/default-plugins/http_server.py index 66b6933..69e0c27 100644 --- a/autorecon/default-plugins/http_server.py +++ b/autorecon/default-plugins/http_server.py @@ -1,5 +1,5 @@ from autorecon.plugins import ServiceScan -from autorecon.io import error, info, fformat +from autorecon.io import fformat from autorecon.config import config from shutil import which import os @@ -78,7 +78,7 @@ class CurlRobots(ServiceScan): with open(filename, mode='wt', encoding='utf8') as robots: robots.write('\n'.join(lines)) else: - info('{bblue}[' + fformat('{tag}') + ']{rst} There did not appear to be a robots.txt file in the webroot (/).') + service.info('{bblue}[' + fformat('{tag}') + ']{rst} There did not appear to be a robots.txt file in the webroot (/).') class CurlKnownSecurity(ServiceScan): @@ -102,7 +102,7 @@ class CurlKnownSecurity(ServiceScan): with open(filename, mode='wt', encoding='utf8') as robots: robots.write('\n'.join(lines)) else: - info('{bblue}[' + fformat('{tag}') + ']{rst} There did not appear to be a .well-known/security.txt file in the webroot (/).') + service.info('{bblue}[' + fformat('{tag}') + ']{rst} There did not appear to be a .well-known/security.txt file in the webroot (/).') class DirBuster(ServiceScan): @@ -126,25 +126,25 @@ class DirBuster(ServiceScan): tool = self.get_option('tool') if tool == 'feroxbuster': if which('feroxbuster') is None: - error('The feroxbuster program could not be found. Make sure it is installed. (On Kali, run: sudo apt install feroxbuster)') + self.error('The feroxbuster program could not be found. Make sure it is installed. (On Kali, run: sudo apt install feroxbuster)') elif tool == 'gobuster': if which('gobuster') is None: - error('The gobuster program could not be found. Make sure it is installed. (On Kali, run: sudo apt install gobuster)') + self.error('The gobuster program could not be found. Make sure it is installed. (On Kali, run: sudo apt install gobuster)') elif tool == 'dirsearch': if which('dirsearch') is None: - error('The dirsearch program could not be found. Make sure it is installed. (On Kali, run: sudo apt install dirsearch)') + self.error('The dirsearch program could not be found. Make sure it is installed. (On Kali, run: sudo apt install dirsearch)') async def run(self, service): dot_extensions = ','.join(['.' + x for x in self.get_option('ext').split(',')]) for wordlist in self.get_option('wordlist'): name = os.path.splitext(os.path.basename(wordlist))[0] if self.get_option('tool') == 'feroxbuster': - await service.execute('feroxbuster -u {http_scheme}://{addressv6}:{port}/ -t ' + str(self.get_option('threads')) + ' -w ' + wordlist + ' -x "' + self.get_option('ext') + '" -v -k -n -q -o "{scandir}/{protocol}_{port}_{http_scheme}_feroxbuster_' + name + '.txt"') + await service.execute('feroxbuster -u {http_scheme}://{addressv6}:{port}/ -t ' + str(self.get_option('threads')) + ' -w ' + wordlist + ' -x "' + self.get_option('ext') + '" -v -k -n -q -e -o "{scandir}/{protocol}_{port}_{http_scheme}_feroxbuster_' + name + '.txt"') elif self.get_option('tool') == 'gobuster': - await service.execute('gobuster dir -u {http_scheme}://{addressv6}:{port}/ -t ' + str(self.get_option('threads')) + ' -w ' + wordlist + ' -e -k -x "' + self.get_option('ext') + '" -z -o "{scandir}/{protocol}_{port}_{http_scheme}_gobuster_' + name + '.txt"') + await service.execute('gobuster dir -u {http_scheme}://{addressv6}:{port}/ -t ' + str(self.get_option('threads')) + ' -w ' + wordlist + ' -e -k -x "' + self.get_option('ext') + '" -z -d -o "{scandir}/{protocol}_{port}_{http_scheme}_gobuster_' + name + '.txt"') elif self.get_option('tool') == 'dirsearch': if service.target.ipversion == 'IPv6': - error('dirsearch does not support IPv6.') + service.error('dirsearch does not support IPv6.') else: await service.execute('dirsearch -u {http_scheme}://{address}:{port}/ -t ' + str(self.get_option('threads')) + ' -e "' + self.get_option('ext') + '" -f -q -w ' + wordlist + ' --format=plain -o "{scandir}/{protocol}_{port}_{http_scheme}_dirsearch_' + name + '.txt"') elif self.get_option('tool') == 'ffuf': @@ -191,6 +191,42 @@ class Nikto(ServiceScan): if service.target.ipversion == 'IPv4': service.add_manual_command('(nikto) old but generally reliable web server enumeration tool:', 'nikto -ask=no -h {http_scheme}://{address}:{port} 2>&1 | tee "{scandir}/{protocol}_{port}_{http_scheme}_nikto.txt"') +class VirtualHost(ServiceScan): + + def __init__(self): + super().__init__() + self.name = 'Virtual Host Enumeration' + self.slug = 'vhost-enum' + self.tags = ['default', 'safe', 'http', 'long'] + + def configure(self): + self.add_option('hostname', help='The hostname to use as the base host (e.g. example.com) for virtual host enumeration. Default: %(default)s') + self.add_list_option('wordlist', default=['/usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt'], help='The wordlist(s) to use when enumerating virtual hosts. Separate multiple wordlists with spaces. Default: %(default)s') + self.add_option('threads', default=10, help='The number of threads to use when enumerating virtual hosts. Default: %(default)s') + self.match_service_name('^http') + self.match_service_name('^nacn_http$', negative_match=True) + + def check(self): + if which('gobuster') is None: + self.error('The gobuster program could not be found. Make sure it is installed. (On Kali, run: sudo apt install gobuster)') + + async def run(self, service): + hostnames = [] + if self.get_option('hostname'): + hostnames.append(self.get_option('hostname')) + if service.target.type == 'hostname' and service.target.address not in hostnames: + hostnames.append(service.target.address) + if self.get_global('domain') and self.get_global('domain') not in hostnames: + hostnames.append(self.get_global('domain')) + + if len(hostnames) > 0: + for wordlist in self.get_option('wordlist'): + name = os.path.splitext(os.path.basename(wordlist))[0] + for hostname in hostnames: + await service.execute('gobuster vhost -u {http_scheme}://' + hostname + ':{port}/ -t ' + str(self.get_option('threads')) + ' -w ' + wordlist + ' -r -o "{scandir}/{protocol}_{port}_{http_scheme}_' + hostname + '_vhosts_' + name + '.txt"') + else: + service.info('The target was not a hostname, nor was a hostname provided as an option. Skipping virtual host enumeration.') + class WhatWeb(ServiceScan): def __init__(self): @@ -219,7 +255,7 @@ class WkHTMLToImage(ServiceScan): def check(self): if which('wkhtmltoimage') is None: - error('The wkhtmltoimage program could not be found. Make sure it is installed. (On Kali, run: sudo apt install wkhtmltopdf)') + self.error('The wkhtmltoimage program could not be found. Make sure it is installed. (On Kali, run: sudo apt install wkhtmltopdf)') async def run(self, service): if which('wkhtmltoimage') is not None: @@ -239,37 +275,3 @@ class WPScan(ServiceScan): def manual(self, service, plugin_was_run): service.add_manual_command('(wpscan) WordPress Security Scanner (useful if WordPress is found):', 'wpscan --url {http_scheme}://{addressv6}:{port}/ --no-update -e vp,vt,tt,cb,dbe,u,m --plugins-detection aggressive --plugins-version-detection aggressive -f cli-no-color 2>&1 | tee "{scandir}/{protocol}_{port}_{http_scheme}_wpscan.txt"') - -class VirtualHost(ServiceScan): - - def __init__(self): - super().__init__() - self.name = 'Virtual Host Enumeration' - self.slug = 'vhost-enum' - self.tags = ['default', 'safe', 'http', 'long'] - - def configure(self): - self.add_option('hostname', help='The hostname to use as the base host (e.g. example.com) for virtual host enumeration. Default: %(default)s') - self.add_list_option('wordlist', default=['/usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt'], help='The wordlist(s) to use when enumerating virtual hosts. Separate multiple wordlists with spaces. Default: %(default)s') - self.add_option('threads', default=10, help='The number of threads to use when enumerating virtual hosts. Default: %(default)s') - self.match_service_name('^http') - self.match_service_name('^nacn_http$', negative_match=True) - - def check(self): - if which('gobuster') is None: - error('The gobuster program could not be found. Make sure it is installed. (On Kali, run: sudo apt install gobuster)') - - async def run(self, service): - if service.target.type == 'hostname' or self.get_option('hostname') or self.get_global('domain'): - if self.get_option('hostname'): - hostname = self.get_option('hostname') - elif service.target.type == 'hostname': - hostname = service.target.address - else: - hostname = self.get_global('domain') - - for wordlist in self.get_option('wordlist'): - name = os.path.splitext(os.path.basename(wordlist))[0] - await service.execute('gobuster vhost -u {http_scheme}://' + hostname + ':{port}/ -t ' + str(self.get_option('threads')) + ' -w ' + wordlist + ' -r -o "{scandir}/{protocol}_{port}_{http_scheme}_vhosts_' + name + '.txt"') - else: - info('The target was not a hostname, nor was a hostname provided as an option. Skipping virtual host enumeration.') diff --git a/autorecon/default-plugins/redis.py b/autorecon/default-plugins/redis.py index 4603904..3be34ed 100644 --- a/autorecon/default-plugins/redis.py +++ b/autorecon/default-plugins/redis.py @@ -1,5 +1,4 @@ from autorecon.plugins import ServiceScan -from autorecon.io import error from shutil import which class NmapRedis(ServiceScan): @@ -27,7 +26,7 @@ class RedisCli(ServiceScan): def check(self): if which('redis-cli') is None: - error('The redis-cli program could not be found. Make sure it is installed. (On Kali, run: sudo apt install redis-tools)') + self.error('The redis-cli program could not be found. Make sure it is installed. (On Kali, run: sudo apt install redis-tools)') async def run(self, service): if which('redis-cli') is not None: diff --git a/autorecon/default-plugins/rpc.py b/autorecon/default-plugins/rpc.py index c48a1ff..e160c11 100644 --- a/autorecon/default-plugins/rpc.py +++ b/autorecon/default-plugins/rpc.py @@ -1,5 +1,4 @@ from autorecon.plugins import ServiceScan -from autorecon.io import error, warn class NmapRPC(ServiceScan): @@ -35,7 +34,7 @@ class RPCDump(ServiceScan): self.tags = ['default', 'safe', 'rpc'] def configure(self): - self.match_service_name(['^msrpc', '^rpcbind', '^erpc']) + self.match_service_name(['^msrpc', '^rpcbind', '^erpc', '^ncacn_http$']) self.match_port('tcp', [135, 139, 443, 445, 593]) async def run(self, service): diff --git a/autorecon/main.py b/autorecon/main.py index f9eedc7..16c8ebe 100644 --- a/autorecon/main.py +++ b/autorecon/main.py @@ -17,7 +17,7 @@ from autorecon.io import slugify, e, fformat, cprint, debug, info, warn, error, from autorecon.plugins import Pattern, PortScan, ServiceScan, Report, AutoRecon from autorecon.targets import Target, Service -VERSION = "2.0.11" +VERSION = "2.0.12" if not os.path.exists(config['config_dir']): shutil.rmtree(config['config_dir'], ignore_errors=True, onerror=None) diff --git a/autorecon/plugins.py b/autorecon/plugins.py index e3eb295..a3a49da 100644 --- a/autorecon/plugins.py +++ b/autorecon/plugins.py @@ -1,7 +1,7 @@ import asyncio, inspect, os, re, sys from typing import final from autorecon.config import config -from autorecon.io import slugify, error, fail, CommandStreamReader +from autorecon.io import slugify, info, warn, error, fail, CommandStreamReader from autorecon.targets import Service class Pattern: @@ -90,6 +90,18 @@ class Plugin(object): except re.error: fail('Error: The pattern "' + pattern + '" in the plugin "' + self.name + '" is invalid regex.') + @final + def info(self, msg, verbosity=0): + info('{bright}[{bgreen}' + self.slug + '{crst}]{rst} ' + msg) + + @final + def warn(self, msg, verbosity=0): + warn('{bright}[{bgreen}' + self.slug + '{crst}]{rst} ' + msg) + + @final + def error(self, msg, verbosity=0): + error('{bright}[{bgreen}' + self.slug + '{crst}]{rst} ' + msg) + class PortScan(Plugin): def __init__(self): diff --git a/autorecon/targets.py b/autorecon/targets.py index f07f959..19a1b5c 100644 --- a/autorecon/targets.py +++ b/autorecon/targets.py @@ -1,7 +1,7 @@ import asyncio, inspect, os from typing import final from autorecon.config import config -from autorecon.io import e, info +from autorecon.io import e, info, warn, error class Target: @@ -31,6 +31,21 @@ class Target: async def extract_services(self, stream, regex=None): return await self.autorecon.extract_services(stream, regex) + @final + def info(self, msg, verbosity=0): + plugin = inspect.currentframe().f_back.f_locals['self'] + info('{bright}[{yellow}' + self.address + '{crst}/{bgreen}' + plugin.slug + '{crst}]{rst} ' + msg) + + @final + def warn(self, msg, verbosity=0): + plugin = inspect.currentframe().f_back.f_locals['self'] + warn('{bright}[{yellow}' + self.address + '{crst}/{bgreen}' + plugin.slug + '{crst}]{rst} ' + msg) + + @final + def error(self, msg, verbosity=0): + plugin = inspect.currentframe().f_back.f_locals['self'] + error('{bright}[{yellow}' + self.address + '{crst}/{bgreen}' + plugin.slug + '{crst}]{rst} ' + msg) + async def execute(self, cmd, blocking=True, outfile=None, errfile=None, future_outfile=None): target = self @@ -120,6 +135,21 @@ class Service: def add_manual_command(self, description, command): self.add_manual_commands(description, command) + @final + def info(self, msg): + plugin = inspect.currentframe().f_back.f_locals['self'] + info('{bright}[{yellow}' + self.target.address + '{crst}/{bgreen}' + self.tag() + '/' + plugin.slug + '{crst}]{rst} ' + msg) + + @final + def warn(self, msg): + plugin = inspect.currentframe().f_back.f_locals['self'] + warn('{bright}[{yellow}' + self.target.address + '{crst}/{bgreen}' + self.tag() + '/' + plugin.slug + '{crst}]{rst} ' + msg) + + @final + def error(self, msg): + plugin = inspect.currentframe().f_back.f_locals['self'] + error('{bright}[{yellow}' + self.target.address + '{crst}/{bgreen}' + self.tag() + '/' + plugin.slug + '{crst}]{rst} ' + msg) + @final async def execute(self, cmd, blocking=True, outfile=None, errfile=None, future_outfile=None): target = self.target diff --git a/pyproject.toml b/pyproject.toml index cb00b1b..7653880 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "autorecon" -version = "2.0.11" +version = "2.0.12" description = "A multi-threaded network reconnaissance tool which performs automated enumeration of services." authors = ["Tib3rius"] license = "GNU GPL v3"