From 406f6cba78ef31e354db6d4541b1d6651d48c01d Mon Sep 17 00:00:00 2001 From: Tib3rius <48113936+Tib3rius@users.noreply.github.com> Date: Tue, 30 Aug 2022 23:29:20 -0400 Subject: [PATCH 01/11] SMB Vuln Plugin Update SMB Vuln plugin now runs all smb-vuln-* scripts in one command instead of only three in separate commands. This duplicates some work from the regular Nmap SMB scan but not too much. Manual command will only scan smb-vuln-* scripts that are marked as "dos". --- autorecon/default-plugins/smb-vuln.py | 10 ++-------- autorecon/main.py | 2 +- pyproject.toml | 2 +- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/autorecon/default-plugins/smb-vuln.py b/autorecon/default-plugins/smb-vuln.py index ab1c8d5..d82ec6f 100644 --- a/autorecon/default-plugins/smb-vuln.py +++ b/autorecon/default-plugins/smb-vuln.py @@ -11,14 +11,8 @@ class SMBVuln(ServiceScan): self.match_service_name(['^smb', '^microsoft\-ds', '^netbios']) async def run(self, service): - await service.execute('nmap {nmap_extra} -sV -p {port} --script="smb-vuln-ms06-025" --script-args="unsafe=1" -oN "{scandir}/{protocol}_{port}_smb_ms06-025.txt" -oX "{scandir}/xml/{protocol}_{port}_smb_ms06-025.xml" {address}') - await service.execute('nmap {nmap_extra} -sV -p {port} --script="smb-vuln-ms07-029" --script-args="unsafe=1" -oN "{scandir}/{protocol}_{port}_smb_ms07-029.txt" -oX "{scandir}/xml/{protocol}_{port}_smb_ms07-029.xml" {address}') - await service.execute('nmap {nmap_extra} -sV -p {port} --script="smb-vuln-ms08-067" --script-args="unsafe=1" -oN "{scandir}/{protocol}_{port}_smb_ms08-067.txt" -oX "{scandir}/xml/{protocol}_{port}_smb_ms08-067.xml" {address}') + await service.execute('nmap {nmap_extra} -sV -p {port} --script="smb-vuln-*" --script-args="unsafe=1" -oN "{scandir}/{protocol}_{port}_smb_vulnerabilities.txt" -oX "{scandir}/xml/{protocol}_{port}_smb_vulnerabilities.xml" {address}') def manual(self, service, plugin_was_run): if not plugin_was_run: # Only suggest these if they weren't run. - service.add_manual_commands('Nmap scans for SMB vulnerabilities that could potentially cause a DoS if scanned (according to Nmap). Be careful:', [ - 'nmap {nmap_extra} -sV -p {port} --script="smb-vuln-ms06-025" --script-args="unsafe=1" -oN "{scandir}/{protocol}_{port}_smb_ms06-025.txt" -oX "{scandir}/xml/{protocol}_{port}_smb_ms06-025.xml" {address}', - 'nmap {nmap_extra} -sV -p {port} --script="smb-vuln-ms07-029" --script-args="unsafe=1" -oN "{scandir}/{protocol}_{port}_smb_ms07-029.txt" -oX "{scandir}/xml/{protocol}_{port}_smb_ms07-029.xml" {address}', - 'nmap {nmap_extra} -sV -p {port} --script="smb-vuln-ms08-067" --script-args="unsafe=1" -oN "{scandir}/{protocol}_{port}_smb_ms08-067.txt" -oX "{scandir}/xml/{protocol}_{port}_smb_ms08-067.xml" {address}' - ]) + service.add_manual_commands('Nmap scans for SMB vulnerabilities that could potentially cause a DoS if scanned (according to Nmap). Be careful:', 'nmap {nmap_extra} -sV -p {port} --script="smb-vuln-* and dos" --script-args="unsafe=1" -oN "{scandir}/{protocol}_{port}_smb_vulnerabilities.txt" -oX "{scandir}/xml/{protocol}_{port}_smb_vulnerabilities.xml" {address}') diff --git a/autorecon/main.py b/autorecon/main.py index 0c76c73..dbbec42 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.25" +VERSION = "2.0.26" if not os.path.exists(config['config_dir']): shutil.rmtree(config['config_dir'], ignore_errors=True, onerror=None) diff --git a/pyproject.toml b/pyproject.toml index 9b4d3a3..ab8ec7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "autorecon" -version = "2.0.25" +version = "2.0.26" description = "A multi-threaded network reconnaissance tool which performs automated enumeration of services." authors = ["Tib3rius"] license = "GNU GPL v3" From 8ee03c2dc143cad4bac8107378cc954dc8049453 Mon Sep 17 00:00:00 2001 From: Tib3rius <48113936+Tib3rius@users.noreply.github.com> Date: Thu, 27 Oct 2022 10:56:00 -0400 Subject: [PATCH 02/11] Update lint_python.yml --- .github/workflows/lint_python.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/lint_python.yml b/.github/workflows/lint_python.yml index abfde12..3cf33ed 100644 --- a/.github/workflows/lint_python.yml +++ b/.github/workflows/lint_python.yml @@ -7,8 +7,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 - run: pip install --upgrade pip poetry - - run: pip install bandit black codespell flake8 flake8-bugbear - flake8-comprehensions isort mypy pytest pyupgrade safety + - run: pip install bandit black codespell flake8 flake8-bugbear flake8-comprehensions isort mypy pytest pyupgrade safety requests - run: bandit --recursive --skip B101 . || true # B101 is assert statements - run: black --check . || true - run: codespell --skip="./autorecon/wordlists" # --ignore-words-list="" --skip="*.css,*.js,*.lock" From 25050606c26f8d3db6ae1e2639a8fd8703e7f32f Mon Sep 17 00:00:00 2001 From: Tib3rius <48113936+Tib3rius@users.noreply.github.com> Date: Thu, 27 Oct 2022 10:58:11 -0400 Subject: [PATCH 03/11] Update portscan-top-tcp-ports.py Fixed missing import. --- autorecon/default-plugins/portscan-top-tcp-ports.py | 1 + 1 file changed, 1 insertion(+) diff --git a/autorecon/default-plugins/portscan-top-tcp-ports.py b/autorecon/default-plugins/portscan-top-tcp-ports.py index d2185aa..d332812 100644 --- a/autorecon/default-plugins/portscan-top-tcp-ports.py +++ b/autorecon/default-plugins/portscan-top-tcp-ports.py @@ -1,5 +1,6 @@ from autorecon.plugins import PortScan from autorecon.config import config +import requests class QuickTCPPortScan(PortScan): From f086edce346130b058b91f850f6c3506f3dc47e8 Mon Sep 17 00:00:00 2001 From: Tib3rius <48113936+Tib3rius@users.noreply.github.com> Date: Fri, 28 Oct 2022 01:41:22 -0400 Subject: [PATCH 04/11] Updated plugin check() Return value of check() will deregister the plugin if it is False. Updated several plugins which use check() to return False where appropriate. Added "Report" class to protected classes. --- autorecon/config.py | 2 +- autorecon/default-plugins/dirbuster.py | 45 +++++++++++-------- .../dnsrecon-subdomain-bruteforce.py | 1 + autorecon/default-plugins/dnsrecon.py | 1 + autorecon/default-plugins/oracle-scanner.py | 1 + autorecon/default-plugins/oracle-tnscmd.py | 1 + autorecon/default-plugins/redis-cli.py | 1 + autorecon/default-plugins/wkhtmltoimage.py | 1 + autorecon/main.py | 11 +++-- pyproject.toml | 2 +- 10 files changed, 41 insertions(+), 25 deletions(-) diff --git a/autorecon/config.py b/autorecon/config.py index d4004e9..c8162ce 100644 --- a/autorecon/config.py +++ b/autorecon/config.py @@ -42,7 +42,7 @@ configurable_boolean_keys = [ ] config = { - 'protected_classes': ['autorecon', 'target', 'service', 'commandstreamreader', 'plugin', 'portscan', 'servicescan', 'global', 'pattern'], + 'protected_classes': ['autorecon', 'target', 'service', 'commandstreamreader', 'plugin', 'portscan', 'report', 'servicescan', 'global', 'pattern'], 'service_exceptions': ['mc-nmf', 'ncacn_http', 'smux', 'status', 'tcpwrapped', 'unknown'], 'config_dir': config_dir, 'global_file': None, diff --git a/autorecon/default-plugins/dirbuster.py b/autorecon/default-plugins/dirbuster.py index 99cd998..4180f19 100644 --- a/autorecon/default-plugins/dirbuster.py +++ b/autorecon/default-plugins/dirbuster.py @@ -18,63 +18,70 @@ class DirBuster(ServiceScan): self.add_option('threads', default=10, help='The number of threads to use when directory busting. Default: %(default)s') self.add_option('ext', default='txt,html,php,asp,aspx,jsp', help='The extensions you wish to fuzz (no dot, comma separated). Default: %(default)s') self.add_true_option('recursive', help='Enables recursive searching (where available). Warning: This may cause significant increases to scan times. Default: %(default)s') + self.add_option('extras', default='', help='Any extra options you wish to pass to the tool when it runs. e.g. --dirbuster.extras=\'-s 200,301 --discover-backup\'') self.match_service_name('^http') self.match_service_name('^nacn_http$', negative_match=True) def check(self): tool = self.get_option('tool') - if tool == 'feroxbuster': - if which('feroxbuster') is None: - 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: - 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: - self.error('The dirsearch program could not be found. Make sure it is installed. (On Kali, run: sudo apt install dirsearch)') + if tool == 'feroxbuster' and which('feroxbuster') is None: + self.error('The feroxbuster program could not be found. Make sure it is installed. (On Kali, run: sudo apt install feroxbuster)') + return False + elif tool == 'gobuster' and 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)') + return False + elif tool == 'dirsearch' and which('dirsearch') is None: + self.error('The dirsearch program could not be found. Make sure it is installed. (On Kali, run: sudo apt install dirsearch)') + return False + elif tool == 'ffuf' and which('ffuf') is None: + self.error('The ffuf program could not be found. Make sure it is installed. (On Kali, run: sudo apt install ffuf)') + return False + elif tool == 'dirb' and which('dirb') is None: + self.error('The dirb program could not be found. Make sure it is installed. (On Kali, run: sudo apt install dirb)') + return False 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 ' + ('' if self.get_option('recursive') else '-n ') + '-q -e -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 ' + ('' if self.get_option('recursive') else '-n ') + '-q -e -o "{scandir}/{protocol}_{port}_{http_scheme}_feroxbuster_' + name + '.txt"' + (' ' + self.get_option('extras') if self.get_option('extras') else '')) 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 -o "{scandir}/{protocol}_{port}_{http_scheme}_gobuster_' + name + '.txt"' + (' ' + self.get_option('extras') if self.get_option('extras') else '')) elif self.get_option('tool') == 'dirsearch': if service.target.ipversion == '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 ' + ('-r ' if self.get_option('recursive') else '') + '-w ' + wordlist + ' --format=plain -o "{scandir}/{protocol}_{port}_{http_scheme}_dirsearch_' + name + '.txt"') + await service.execute('dirsearch -u {http_scheme}://{address}:{port}/ -t ' + str(self.get_option('threads')) + ' -e "' + self.get_option('ext') + '" -f -q ' + ('-r ' if self.get_option('recursive') else '') + '-w ' + wordlist + ' --format=plain -o "{scandir}/{protocol}_{port}_{http_scheme}_dirsearch_' + name + '.txt"' + (' ' + self.get_option('extras') if self.get_option('extras') else '')) elif self.get_option('tool') == 'ffuf': - await service.execute('ffuf -u {http_scheme}://{addressv6}:{port}/FUZZ -t ' + str(self.get_option('threads')) + ' -w ' + wordlist + ' -e "' + dot_extensions + '" -v ' + ('-recursion ' if self.get_option('recursive') else '') + '-noninteractive | tee {scandir}/{protocol}_{port}_{http_scheme}_ffuf_' + name + '.txt') + await service.execute('ffuf -u {http_scheme}://{addressv6}:{port}/FUZZ -t ' + str(self.get_option('threads')) + ' -w ' + wordlist + ' -e "' + dot_extensions + '" -v ' + ('-recursion ' if self.get_option('recursive') else '') + '-noninteractive' + (' ' + self.get_option('extras') if self.get_option('extras') else '') + ' | tee {scandir}/{protocol}_{port}_{http_scheme}_ffuf_' + name + '.txt') elif self.get_option('tool') == 'dirb': - await service.execute('dirb {http_scheme}://{addressv6}:{port}/ ' + wordlist + ' -l ' + ('' if self.get_option('recursive') else '-r ') + '-S -X ",' + dot_extensions + '" -o "{scandir}/{protocol}_{port}_{http_scheme}_dirb_' + name + '.txt"') + await service.execute('dirb {http_scheme}://{addressv6}:{port}/ ' + wordlist + ' -l ' + ('' if self.get_option('recursive') else '-r ') + '-S -X ",' + dot_extensions + '" -o "{scandir}/{protocol}_{port}_{http_scheme}_dirb_' + name + '.txt"' + (' ' + self.get_option('extras') if self.get_option('extras') else '')) def manual(self, service, plugin_was_run): dot_extensions = ','.join(['.' + x for x in self.get_option('ext').split(',')]) if self.get_option('tool') == 'feroxbuster': service.add_manual_command('(feroxbuster) Multi-threaded recursive directory/file enumeration for web servers using various wordlists:', [ - 'feroxbuster -u {http_scheme}://{addressv6}:{port} -t ' + str(self.get_option('threads')) + ' -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x "' + self.get_option('ext') + '" -v -k ' + ('' if self.get_option('recursive') else '-n ') + '-e -o {scandir}/{protocol}_{port}_{http_scheme}_feroxbuster_dirbuster.txt' + 'feroxbuster -u {http_scheme}://{addressv6}:{port} -t ' + str(self.get_option('threads')) + ' -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x "' + self.get_option('ext') + '" -v -k ' + ('' if self.get_option('recursive') else '-n ') + '-e -o {scandir}/{protocol}_{port}_{http_scheme}_feroxbuster_dirbuster.txt' + (' ' + self.get_option('extras') if self.get_option('extras') else '') ]) elif self.get_option('tool') == 'gobuster': service.add_manual_command('(gobuster v3) Multi-threaded directory/file enumeration for web servers using various wordlists:', [ - 'gobuster dir -u {http_scheme}://{addressv6}:{port}/ -t ' + str(self.get_option('threads')) + ' -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -e -k -x "' + self.get_option('ext') + '" -o "{scandir}/{protocol}_{port}_{http_scheme}_gobuster_dirbuster.txt"' + 'gobuster dir -u {http_scheme}://{addressv6}:{port}/ -t ' + str(self.get_option('threads')) + ' -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -e -k -x "' + self.get_option('ext') + '" -o "{scandir}/{protocol}_{port}_{http_scheme}_gobuster_dirbuster.txt"' + (' ' + self.get_option('extras') if self.get_option('extras') else '') ]) elif self.get_option('tool') == 'dirsearch': if service.target.ipversion == 'IPv4': service.add_manual_command('(dirsearch) Multi-threaded recursive directory/file enumeration for web servers using various wordlists:', [ - 'dirsearch -u {http_scheme}://{address}:{port}/ -t ' + str(self.get_option('threads')) + ' -e "' + self.get_option('ext') + '" -f ' + ('-r ' if self.get_option('recursive') else '') + '-w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt --format=plain --output="{scandir}/{protocol}_{port}_{http_scheme}_dirsearch_dirbuster.txt"' + 'dirsearch -u {http_scheme}://{address}:{port}/ -t ' + str(self.get_option('threads')) + ' -e "' + self.get_option('ext') + '" -f ' + ('-r ' if self.get_option('recursive') else '') + '-w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt --format=plain --output="{scandir}/{protocol}_{port}_{http_scheme}_dirsearch_dirbuster.txt"' + (' ' + self.get_option('extras') if self.get_option('extras') else '') ]) elif self.get_option('tool') == 'ffuf': service.add_manual_command('(ffuf) Multi-threaded recursive directory/file enumeration for web servers using various wordlists:', [ - 'ffuf -u {http_scheme}://{addressv6}:{port}/FUZZ -t ' + str(self.get_option('threads')) + ' -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -e "' + dot_extensions + '" -v ' + ('-recursion ' if self.get_option('recursive') else '') + '-noninteractive | tee {scandir}/{protocol}_{port}_{http_scheme}_ffuf_dirbuster.txt' + 'ffuf -u {http_scheme}://{addressv6}:{port}/FUZZ -t ' + str(self.get_option('threads')) + ' -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -e "' + dot_extensions + '" -v ' + ('-recursion ' if self.get_option('recursive') else '') + '-noninteractive' + (' ' + self.get_option('extras') if self.get_option('extras') else '') + ' | tee {scandir}/{protocol}_{port}_{http_scheme}_ffuf_dirbuster.txt' ]) elif self.get_option('tool') == 'dirb': service.add_manual_command('(dirb) Recursive directory/file enumeration for web servers using various wordlists:', [ - 'dirb {http_scheme}://{addressv6}:{port}/ /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -l ' + ('' if self.get_option('recursive') else '-r ') + '-X ",' + dot_extensions + '" -o "{scandir}/{protocol}_{port}_{http_scheme}_dirb_dirbuster.txt"' + 'dirb {http_scheme}://{addressv6}:{port}/ /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -l ' + ('' if self.get_option('recursive') else '-r ') + '-X ",' + dot_extensions + '" -o "{scandir}/{protocol}_{port}_{http_scheme}_dirb_dirbuster.txt"' + (' ' + self.get_option('extras') if self.get_option('extras') else '') ]) diff --git a/autorecon/default-plugins/dnsrecon-subdomain-bruteforce.py b/autorecon/default-plugins/dnsrecon-subdomain-bruteforce.py index b9306fb..8f082bb 100644 --- a/autorecon/default-plugins/dnsrecon-subdomain-bruteforce.py +++ b/autorecon/default-plugins/dnsrecon-subdomain-bruteforce.py @@ -16,6 +16,7 @@ class DnsReconSubdomainBruteforce(ServiceScan): 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)') + return False def manual(self, service, plugin_was_run): domain_name = '' diff --git a/autorecon/default-plugins/dnsrecon.py b/autorecon/default-plugins/dnsrecon.py index f53c51d..cfb9edc 100644 --- a/autorecon/default-plugins/dnsrecon.py +++ b/autorecon/default-plugins/dnsrecon.py @@ -16,6 +16,7 @@ class DnsRecon(ServiceScan): 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)') + return False 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.', [ diff --git a/autorecon/default-plugins/oracle-scanner.py b/autorecon/default-plugins/oracle-scanner.py index c610420..16653cc 100644 --- a/autorecon/default-plugins/oracle-scanner.py +++ b/autorecon/default-plugins/oracle-scanner.py @@ -14,6 +14,7 @@ class OracleScanner(ServiceScan): def check(self): if which('oscanner') is None: self.error('The oscanner program could not be found. Make sure it is installed. (On Kali, run: sudo apt install oscanner)') + return False 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/oracle-tnscmd.py b/autorecon/default-plugins/oracle-tnscmd.py index b43ec12..7ea12c1 100644 --- a/autorecon/default-plugins/oracle-tnscmd.py +++ b/autorecon/default-plugins/oracle-tnscmd.py @@ -14,6 +14,7 @@ class OracleTNScmd(ServiceScan): def check(self): if which('tnscmd10g') is None: self.error('The tnscmd10g program could not be found. Make sure it is installed. (On Kali, run: sudo apt install tnscmd10g)') + return False async def run(self, service): if service.target.ipversion == 'IPv4': diff --git a/autorecon/default-plugins/redis-cli.py b/autorecon/default-plugins/redis-cli.py index a6f6c79..74b3803 100644 --- a/autorecon/default-plugins/redis-cli.py +++ b/autorecon/default-plugins/redis-cli.py @@ -14,6 +14,7 @@ class RedisCli(ServiceScan): def check(self): if which('redis-cli') is None: self.error('The redis-cli program could not be found. Make sure it is installed. (On Kali, run: sudo apt install redis-tools)') + return False async def run(self, service): if which('redis-cli') is not None: diff --git a/autorecon/default-plugins/wkhtmltoimage.py b/autorecon/default-plugins/wkhtmltoimage.py index 7c4890a..d4d30ba 100644 --- a/autorecon/default-plugins/wkhtmltoimage.py +++ b/autorecon/default-plugins/wkhtmltoimage.py @@ -15,6 +15,7 @@ class WkHTMLToImage(ServiceScan): def check(self): if which('wkhtmltoimage') is None: self.error('The wkhtmltoimage program could not be found. Make sure it is installed. (On Kali, run: sudo apt install wkhtmltopdf)') + return False async def run(self, service): if which('wkhtmltoimage') is not None: diff --git a/autorecon/main.py b/autorecon/main.py index dbbec42..5faa654 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.26" +VERSION = "2.0.27" if not os.path.exists(config['config_dir']): shutil.rmtree(config['config_dir'], ignore_errors=True, onerror=None) @@ -809,7 +809,7 @@ async def run(): else: config['plugins_dir'] = None - parser = argparse.ArgumentParser(add_help=False, description='Network reconnaissance tool to port scan and automatically enumerate services found on multiple targets.') + parser = argparse.ArgumentParser(add_help=False, allow_abbrev=False, description='Network reconnaissance tool to port scan and automatically enumerate services found on multiple targets.') parser.add_argument('targets', action='store', help='IP addresses (e.g. 10.0.0.1), CIDR notation (e.g. 10.0.0.1/24), or resolvable hostnames (e.g. foo.bar) to scan.', nargs='*') parser.add_argument('-t', '--target-file', action='store', type=str, default='', help='Read targets from file.') parser.add_argument('-p', '--ports', action='store', type=str, help='Comma separated list of ports / port ranges to scan. Specify TCP/UDP ports by prepending list with T:/U: To scan both TCP/UDP, put port(s) at start or specify B: e.g. 53,T:21-25,80,U:123,B:123. Default: %(default)s') @@ -1081,6 +1081,7 @@ async def run(): autorecon.argparse.set_defaults(**{key: val}) parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS, help='Show this help message and exit.') + parser.error = lambda s: fail(s[0].upper() + s[1:]) args = parser.parse_args() args_dict = vars(args) @@ -1138,7 +1139,7 @@ async def run(): else: error('Invalid value provided to --max-plugin-global-instances. Values must be in the format PLUGIN:NUMBER.') - for plugin in autorecon.plugins.values(): + for slug, plugin in autorecon.plugins.items(): if hasattr(plugin, 'max_target_instances') and plugin.slug in max_plugin_target_instances: plugin.max_target_instances = max_plugin_target_instances[plugin.slug] @@ -1147,7 +1148,9 @@ async def run(): for member_name, _ in inspect.getmembers(plugin, predicate=inspect.ismethod): if member_name == 'check': - plugin.check() + if plugin.check() == False: + autorecon.plugins.pop(slug) + continue continue if config['ports']: diff --git a/pyproject.toml b/pyproject.toml index ab8ec7e..9e5d658 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "autorecon" -version = "2.0.26" +version = "2.0.27" description = "A multi-threaded network reconnaissance tool which performs automated enumeration of services." authors = ["Tib3rius"] license = "GNU GPL v3" From 08d8f7b27ce53788be72287b08130e8df201ee2b Mon Sep 17 00:00:00 2001 From: lapinou <10383569+lap1nou@users.noreply.github.com> Date: Fri, 28 Oct 2022 07:44:38 +0200 Subject: [PATCH 05/11] Added enum4linux-ng choice (#173) * Added enum4linux-ng choice * Update enum4linux.py Co-authored-by: Tib3rius <48113936+Tib3rius@users.noreply.github.com> --- autorecon/default-plugins/enum4linux.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/autorecon/default-plugins/enum4linux.py b/autorecon/default-plugins/enum4linux.py index 9ca0a57..1f278fa 100644 --- a/autorecon/default-plugins/enum4linux.py +++ b/autorecon/default-plugins/enum4linux.py @@ -1,4 +1,5 @@ from autorecon.plugins import ServiceScan +from shutil import which class Enum4Linux(ServiceScan): @@ -8,11 +9,25 @@ class Enum4Linux(ServiceScan): self.tags = ['default', 'safe', 'active-directory'] def configure(self): + self.add_choice_option('tool', default=('enum4linux-ng' if which('enum4linux-ng') else 'enum4linux'), choices=['enum4linux-ng', 'enum4linux'], help='The tool to use for doing Windows and Samba enumeration. Default: %(default)s') self.match_service_name(['^ldap', '^smb', '^microsoft\-ds', '^netbios']) self.match_port('tcp', [139, 389, 445]) self.match_port('udp', 137) self.run_once(True) + def check(self): + tool = self.get_option('tool') + if tool == 'enum4linux' and which('enum4linux') is None: + self.error('The enum4linux program could not be found. Make sure it is installed. (On Kali, run: sudo apt install enum4linux)') + return False + elif tool == 'enum4linux-ng' and which('enum4linux-ng') is None: + self.error('The enum4linux-ng program could not be found. Make sure it is installed. (https://github.com/cddmp/enum4linux-ng)') + return False + async def run(self, service): if service.target.ipversion == 'IPv4': - await service.execute('enum4linux -a -M -l -d {address} 2>&1', outfile='enum4linux.txt') + if self.tool is not None: + if self.tool == 'enum4linux': + await service.execute('enum4linux -a -M -l -d {address} 2>&1', outfile='enum4linux.txt') + elif self.tool == 'enum4linux-ng': + await service.execute('enum4linux-ng -A -d -v {address} 2>&1', outfile='enum4linux-ng.txt') From da718cea253f261c0bca0024b1dd0f0c619dff56 Mon Sep 17 00:00:00 2001 From: Tib3rius <48113936+Tib3rius@users.noreply.github.com> Date: Fri, 4 Nov 2022 15:45:07 -0400 Subject: [PATCH 06/11] Fixed bug in enum4linux plugin. Fixes #178 --- autorecon/default-plugins/enum4linux.py | 7 ++++--- autorecon/main.py | 2 +- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/autorecon/default-plugins/enum4linux.py b/autorecon/default-plugins/enum4linux.py index 1f278fa..ac3e787 100644 --- a/autorecon/default-plugins/enum4linux.py +++ b/autorecon/default-plugins/enum4linux.py @@ -26,8 +26,9 @@ class Enum4Linux(ServiceScan): async def run(self, service): if service.target.ipversion == 'IPv4': - if self.tool is not None: - if self.tool == 'enum4linux': + tool = self.get_option('tool') + if tool is not None: + if tool == 'enum4linux': await service.execute('enum4linux -a -M -l -d {address} 2>&1', outfile='enum4linux.txt') - elif self.tool == 'enum4linux-ng': + elif tool == 'enum4linux-ng': await service.execute('enum4linux-ng -A -d -v {address} 2>&1', outfile='enum4linux-ng.txt') diff --git a/autorecon/main.py b/autorecon/main.py index 5faa654..b2de01a 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.27" +VERSION = "2.0.28" if not os.path.exists(config['config_dir']): shutil.rmtree(config['config_dir'], ignore_errors=True, onerror=None) diff --git a/pyproject.toml b/pyproject.toml index 9e5d658..dab9e19 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "autorecon" -version = "2.0.27" +version = "2.0.28" description = "A multi-threaded network reconnaissance tool which performs automated enumeration of services." authors = ["Tib3rius"] license = "GNU GPL v3" From c67909f21bd3a1b2f3c7e8a717f5627151db6e5c Mon Sep 17 00:00:00 2001 From: Tib3rius <48113936+Tib3rius@users.noreply.github.com> Date: Tue, 29 Nov 2022 12:04:15 -0500 Subject: [PATCH 07/11] Added ability to override unresolvable hosts failure. Using --disable-sanity-checks will allow AutoRecon to run even if target(s) were unresolvable (one or more targets must be valid however). Added a new plugin for ajp. --- autorecon/config.py | 2 +- autorecon/default-plugins/nmap-ajp.py | 14 ++++++++++++++ autorecon/main.py | 11 ++++++++--- pyproject.toml | 2 +- 4 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 autorecon/default-plugins/nmap-ajp.py diff --git a/autorecon/config.py b/autorecon/config.py index c8162ce..997a20e 100644 --- a/autorecon/config.py +++ b/autorecon/config.py @@ -43,7 +43,7 @@ configurable_boolean_keys = [ config = { 'protected_classes': ['autorecon', 'target', 'service', 'commandstreamreader', 'plugin', 'portscan', 'report', 'servicescan', 'global', 'pattern'], - 'service_exceptions': ['mc-nmf', 'ncacn_http', 'smux', 'status', 'tcpwrapped', 'unknown'], + 'service_exceptions': ['infocrypt', 'mc-nmf', 'ncacn_http', 'smux', 'status', 'tcpwrapped', 'unknown'], 'config_dir': config_dir, 'global_file': None, 'ports': None, diff --git a/autorecon/default-plugins/nmap-ajp.py b/autorecon/default-plugins/nmap-ajp.py new file mode 100644 index 0000000..fcba5a0 --- /dev/null +++ b/autorecon/default-plugins/nmap-ajp.py @@ -0,0 +1,14 @@ +from autorecon.plugins import ServiceScan + +class NmapAJP(ServiceScan): + + def __init__(self): + super().__init__() + self.name = 'Nmap AJP' + self.tags = ['default', 'safe', 'ajp'] + + def configure(self): + self.match_service_name(['^ajp13']) + + async def run(self, service): + await service.execute('nmap {nmap_extra} -sV -p {port} --script="banner,(ajp-* or ssl*) and not (brute or broadcast or dos or external or fuzzer)" -oN "{scandir}/{protocol}_{port}_ajp_nmap.txt" -oX "{scandir}/xml/{protocol}_{port}_ajp_nmap.xml" {address}') diff --git a/autorecon/main.py b/autorecon/main.py index b2de01a..2569bf5 100644 --- a/autorecon/main.py +++ b/autorecon/main.py @@ -4,7 +4,7 @@ import argparse, asyncio, importlib.util, inspect, ipaddress, math, os, re, sele from datetime import datetime try: - import appdirs, colorama, toml, unidecode + import appdirs, colorama, impacket, requests, toml, unidecode from colorama import Fore, Style except ModuleNotFoundError: print('One or more required modules was not installed. Please run or re-run: ' + ('sudo ' if os.getuid() == 0 else '') + 'python3 -m pip install -r requirements.txt') @@ -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.28" +VERSION = "2.0.29" if not os.path.exists(config['config_dir']): shutil.rmtree(config['config_dir'], ignore_errors=True, onerror=None) @@ -1318,6 +1318,7 @@ async def run(): error('The target file ' + args.target_file + ' could not be read.') sys.exit(1) + unresolvable_targets = False for target in raw_targets: try: ip = ipaddress.ip_address(target) @@ -1397,8 +1398,12 @@ async def run(): autorecon.pending_targets.append(Target(target, ip, 'IPv6', 'hostname', autorecon)) except socket.gaierror: + unresolvable_targets = True error(target + ' does not appear to be a valid IP address, IP range, or resolvable hostname.') - errors = True + + if not args.disable_sanity_checks and unresolvable_targets == True: + error('AutoRecon will not run if any targets are invalid / unresolvable. To override this, re-run with the --disable-sanity-checks option.') + errors = True if len(autorecon.pending_targets) == 0: error('You must specify at least one target to scan!') diff --git a/pyproject.toml b/pyproject.toml index dab9e19..46845ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "autorecon" -version = "2.0.28" +version = "2.0.29" description = "A multi-threaded network reconnaissance tool which performs automated enumeration of services." authors = ["Tib3rius"] license = "GNU GPL v3" From 65fc104b2d3953211d8430080360c68df4cf4c37 Mon Sep 17 00:00:00 2001 From: Tib3rius <48113936+Tib3rius@users.noreply.github.com> Date: Wed, 7 Dec 2022 15:12:09 -0500 Subject: [PATCH 08/11] Updates & bug fixes. Plugins & wordlists are now stored in ~/.local/share/AutoRecon. Default config / global config files are still stored in ~/.config/AutoRecon. Removed feature which used the cwd's config.toml, global.toml, and plugins dir if detected. Fixed bug which wouldn't generate combined reports in some cases. --- autorecon/config.py | 2 + autorecon/default-plugins/dirbuster.py | 2 +- autorecon/main.py | 66 +++++++++++++++----------- pyproject.toml | 2 +- 4 files changed, 41 insertions(+), 31 deletions(-) diff --git a/autorecon/config.py b/autorecon/config.py index 997a20e..1fcfd34 100644 --- a/autorecon/config.py +++ b/autorecon/config.py @@ -1,6 +1,7 @@ import appdirs, os config_dir = appdirs.user_config_dir('AutoRecon') +data_dir = appdirs.user_data_dir('AutoRecon') configurable_keys = [ 'ports', @@ -45,6 +46,7 @@ config = { 'protected_classes': ['autorecon', 'target', 'service', 'commandstreamreader', 'plugin', 'portscan', 'report', 'servicescan', 'global', 'pattern'], 'service_exceptions': ['infocrypt', 'mc-nmf', 'ncacn_http', 'smux', 'status', 'tcpwrapped', 'unknown'], 'config_dir': config_dir, + 'data_dir': data_dir, 'global_file': None, 'ports': None, 'max_scans': 50, diff --git a/autorecon/default-plugins/dirbuster.py b/autorecon/default-plugins/dirbuster.py index 4180f19..5f77851 100644 --- a/autorecon/default-plugins/dirbuster.py +++ b/autorecon/default-plugins/dirbuster.py @@ -14,7 +14,7 @@ class DirBuster(ServiceScan): def configure(self): self.add_choice_option('tool', default='feroxbuster', choices=['feroxbuster', 'gobuster', 'dirsearch', 'ffuf', 'dirb'], help='The tool to use for directory busting. Default: %(default)s') - self.add_list_option('wordlist', default=[os.path.join(config['config_dir'], 'wordlists', 'dirbuster.txt')], help='The wordlist(s) to use when directory busting. Separate multiple wordlists with spaces. Default: %(default)s') + self.add_list_option('wordlist', default=[os.path.join(config['data_dir'], 'wordlists', 'dirbuster.txt')], help='The wordlist(s) to use when directory busting. Separate multiple wordlists with spaces. Default: %(default)s') self.add_option('threads', default=10, help='The number of threads to use when directory busting. Default: %(default)s') self.add_option('ext', default='txt,html,php,asp,aspx,jsp', help='The extensions you wish to fuzz (no dot, comma separated). Default: %(default)s') self.add_true_option('recursive', help='Enables recursive searching (where available). Warning: This may cause significant increases to scan times. Default: %(default)s') diff --git a/autorecon/main.py b/autorecon/main.py index 2569bf5..67e0855 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.29" +VERSION = "2.0.30" if not os.path.exists(config['config_dir']): shutil.rmtree(config['config_dir'], ignore_errors=True, onerror=None) @@ -25,19 +25,29 @@ if not os.path.exists(config['config_dir']): open(os.path.join(config['config_dir'], 'VERSION-' + VERSION), 'a').close() shutil.copy(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'config.toml'), os.path.join(config['config_dir'], 'config.toml')) shutil.copy(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'global.toml'), os.path.join(config['config_dir'], 'global.toml')) - shutil.copytree(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'default-plugins'), os.path.join(config['config_dir'], 'plugins')) - shutil.copytree(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'wordlists'), os.path.join(config['config_dir'], 'wordlists')) else: if not os.path.exists(os.path.join(config['config_dir'], 'config.toml')): shutil.copy(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'config.toml'), os.path.join(config['config_dir'], 'config.toml')) if not os.path.exists(os.path.join(config['config_dir'], 'global.toml')): shutil.copy(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'global.toml'), os.path.join(config['config_dir'], 'global.toml')) - if not os.path.exists(os.path.join(config['config_dir'], 'plugins')): - shutil.copytree(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'default-plugins'), os.path.join(config['config_dir'], 'plugins')) - if not os.path.exists(os.path.join(config['config_dir'], 'wordlists')): - shutil.copytree(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'wordlists'), os.path.join(config['config_dir'], 'wordlists')) if not os.path.exists(os.path.join(config['config_dir'], 'VERSION-' + VERSION)): - warn('It looks like the config/plugins in ' + config['config_dir'] + ' are outdated. Please remove the ' + config['config_dir'] + ' directory and re-run AutoRecon to rebuild them.') + warn('It looks like the config in ' + config['config_dir'] + ' is outdated. Please remove the ' + config['config_dir'] + ' directory and re-run AutoRecon to rebuild it.') + + +if not os.path.exists(config['data_dir']): + shutil.rmtree(config['data_dir'], ignore_errors=True, onerror=None) + os.makedirs(config['data_dir'], exist_ok=True) + open(os.path.join(config['data_dir'], 'VERSION-' + VERSION), 'a').close() + shutil.copytree(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'default-plugins'), os.path.join(config['data_dir'], 'plugins')) + shutil.copytree(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'wordlists'), os.path.join(config['data_dir'], 'wordlists')) +else: + if not os.path.exists(os.path.join(config['data_dir'], 'plugins')): + shutil.copytree(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'default-plugins'), os.path.join(config['data_dir'], 'plugins')) + if not os.path.exists(os.path.join(config['data_dir'], 'wordlists')): + shutil.copytree(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'wordlists'), os.path.join(config['data_dir'], 'wordlists')) + if not os.path.exists(os.path.join(config['data_dir'], 'VERSION-' + VERSION)): + warn('It looks like the plugins in ' + config['data_dir'] + ' are outdated. Please remove the ' + config['data_dir'] + ' directory and re-run AutoRecon to rebuild them.') + # Save current terminal settings so we can restore them. terminal_settings = termios.tcgetattr(sys.stdin.fileno()) @@ -786,26 +796,20 @@ async def scan_target(target): async def run(): # Find config file. - if os.path.isfile(os.path.join(os.getcwd(), 'config.toml')): - config_file = os.path.join(os.getcwd(), 'config.toml') - elif os.path.isfile(os.path.join(config['config_dir'], 'config.toml')): + if os.path.isfile(os.path.join(config['config_dir'], 'config.toml')): config_file = os.path.join(config['config_dir'], 'config.toml') else: config_file = None # Find global file. - if os.path.isfile(os.path.join(os.getcwd(), 'global.toml')): - config['global_file'] = os.path.join(os.getcwd(), 'global.toml') - elif os.path.isfile(os.path.join(config['config_dir'], 'global.toml')): + if os.path.isfile(os.path.join(config['config_dir'], 'global.toml')): config['global_file'] = os.path.join(config['config_dir'], 'global.toml') else: config['global_file'] = None # Find plugins. - if os.path.isdir(os.path.join(os.getcwd(), 'plugins')): - config['plugins_dir'] = os.path.join(os.getcwd(), 'plugins') - elif os.path.isdir(os.path.join(config['config_dir'], 'plugins')): - config['plugins_dir'] = os.path.join(config['config_dir'], 'plugins') + if os.path.isdir(os.path.join(config['data_dir'], 'plugins')): + config['plugins_dir'] = os.path.join(config['data_dir'], 'plugins') else: config['plugins_dir'] = None @@ -1515,19 +1519,23 @@ async def run(): # If there's only one target we don't need a combined report if len(autorecon.completed_targets) > 1: for plugin in autorecon.plugin_types['report']: - plugin_tag_set = set(plugin.tags) + if config['reports'] and plugin.slug in config['reports']: + matching_tags = True + excluded_tags = False + else: + plugin_tag_set = set(plugin.tags) - matching_tags = False - for tag_group in autorecon.tags: - if set(tag_group).issubset(plugin_tag_set): - matching_tags = True - break + matching_tags = False + for tag_group in autorecon.tags: + if set(tag_group).issubset(plugin_tag_set): + matching_tags = True + break - excluded_tags = False - for tag_group in autorecon.excluded_tags: - if set(tag_group).issubset(plugin_tag_set): - excluded_tags = True - break + excluded_tags = False + for tag_group in autorecon.excluded_tags: + if set(tag_group).issubset(plugin_tag_set): + excluded_tags = True + break if matching_tags and not excluded_tags: pending.add(asyncio.create_task(generate_report(plugin, autorecon.completed_targets))) diff --git a/pyproject.toml b/pyproject.toml index 46845ba..32fe5ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "autorecon" -version = "2.0.29" +version = "2.0.30" description = "A multi-threaded network reconnaissance tool which performs automated enumeration of services." authors = ["Tib3rius"] license = "GNU GPL v3" From 851ffbd316a4072f6b03f46ec048013137d28801 Mon Sep 17 00:00:00 2001 From: Tib3rius <48113936+Tib3rius@users.noreply.github.com> Date: Tue, 10 Jan 2023 16:24:14 -0500 Subject: [PATCH 09/11] Plugin updates, bug fix, and feature update. A few manual plugin updates (command formatting etc.) Fixed bug where processes were left running after AutoRecon is cancelled and/or times out. Status messages now include PIDs of running processes if verbosity >= 2. Closes #183 Fixes #184 --- autorecon/default-plugins/bruteforce-smb.py | 17 ++++ autorecon/default-plugins/lookup-sid.py | 2 +- autorecon/default-plugins/winrm-detection.py | 8 +- autorecon/main.py | 97 +++++++++++++++----- pyproject.toml | 3 +- requirements.txt | 1 + 6 files changed, 101 insertions(+), 27 deletions(-) create mode 100644 autorecon/default-plugins/bruteforce-smb.py diff --git a/autorecon/default-plugins/bruteforce-smb.py b/autorecon/default-plugins/bruteforce-smb.py new file mode 100644 index 0000000..da658cc --- /dev/null +++ b/autorecon/default-plugins/bruteforce-smb.py @@ -0,0 +1,17 @@ +from autorecon.plugins import ServiceScan + +class BruteforceSMB(ServiceScan): + + def __init__(self): + super().__init__() + self.name = 'Bruteforce SMB' + self.tags = ['default', 'safe', 'active-directory'] + + def configure(self): + self.match_service('tcp', 445, '^microsoft\-ds') + self.match_service('tcp', 139, '^netbios') + + def manual(self, service, plugin_was_run): + service.add_manual_command('Bruteforce SMB', [ + 'crackmapexec smb {address} --port={port} -u "' + self.get_global('username_wordlist', default='/usr/share/seclists/Usernames/top-usernames-shortlist.txt') + '" -p "' + self.get_global('password_wordlist', default='/usr/share/seclists/Passwords/darkweb2017-top100.txt') + '"' + ]) diff --git a/autorecon/default-plugins/lookup-sid.py b/autorecon/default-plugins/lookup-sid.py index 7136262..5c4bb4d 100644 --- a/autorecon/default-plugins/lookup-sid.py +++ b/autorecon/default-plugins/lookup-sid.py @@ -12,5 +12,5 @@ class LookupSID(ServiceScan): def manual(self, service, plugin_was_run): service.add_manual_command('Lookup SIDs', [ - 'lookupsid.py [username]:[password]@{address}' + 'impacket-lookupsid \'[username]:[password]@{address}\'' ]) diff --git a/autorecon/default-plugins/winrm-detection.py b/autorecon/default-plugins/winrm-detection.py index a0aa97c..8742f47 100644 --- a/autorecon/default-plugins/winrm-detection.py +++ b/autorecon/default-plugins/winrm-detection.py @@ -19,14 +19,14 @@ class WinRMDetection(ServiceScan): def manual(self, service, plugin_was_run): service.add_manual_commands('Bruteforce logins:', [ - 'crackmapexec winrm {address} -d ' + self.get_global('domain', default='') + ' -u ' + self.get_global('username_wordlist', default='/usr/share/seclists/Usernames/top-usernames-shortlist.txt') + ' -p ' + self.get_global('password_wordlist', default='/usr/share/seclists/Passwords/darkweb2017-top100.txt') + 'crackmapexec winrm {address} -d \'' + self.get_global('domain', default='') + '\' -u \'' + self.get_global('username_wordlist', default='/usr/share/seclists/Usernames/top-usernames-shortlist.txt') + '\' -p \'' + self.get_global('password_wordlist', default='/usr/share/seclists/Passwords/darkweb2017-top100.txt') + '\'' ]) service.add_manual_commands('Check login (requires credentials):', [ - 'crackmapexec winrm {address} -d ' + self.get_global('domain', default='') + ' -u -p -x "whoami"' + 'crackmapexec winrm {address} -d \'' + self.get_global('domain', default='') + '\' -u \'\' -p \'\'' ]) service.add_manual_commands('Evil WinRM (gem install evil-winrm):', [ - 'evil-winrm -u -p -i {address}', - 'evil-winrm -u -H -i {address}' + 'evil-winrm -u \'\' -p \'\' -i {address}', + 'evil-winrm -u \'\' -H \'\' -i {address}' ]) diff --git a/autorecon/main.py b/autorecon/main.py index 67e0855..f9542de 100644 --- a/autorecon/main.py +++ b/autorecon/main.py @@ -4,7 +4,7 @@ import argparse, asyncio, importlib.util, inspect, ipaddress, math, os, re, sele from datetime import datetime try: - import appdirs, colorama, impacket, requests, toml, unidecode + import appdirs, colorama, impacket, psutil, requests, toml, unidecode from colorama import Fore, Style except ModuleNotFoundError: print('One or more required modules was not installed. Please run or re-run: ' + ('sudo ' if os.getuid() == 0 else '') + 'python3 -m pip install -r requirements.txt') @@ -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.30" +VERSION = "2.0.31" if not os.path.exists(config['config_dir']): shutil.rmtree(config['config_dir'], ignore_errors=True, onerror=None) @@ -92,17 +92,35 @@ def calculate_elapsed_time(start_time, short=False): else: return ', '.join(elapsed_time) -def cancel_all_tasks(signal, frame): +# sig and frame args are only present so the function +# works with signal.signal() and handles Ctrl-C. +# They are not used for any other purpose. +def cancel_all_tasks(sig, frame): for task in asyncio.all_tasks(): task.cancel() + processes = [] + for target in autorecon.scanning_targets: for process_list in target.running_tasks.values(): for process_dict in process_list['processes']: try: - process_dict['process'].kill() - except ProcessLookupError: # Will get raised if the process finishes before we get to killing it. + parent = psutil.Process(process_dict['process'].pid) + processes.extend(parent.children(recursive=True)) + processes.append(parent) + except psutil.NoSuchProcess: pass + + for process in processes: + try: + process.send_signal(signal.SIGKILL) + except psutil.NoSuchProcess: # Will get raised if the process finishes before we get to killing it. + pass + + _, alive = psutil.wait_procs(processes, timeout=10) + if len(alive) > 0: + error('The following process IDs could not be killed: ' + ', '.join([str(x.pid) for x in sorted(alive, key=lambda x: x.pid)])) + if not config['disable_keyboard_control']: # Restore original terminal settings. @@ -114,9 +132,28 @@ async def start_heartbeat(target, period=60): async with target.lock: count = len(target.running_tasks) - tasks_list = '' + tasks_list = [] if config['verbose'] >= 1: - tasks_list = ': {bblue}' + ', '.join(target.running_tasks.keys()) + '{rst}' + for tag, task in target.running_tasks.items(): + task_str = tag + + if config['verbose'] >= 2: + processes = [] + for process_dict in task['processes']: + if process_dict['process'].returncode is None: + processes.append(str(process_dict['process'].pid)) + try: + for child in psutil.Process(process_dict['process'].pid).children(recursive=True): + processes.append(str(child.pid)) + except psutil.NoSuchProcess: + pass + + if processes: + task_str += ' (PID' + ('s' if len(processes) > 1 else '') + ': ' + ', '.join(processes) + ')' + + tasks_list.append(task_str) + + tasks_list = ': {bblue}' + ', '.join(tasks_list) + '{rst}' current_time = datetime.now().strftime('%H:%M:%S') @@ -153,24 +190,42 @@ async def keyboard(): if len(input) > 0 and input[0] == 's': input = input[1:] for target in autorecon.scanning_targets: - count = len(target.running_tasks) + async with target.lock: + count = len(target.running_tasks) - tasks_list = [] - if config['verbose'] >= 1: - for key, value in target.running_tasks.items(): - elapsed_time = calculate_elapsed_time(value['start'], short=True) - tasks_list.append('{bblue}' + key + '{rst}' + ' (elapsed: ' + elapsed_time + ')') + tasks_list = [] + if config['verbose'] >= 1: + for tag, task in target.running_tasks.items(): + elapsed_time = calculate_elapsed_time(task['start'], short=True) - tasks_list = ':\n ' + '\n '.join(tasks_list) - else: - tasks_list = '' + task_str = '{bblue}' + tag + '{rst}' + ' (elapsed: ' + elapsed_time + ')' - current_time = datetime.now().strftime('%H:%M:%S') + if config['verbose'] >= 2: + processes = [] + for process_dict in task['processes']: + if process_dict['process'].returncode is None: + processes.append(str(process_dict['process'].pid)) + try: + for child in psutil.Process(process_dict['process'].pid).children(recursive=True): + processes.append(str(child.pid)) + except psutil.NoSuchProcess: + pass + + if processes: + task_str += ' (PID' + ('s' if len(processes) > 1 else '') + ': ' + ', '.join(processes) + ')' + + tasks_list.append(task_str) - if count > 1: - info('{bgreen}' + current_time + '{rst} - There are {byellow}' + str(count) + '{rst} scans still running against {byellow}' + target.address + '{rst}' + tasks_list) - elif count == 1: - info('{bgreen}' + current_time + '{rst} - There is {byellow}1{rst} scan still running against {byellow}' + target.address + '{rst}' + tasks_list) + tasks_list = ':\n ' + '\n '.join(tasks_list) + else: + tasks_list = '' + + current_time = datetime.now().strftime('%H:%M:%S') + + if count > 1: + info('{bgreen}' + current_time + '{rst} - There are {byellow}' + str(count) + '{rst} scans still running against {byellow}' + target.address + '{rst}' + tasks_list) + elif count == 1: + info('{bgreen}' + current_time + '{rst} - There is {byellow}1{rst} scan still running against {byellow}' + target.address + '{rst}' + tasks_list) else: input = input[1:] await asyncio.sleep(0.1) diff --git a/pyproject.toml b/pyproject.toml index 32fe5ce..93a45f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "autorecon" -version = "2.0.30" +version = "2.0.31" description = "A multi-threaded network reconnaissance tool which performs automated enumeration of services." authors = ["Tib3rius"] license = "GNU GPL v3" @@ -14,6 +14,7 @@ python = "^3.8" appdirs = "^1.4.4" colorama = "^0.4.5" impacket = "^0.10.0" +psutil = "^5.9.4" requests = "^2.28.1" toml = "^0.10.2" Unidecode = "^1.3.1" diff --git a/requirements.txt b/requirements.txt index 52563e4..311f8ed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ appdirs colorama impacket +psutil requests toml unidecode From 9cfbc4a7ce8622270777650dab4e18c0cc0f6880 Mon Sep 17 00:00:00 2001 From: Tib3rius <48113936+Tib3rius@users.noreply.github.com> Date: Mon, 16 Jan 2023 00:42:52 -0500 Subject: [PATCH 10/11] Update main.py Potential fix for #187 --- autorecon/main.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/autorecon/main.py b/autorecon/main.py index f9542de..f540972 100644 --- a/autorecon/main.py +++ b/autorecon/main.py @@ -49,8 +49,8 @@ else: warn('It looks like the plugins in ' + config['data_dir'] + ' are outdated. Please remove the ' + config['data_dir'] + ' directory and re-run AutoRecon to rebuild them.') -# Save current terminal settings so we can restore them. -terminal_settings = termios.tcgetattr(sys.stdin.fileno()) +# Saves current terminal settings so we can restore them. +terminal_settings = None autorecon = AutoRecon() @@ -120,11 +120,11 @@ def cancel_all_tasks(sig, frame): _, alive = psutil.wait_procs(processes, timeout=10) if len(alive) > 0: error('The following process IDs could not be killed: ' + ', '.join([str(x.pid) for x in sorted(alive, key=lambda x: x.pid)])) - - + if not config['disable_keyboard_control']: # Restore original terminal settings. - termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, terminal_settings) + if terminal_settings is not None: + termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, terminal_settings) async def start_heartbeat(target, period=60): while True: @@ -1513,6 +1513,9 @@ async def run(): start_time = time.time() + if not config['disable_keyboard_control']: + terminal_settings = termios.tcgetattr(sys.stdin.fileno()) + pending = [] i = 0 while autorecon.pending_targets: @@ -1616,7 +1619,8 @@ async def run(): if not config['disable_keyboard_control']: # Restore original terminal settings. - termios.tcsetattr(sys.stdin, termios.TCSADRAIN, terminal_settings) + if terminal_settings is not None: + termios.tcsetattr(sys.stdin, termios.TCSADRAIN, terminal_settings) def main(): # Capture Ctrl+C and cancel everything. From a9608d663df07d61c5098241d97d1ed35609fc3b Mon Sep 17 00:00:00 2001 From: Tib3rius <48113936+Tib3rius@users.noreply.github.com> Date: Sun, 19 Feb 2023 17:16:27 -0500 Subject: [PATCH 11/11] Bug fixes and updates. Fixed bug in dirbuster and vhost-enum plugins. Redirections are now followed. This could potentially cause issues down the line but for now it seems to work. Have re-enabled nikto as an auto-run plugin, however several constraints are in place to ensure it doesn't take too long. --- .gitignore | 1 + README.md | 10 ++-- autorecon/default-plugins/dirbuster.py | 20 +++---- autorecon/default-plugins/nikto.py | 6 ++- .../virtual-host-enumeration.py | 10 ++-- autorecon/main.py | 2 +- poetry.lock | 54 ------------------- poetry.toml | 2 - pyproject.toml | 2 +- requirements.txt | 14 ++--- 10 files changed, 34 insertions(+), 87 deletions(-) delete mode 100644 poetry.lock delete mode 100644 poetry.toml diff --git a/.gitignore b/.gitignore index a89c11f..6710e6f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ __pycache__ *.pyc results/ +poetry.* \ No newline at end of file diff --git a/README.md b/README.md index 8da16dc..69241be 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ sudo apt update ### Python 3 -AutoRecon requires the usage of Python 3.7+ and pip, which can be installed on Kali Linux using the following commands: +AutoRecon requires the usage of Python 3.8+ and pip, which can be installed on Kali Linux using the following commands: ```bash sudo apt install python3 @@ -174,9 +174,7 @@ Assuming you did not modify any of the content in the AutoRecon directory, this ### Plugins -A plugin update process is in the works. Until then, after upgrading, remove the ~/.config/AutoRecon directory and run AutoRecon with any argument to repopulate with the latest files. - -If you depend on the ~/.config/AutoRecon/config.toml file (i.e. you have made modifications to it) then simply remove everything in the ~/.config/AutoRecon apart from the config.toml file (including the VERSION-x.x.x file). +A plugin update process is in the works. Until then, after upgrading, remove the ~/.local/share/AutoRecon directory and run AutoRecon with any argument to repopulate with the latest files. ## Usage @@ -224,7 +222,7 @@ optional arguments: Override --tags / --exclude-tags for the listed ServiceScan plugins (comma separated). Default: None --reports PLUGINS Override --tags / --exclude-tags for the listed Report plugins (comma separated). Default: None --plugins-dir PLUGINS_DIR - The location of the plugins directory. Default: ~/.config/AutoRecon/plugins + The location of the plugins directory. Default: ~/.local/share/AutoRecon/plugins --add-plugins-dir PLUGINS_DIR The location of an additional plugins directory to add to the main one. Default: None -l [TYPE], --list [TYPE] @@ -266,7 +264,7 @@ plugin arguments: The tool to use for directory busting. Default: feroxbuster --dirbuster.wordlist VALUE [VALUE ...] The wordlist(s) to use when directory busting. Separate multiple wordlists with spaces. Default: - ['~/.config/AutoRecon/wordlists/dirbuster.txt'] + ['~/.local/share/AutoRecon/wordlists/dirbuster.txt'] --dirbuster.threads VALUE The number of threads to use when directory busting. Default: 10 --dirbuster.ext VALUE diff --git a/autorecon/default-plugins/dirbuster.py b/autorecon/default-plugins/dirbuster.py index 5f77851..bd48ab0 100644 --- a/autorecon/default-plugins/dirbuster.py +++ b/autorecon/default-plugins/dirbuster.py @@ -45,43 +45,43 @@ class DirBuster(ServiceScan): 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 ' + ('' if self.get_option('recursive') else '-n ') + '-q -e -o "{scandir}/{protocol}_{port}_{http_scheme}_feroxbuster_' + name + '.txt"' + (' ' + self.get_option('extras') if self.get_option('extras') else '')) + await service.execute('feroxbuster -u {http_scheme}://{addressv6}:{port}/ -t ' + str(self.get_option('threads')) + ' -w ' + wordlist + ' -x "' + self.get_option('ext') + '" -v -k ' + ('' if self.get_option('recursive') else '-n ') + '-q -e -r -o "{scandir}/{protocol}_{port}_{http_scheme}_feroxbuster_' + name + '.txt"' + (' ' + self.get_option('extras') if self.get_option('extras') else '')) 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"' + (' ' + self.get_option('extras') if self.get_option('extras') else '')) + 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 -r -o "{scandir}/{protocol}_{port}_{http_scheme}_gobuster_' + name + '.txt"' + (' ' + self.get_option('extras') if self.get_option('extras') else '')) elif self.get_option('tool') == 'dirsearch': if service.target.ipversion == '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 ' + ('-r ' if self.get_option('recursive') else '') + '-w ' + wordlist + ' --format=plain -o "{scandir}/{protocol}_{port}_{http_scheme}_dirsearch_' + name + '.txt"' + (' ' + self.get_option('extras') if self.get_option('extras') else '')) + await service.execute('dirsearch -u {http_scheme}://{address}:{port}/ -t ' + str(self.get_option('threads')) + ' -e "' + self.get_option('ext') + '" -f -q -F ' + ('-r ' if self.get_option('recursive') else '') + '-w ' + wordlist + ' --format=plain -o "{scandir}/{protocol}_{port}_{http_scheme}_dirsearch_' + name + '.txt"' + (' ' + self.get_option('extras') if self.get_option('extras') else '')) elif self.get_option('tool') == 'ffuf': - await service.execute('ffuf -u {http_scheme}://{addressv6}:{port}/FUZZ -t ' + str(self.get_option('threads')) + ' -w ' + wordlist + ' -e "' + dot_extensions + '" -v ' + ('-recursion ' if self.get_option('recursive') else '') + '-noninteractive' + (' ' + self.get_option('extras') if self.get_option('extras') else '') + ' | tee {scandir}/{protocol}_{port}_{http_scheme}_ffuf_' + name + '.txt') + await service.execute('ffuf -u {http_scheme}://{addressv6}:{port}/FUZZ -t ' + str(self.get_option('threads')) + ' -w ' + wordlist + ' -e "' + dot_extensions + '" -v -r ' + ('-recursion ' if self.get_option('recursive') else '') + '-noninteractive' + (' ' + self.get_option('extras') if self.get_option('extras') else '') + ' | tee {scandir}/{protocol}_{port}_{http_scheme}_ffuf_' + name + '.txt') elif self.get_option('tool') == 'dirb': - await service.execute('dirb {http_scheme}://{addressv6}:{port}/ ' + wordlist + ' -l ' + ('' if self.get_option('recursive') else '-r ') + '-S -X ",' + dot_extensions + '" -o "{scandir}/{protocol}_{port}_{http_scheme}_dirb_' + name + '.txt"' + (' ' + self.get_option('extras') if self.get_option('extras') else '')) + await service.execute('dirb {http_scheme}://{addressv6}:{port}/ ' + wordlist + ' -l ' + ('' if self.get_option('recursive') else '-r ') + '-S -X ",' + dot_extensions + '" -f -o "{scandir}/{protocol}_{port}_{http_scheme}_dirb_' + name + '.txt"' + (' ' + self.get_option('extras') if self.get_option('extras') else '')) def manual(self, service, plugin_was_run): dot_extensions = ','.join(['.' + x for x in self.get_option('ext').split(',')]) if self.get_option('tool') == 'feroxbuster': service.add_manual_command('(feroxbuster) Multi-threaded recursive directory/file enumeration for web servers using various wordlists:', [ - 'feroxbuster -u {http_scheme}://{addressv6}:{port} -t ' + str(self.get_option('threads')) + ' -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x "' + self.get_option('ext') + '" -v -k ' + ('' if self.get_option('recursive') else '-n ') + '-e -o {scandir}/{protocol}_{port}_{http_scheme}_feroxbuster_dirbuster.txt' + (' ' + self.get_option('extras') if self.get_option('extras') else '') + 'feroxbuster -u {http_scheme}://{addressv6}:{port} -t ' + str(self.get_option('threads')) + ' -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x "' + self.get_option('ext') + '" -v -k ' + ('' if self.get_option('recursive') else '-n ') + '-e -r -o {scandir}/{protocol}_{port}_{http_scheme}_feroxbuster_dirbuster.txt' + (' ' + self.get_option('extras') if self.get_option('extras') else '') ]) elif self.get_option('tool') == 'gobuster': service.add_manual_command('(gobuster v3) Multi-threaded directory/file enumeration for web servers using various wordlists:', [ - 'gobuster dir -u {http_scheme}://{addressv6}:{port}/ -t ' + str(self.get_option('threads')) + ' -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -e -k -x "' + self.get_option('ext') + '" -o "{scandir}/{protocol}_{port}_{http_scheme}_gobuster_dirbuster.txt"' + (' ' + self.get_option('extras') if self.get_option('extras') else '') + 'gobuster dir -u {http_scheme}://{addressv6}:{port}/ -t ' + str(self.get_option('threads')) + ' -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -e -k -x "' + self.get_option('ext') + '" -r -o "{scandir}/{protocol}_{port}_{http_scheme}_gobuster_dirbuster.txt"' + (' ' + self.get_option('extras') if self.get_option('extras') else '') ]) elif self.get_option('tool') == 'dirsearch': if service.target.ipversion == 'IPv4': service.add_manual_command('(dirsearch) Multi-threaded recursive directory/file enumeration for web servers using various wordlists:', [ - 'dirsearch -u {http_scheme}://{address}:{port}/ -t ' + str(self.get_option('threads')) + ' -e "' + self.get_option('ext') + '" -f ' + ('-r ' if self.get_option('recursive') else '') + '-w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt --format=plain --output="{scandir}/{protocol}_{port}_{http_scheme}_dirsearch_dirbuster.txt"' + (' ' + self.get_option('extras') if self.get_option('extras') else '') + 'dirsearch -u {http_scheme}://{address}:{port}/ -t ' + str(self.get_option('threads')) + ' -e "' + self.get_option('ext') + '" -f -F ' + ('-r ' if self.get_option('recursive') else '') + '-w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt --format=plain --output="{scandir}/{protocol}_{port}_{http_scheme}_dirsearch_dirbuster.txt"' + (' ' + self.get_option('extras') if self.get_option('extras') else '') ]) elif self.get_option('tool') == 'ffuf': service.add_manual_command('(ffuf) Multi-threaded recursive directory/file enumeration for web servers using various wordlists:', [ - 'ffuf -u {http_scheme}://{addressv6}:{port}/FUZZ -t ' + str(self.get_option('threads')) + ' -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -e "' + dot_extensions + '" -v ' + ('-recursion ' if self.get_option('recursive') else '') + '-noninteractive' + (' ' + self.get_option('extras') if self.get_option('extras') else '') + ' | tee {scandir}/{protocol}_{port}_{http_scheme}_ffuf_dirbuster.txt' + 'ffuf -u {http_scheme}://{addressv6}:{port}/FUZZ -t ' + str(self.get_option('threads')) + ' -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -e "' + dot_extensions + '" -v -r ' + ('-recursion ' if self.get_option('recursive') else '') + '-noninteractive' + (' ' + self.get_option('extras') if self.get_option('extras') else '') + ' | tee {scandir}/{protocol}_{port}_{http_scheme}_ffuf_dirbuster.txt' ]) elif self.get_option('tool') == 'dirb': service.add_manual_command('(dirb) Recursive directory/file enumeration for web servers using various wordlists:', [ - 'dirb {http_scheme}://{addressv6}:{port}/ /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -l ' + ('' if self.get_option('recursive') else '-r ') + '-X ",' + dot_extensions + '" -o "{scandir}/{protocol}_{port}_{http_scheme}_dirb_dirbuster.txt"' + (' ' + self.get_option('extras') if self.get_option('extras') else '') + 'dirb {http_scheme}://{addressv6}:{port}/ /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -l ' + ('' if self.get_option('recursive') else '-r ') + '-X ",' + dot_extensions + '" -f -o "{scandir}/{protocol}_{port}_{http_scheme}_dirb_dirbuster.txt"' + (' ' + self.get_option('extras') if self.get_option('extras') else '') ]) diff --git a/autorecon/default-plugins/nikto.py b/autorecon/default-plugins/nikto.py index b696cb5..94ab499 100644 --- a/autorecon/default-plugins/nikto.py +++ b/autorecon/default-plugins/nikto.py @@ -11,6 +11,10 @@ class Nikto(ServiceScan): self.match_service_name('^http') self.match_service_name('^nacn_http$', negative_match=True) - def manual(self, service, plugin_was_run): + async def run(self, service): if service.target.ipversion == 'IPv4': + await service.execute('nikto -ask=no -Tuning=x4567890ac -nointeractive -host {http_scheme}://{address}:{port} 2>&1 | tee "{scandir}/{protocol}_{port}_{http_scheme}_nikto.txt"') + + def manual(self, service, plugin_was_run): + if service.target.ipversion == 'IPv4' and not plugin_was_run: 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"') diff --git a/autorecon/default-plugins/virtual-host-enumeration.py b/autorecon/default-plugins/virtual-host-enumeration.py index 96e2561..031778e 100644 --- a/autorecon/default-plugins/virtual-host-enumeration.py +++ b/autorecon/default-plugins/virtual-host-enumeration.py @@ -1,6 +1,7 @@ from autorecon.plugins import ServiceScan from shutil import which -import os, random, string +import os, requests, random, string, urllib3 +urllib3.disable_warnings() class VirtualHost(ServiceScan): @@ -30,10 +31,9 @@ class VirtualHost(ServiceScan): for wordlist in self.get_option('wordlist'): name = os.path.splitext(os.path.basename(wordlist))[0] for hostname in hostnames: - _, stdout, _ = await service.execute('curl -sk -o /dev/null -H "Host: ' + ''.join(random.choice(string.ascii_letters) for i in range(20)) + '.' + hostname + '" {http_scheme}://' + hostname + ':{port}/ -w "%{{size_download}}"') + wildcard = requests.get(('https' if service.secure else 'http') + '://' + service.target.address + ':' + str(service.port) + '/', headers={'Host':''.join(random.choice(string.ascii_letters) for i in range(20)) + '.' + hostname}, verify=False) - size = ''.join(await stdout.readlines()) - - await service.execute('ffuf -u {http_scheme}://' + hostname + ':{port}/ -t ' + str(self.get_option('threads')) + ' -w ' + wordlist + ' -H "Host: FUZZ.' + hostname + '" -fs ' + size + ' -noninteractive -s | tee "{scandir}/{protocol}_{port}_{http_scheme}_' + hostname + '_vhosts_' + name + '.txt"') + size = str(len(wildcard.content)) + await service.execute('ffuf -u {http_scheme}://' + hostname + ':{port}/ -t ' + str(self.get_option('threads')) + ' -w ' + wordlist + ' -H "Host: FUZZ.' + hostname + '" -fs ' + size + ' -r -noninteractive -s | tee "{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.') diff --git a/autorecon/main.py b/autorecon/main.py index f540972..c8f5ece 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.31" +VERSION = "2.0.32" if not os.path.exists(config['config_dir']): shutil.rmtree(config['config_dir'], ignore_errors=True, onerror=None) diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index c8cfa8e..0000000 --- a/poetry.lock +++ /dev/null @@ -1,54 +0,0 @@ -[[package]] -name = "appdirs" -version = "1.4.4" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "colorama" -version = "0.4.4" -description = "Cross-platform colored terminal text." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "main" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "unidecode" -version = "1.3.2" -description = "ASCII transliterations of Unicode text" -category = "main" -optional = false -python-versions = ">=3.5" - -[metadata] -lock-version = "1.1" -python-versions = "^3.7" -content-hash = "681db41aa556d6d3f79e1e8ee0107bccd078e39c8db7e6e0159860c96ea93c5b" - -[metadata.files] -appdirs = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, -] -colorama = [ - {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, - {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, -] -toml = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] -unidecode = [ - {file = "Unidecode-1.3.2-py3-none-any.whl", hash = "sha256:215fe33c9d1c889fa823ccb66df91b02524eb8cc8c9c80f9c5b8129754d27829"}, - {file = "Unidecode-1.3.2.tar.gz", hash = "sha256:669898c1528912bcf07f9819dc60df18d057f7528271e31f8ec28cc88ef27504"}, -] diff --git a/poetry.toml b/poetry.toml deleted file mode 100644 index 3b549d6..0000000 --- a/poetry.toml +++ /dev/null @@ -1,2 +0,0 @@ -[virtualenvs] -create = true diff --git a/pyproject.toml b/pyproject.toml index 93a45f3..064585f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "autorecon" -version = "2.0.31" +version = "2.0.32" description = "A multi-threaded network reconnaissance tool which performs automated enumeration of services." authors = ["Tib3rius"] license = "GNU GPL v3" diff --git a/requirements.txt b/requirements.txt index 311f8ed..fc89076 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -appdirs -colorama -impacket -psutil -requests -toml -unidecode +appdirs>=1.4.4 +colorama>=0.4.5 +impacket>=0.10.0 +psutil>=5.9.4 +requests>=2.28.1 +toml>=0.10.2 +Unidecode>=1.3.1 \ No newline at end of file