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.
This commit is contained in:
Tib3rius 2022-01-13 18:33:45 -05:00
parent fbe3fb48c3
commit 4912a235ad
10 changed files with 170 additions and 98 deletions

View File

@ -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')

View File

@ -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.')

View File

@ -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 <DOMAIN-NAME> 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 <DOMAIN-NAME> 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 = '<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 = '<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.')

View File

@ -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.')

View File

@ -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:

View File

@ -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):

View File

@ -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)

View File

@ -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):

View File

@ -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

View File

@ -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"