Merge 34ed2dff6a into e7e98f60bd
This commit is contained in:
commit
68a586becf
|
|
@ -87,7 +87,7 @@ whatweb
|
|||
On Kali Linux, you can ensure these are all installed using the following commands:
|
||||
|
||||
```bash
|
||||
sudo apt install seclists curl dnsrecon enum4linux feroxbuster gobuster impacket-scripts nbtscan nikto nmap onesixtyone oscanner redis-tools smbclient smbmap snmp sslscan sipvicious tnscmd10g whatweb
|
||||
sudo apt install seclists curl dnsrecon enum4linux feroxbuster gobuster impacket-scripts nbtscan nuclei nikto nmap onesixtyone oscanner redis-tools smbclient smbmap snmp sslscan sipvicious tnscmd10g whatweb
|
||||
```
|
||||
|
||||
### Installation Method #1: pipx (Recommended)
|
||||
|
|
|
|||
|
|
@ -77,5 +77,6 @@ config = {
|
|||
'max_plugin_target_instances': None,
|
||||
'max_plugin_global_instances': None,
|
||||
'accessible': False,
|
||||
'verbose': 0
|
||||
'verbose': 0,
|
||||
'imported_nmap_services': None
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class BruteforceHTTP(ServiceScan):
|
|||
self.match_service_name('^nacn_http$', negative_match=True)
|
||||
|
||||
def manual(self, service, plugin_was_run):
|
||||
service.add_manual_commands('Credential bruteforcing commands (don\'t run these without modifying them):', [
|
||||
service.add_manual_commands('Credential bruteforcing commands (dont run these without modifying them):', [
|
||||
'hydra -L "' + 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') + '" -e nsr -s {port} -o "{scandir}/{protocol}_{port}_{http_scheme}_auth_hydra.txt" {http_scheme}-get://{addressv6}/path/to/auth/area',
|
||||
'medusa -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') + '" -e ns -n {port} -O "{scandir}/{protocol}_{port}_{http_scheme}_auth_medusa.txt" -M http -h {addressv6} -m DIR:/path/to/auth/area',
|
||||
'hydra -L "' + 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') + '" -e nsr -s {port} -o "{scandir}/{protocol}_{port}_{http_scheme}_form_hydra.txt" {http_scheme}-post-form://{addressv6}/path/to/login.php:"username=^USER^&password=^PASS^":"invalid-login-message"',
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ 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 -r -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 --dont-extract-links') + ' -q -r --auto-bail --status-codes 200,204,301,302,307,308,401,403,405 -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 -r -o "{scandir}/{protocol}_{port}_{http_scheme}_gobuster_' + name + '.txt"' + (' ' + self.get_option('extras') if self.get_option('extras') else ''))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
from autorecon.plugins import ServiceScan
|
||||
from shutil import which
|
||||
|
||||
|
||||
class Nuclei(ServiceScan):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.name = "nuclei"
|
||||
self.tags = ["default", "safe", "long"]
|
||||
|
||||
self.cmd = 'nuclei -disable-update-check -no-color -target {address}:{port} -scan-all-ips -o "{scandir}/{protocol}_{port}_nuclei.txt"'
|
||||
|
||||
def configure(self):
|
||||
self.match_all_service_names(True)
|
||||
self.add_pattern(
|
||||
r"(.*\[(critical|high)\].*)",
|
||||
description="Nuclei {match2} finding: {match1}",
|
||||
)
|
||||
|
||||
def check(self):
|
||||
if which("nuclei") is None:
|
||||
self.error(
|
||||
"The program nuclei could not be found. Make sure it is installed. (On Kali, run: sudo apt install nuclei)"
|
||||
)
|
||||
return False
|
||||
|
||||
async def run(self, service):
|
||||
if service.target.ipversion == "IPv4":
|
||||
await service.execute(self.cmd)
|
||||
|
||||
def manual(self, service, plugin_was_run):
|
||||
if service.target.ipversion == "IPv4" and not plugin_was_run:
|
||||
service.add_manual_command(
|
||||
f"({self.name}) Fast and customizable vulnerability scanner based on simple YAML based DSL:",
|
||||
self.cmd,
|
||||
)
|
||||
|
|
@ -20,11 +20,11 @@ class AllTCPPortScan(PortScan):
|
|||
|
||||
if target.ports:
|
||||
if target.ports['tcp']:
|
||||
process, stdout, stderr = await target.execute('nmap {nmap_extra} -sV -sC --version-all' + traceroute_os + ' -p ' + target.ports['tcp'] + ' -oN "{scandir}/_full_tcp_nmap.txt" -oX "{scandir}/xml/_full_tcp_nmap.xml" {address}', blocking=False)
|
||||
process, stdout, stderr = await target.execute('nmap {nmap_extra} -sV -sC --version-all' + traceroute_os + ' -p ' + target.ports['tcp'] + ' -oN "{scandir}/' + str(target.address) + '_full_tcp_nmap.txt" -oX "{scandir}/xml/' + str(target.address) + '_full_tcp_nmap.xml" {address}', blocking=False)
|
||||
else:
|
||||
return []
|
||||
else:
|
||||
process, stdout, stderr = await target.execute('nmap {nmap_extra} -sV -sC --version-all' + traceroute_os + ' -p- -oN "{scandir}/_full_tcp_nmap.txt" -oX "{scandir}/xml/_full_tcp_nmap.xml" {address}', blocking=False)
|
||||
process, stdout, stderr = await target.execute('nmap {nmap_extra} -sV -sC --version-all' + traceroute_os + ' -p- -oN "{scandir}/' + str(target.address) + '_full_tcp_nmap.txt" -oX "{scandir}/xml/' + str(target.address) + '_full_tcp_nmap.xml" {address}', blocking=False)
|
||||
services = []
|
||||
while True:
|
||||
line = await stdout.readline()
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@ class GuessPortScan(PortScan):
|
|||
async def run(self, target):
|
||||
if target.ports:
|
||||
if target.ports['tcp']:
|
||||
process, stdout, stderr = await target.execute('nmap {nmap_extra} -A --osscan-guess --version-all -p ' + target.ports['tcp'] + ' -oN "{scandir}/_custom_ports_tcp_nmap.txt" -oX "{scandir}/xml/_custom_ports_tcp_nmap.xml" {address}', blocking=False)
|
||||
process, stdout, stderr = await target.execute('nmap {nmap_extra} -A --osscan-guess --version-all -p ' + target.ports['tcp'] + ' -oN "{scandir}/' + str(target.address) + '_custom_ports_tcp_nmap.txt" -oX "{scandir}/xml/' + str(target.address) + '_custom_ports_tcp_nmap.xml" {address}', blocking=False)
|
||||
else:
|
||||
return []
|
||||
else:
|
||||
process, stdout, stderr = await target.execute('nmap {nmap_extra} -A --osscan-guess --version-all -p- -oN "{scandir}/_quick_tcp_nmap.txt" -oX "{scandir}/xml/_quick_tcp_nmap.xml" {address}', blocking=False)
|
||||
process, stdout, stderr = await target.execute('nmap {nmap_extra} -A --osscan-guess --version-all -p- -oN "{scandir}/' + str(target.address) + '_quick_tcp_nmap.txt" -oX "{scandir}/xml/' + str(target.address) + '_quick_tcp_nmap.xml" {address}', blocking=False)
|
||||
|
||||
insecure_ports = {
|
||||
'20':'ftp', '21':'ftp', '22':'ssh', '23':'telnet', '25':'smtp', '53':'domain', '69':'tftp', '79':'finger', '80':'http', '88':'kerberos', '109':'pop3', '110':'pop3', '111':'rpcbind', '119':'nntp', '135':'msrpc', '139':'netbios-ssn', '143':'imap', '161':'snmp', '220':'imap', '389':'ldap', '433':'nntp', '445':'smb', '587':'smtp', '631':'ipp', '873':'rsync', '1098':'java-rmi', '1099':'java-rmi', '1433':'mssql', '1521':'oracle', '2049':'nfs', '2483':'oracle', '3020':'smb', '3306':'mysql', '3389':'rdp', '3632':'distccd', '5060':'asterisk', '5500':'vnc', '5900':'vnc', '5985':'wsman', '6379':'redis', '8080':'http-proxy', '27017':'mongod', '27018':'mongod', '27019':'mongod'
|
||||
|
|
|
|||
|
|
@ -17,11 +17,11 @@ class Top100UDPPortScan(PortScan):
|
|||
if os.getuid() == 0 or config['disable_sanity_checks']:
|
||||
if target.ports:
|
||||
if target.ports['udp']:
|
||||
process, stdout, stderr = await target.execute('nmap {nmap_extra} -sU -A --osscan-guess -p ' + target.ports['udp'] + ' -oN "{scandir}/_custom_ports_udp_nmap.txt" -oX "{scandir}/xml/_custom_ports_udp_nmap.xml" {address}', blocking=False)
|
||||
process, stdout, stderr = await target.execute('nmap {nmap_extra} -sU -A --osscan-guess -p ' + target.ports['udp'] + ' -oN "{scandir}/' + str(target.address) + '_custom_ports_udp_nmap.txt" -oX "{scandir}/xml/' + str(target.address) + '_custom_ports_udp_nmap.xml" {address}', blocking=False)
|
||||
else:
|
||||
return []
|
||||
else:
|
||||
process, stdout, stderr = await target.execute('nmap {nmap_extra} -sU -A --top-ports 100 -oN "{scandir}/_top_100_udp_nmap.txt" -oX "{scandir}/xml/_top_100_udp_nmap.xml" {address}', blocking=False)
|
||||
process, stdout, stderr = await target.execute('nmap {nmap_extra} -sU -A --top-ports 100 -oN "{scandir}/' + str(target.address) + '_top_100_udp_nmap.txt" -oX "{scandir}/xml/' + str(target.address) + '_top_100_udp_nmap.xml" {address}', blocking=False)
|
||||
services = []
|
||||
while True:
|
||||
line = await stdout.readline()
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class QuickTCPPortScan(PortScan):
|
|||
else:
|
||||
traceroute_os = ' -A --osscan-guess'
|
||||
|
||||
process, stdout, stderr = await target.execute('nmap {nmap_extra} -sV -sC --version-all' + traceroute_os + ' -oN "{scandir}/_quick_tcp_nmap.txt" -oX "{scandir}/xml/_quick_tcp_nmap.xml" {address}', blocking=False)
|
||||
process, stdout, stderr = await target.execute('nmap {nmap_extra} -sV -sC --version-all' + traceroute_os + ' -oN "{scandir}/' + str(target.address) + '_quick_tcp_nmap.txt" -oX "{scandir}/xml/' + str(target.address) + '_quick_tcp_nmap.xml" {address}', blocking=False)
|
||||
services = await target.extract_services(stdout)
|
||||
|
||||
for service in services:
|
||||
|
|
|
|||
|
|
@ -10,24 +10,31 @@ class Markdown(Report):
|
|||
|
||||
async def run(self, targets):
|
||||
if len(targets) > 1:
|
||||
report = os.path.join(config['output'], 'report.md')
|
||||
report = os.path.join(config['output'], 'Full_Report.md')
|
||||
single_target = False
|
||||
elif len(targets) == 1:
|
||||
report = os.path.join(targets[0].reportdir, 'report.md')
|
||||
report = targets[0].reportdir
|
||||
single_target = True
|
||||
else:
|
||||
return
|
||||
|
||||
|
||||
os.makedirs(report, exist_ok=True)
|
||||
|
||||
for target in targets:
|
||||
os.makedirs(os.path.join(report, target.address), exist_ok=True)
|
||||
# Use target.address subdirectory only if multiple targets exist
|
||||
target_root = report if single_target else os.path.join(report, target.address)
|
||||
os.makedirs(target_root, exist_ok=True)
|
||||
|
||||
files = [os.path.abspath(filename) for filename in glob.iglob(os.path.join(target.scandir, '**/*'), recursive=True) if os.path.isfile(filename) and filename.endswith(('.txt', '.html'))]
|
||||
|
||||
# --- Port Scans ---
|
||||
if target.scans['ports']:
|
||||
os.makedirs(os.path.join(report, target.address, 'Port Scans'), exist_ok=True)
|
||||
ports_dir = os.path.join(target_root, 'Port Scans')
|
||||
os.makedirs(ports_dir, exist_ok=True)
|
||||
for scan in target.scans['ports'].keys():
|
||||
if len(target.scans['ports'][scan]['commands']) > 0:
|
||||
with open(os.path.join(report, target.address, 'Port Scans', 'PortScan - ' + target.scans['ports'][scan]['plugin'].name + '.md'), 'w') as output:
|
||||
with open(os.path.join(ports_dir, 'PortScan - ' + target.scans['ports'][scan]['plugin'].name + '.md'), 'w') as output:
|
||||
for command in target.scans['ports'][scan]['commands']:
|
||||
output.writelines('```bash\n' + command[0] + '\n```')
|
||||
for filename in files:
|
||||
|
|
@ -35,13 +42,17 @@ class Markdown(Report):
|
|||
output.writelines('\n\n[' + filename + '](file://' + filename + '):\n\n')
|
||||
with open(filename, 'r') as file:
|
||||
output.writelines('```\n' + file.read() + '\n```\n')
|
||||
|
||||
# --- Services ---
|
||||
if target.scans['services']:
|
||||
os.makedirs(os.path.join(report, target.address, 'Services'), exist_ok=True)
|
||||
services_dir = os.path.join(target_root, 'Services')
|
||||
os.makedirs(services_dir, exist_ok=True)
|
||||
for service in target.scans['services'].keys():
|
||||
os.makedirs(os.path.join(report, target.address, 'Services', 'Service - ' + service.tag().replace('/', '-')), exist_ok=True)
|
||||
service_dir = os.path.join(services_dir, 'Service - ' + service.tag().replace('/', '-'))
|
||||
os.makedirs(service_dir, exist_ok=True)
|
||||
for plugin in target.scans['services'][service].keys():
|
||||
if len(target.scans['services'][service][plugin]['commands']) > 0:
|
||||
with open(os.path.join(report, target.address, 'Services', 'Service - ' + service.tag().replace('/', '-'), target.scans['services'][service][plugin]['plugin'].name + '.md'), 'w') as output:
|
||||
with open(os.path.join(service_dir, target.scans['services'][service][plugin]['plugin'].name + '.md'), 'w') as output:
|
||||
for command in target.scans['services'][service][plugin]['commands']:
|
||||
output.writelines('```bash\n' + command[0] + '\n```')
|
||||
for filename in files:
|
||||
|
|
@ -50,26 +61,30 @@ class Markdown(Report):
|
|||
with open(filename, 'r') as file:
|
||||
output.writelines('```\n' + file.read() + '\n```\n')
|
||||
|
||||
# --- Manual Commands ---
|
||||
manual_commands = os.path.join(target.scandir, '_manual_commands.txt')
|
||||
if os.path.isfile(manual_commands):
|
||||
with open(os.path.join(report, target.address, 'Manual Commands' + '.md'), 'w') as output:
|
||||
with open(os.path.join(target_root, 'Manual Commands.md'), 'w') as output:
|
||||
with open(manual_commands, 'r') as file:
|
||||
output.writelines('```bash\n' + file.read() + '\n```')
|
||||
|
||||
# --- Patterns ---
|
||||
patterns = os.path.join(target.scandir, '_patterns.log')
|
||||
if os.path.isfile(patterns):
|
||||
with open(os.path.join(report, target.address, 'Patterns' + '.md'), 'w') as output:
|
||||
with open(os.path.join(target_root, 'Patterns.md'), 'w') as output:
|
||||
with open(patterns, 'r') as file:
|
||||
output.writelines(file.read())
|
||||
|
||||
# --- Commands ---
|
||||
commands = os.path.join(target.scandir, '_commands.log')
|
||||
if os.path.isfile(commands):
|
||||
with open(os.path.join(report, target.address, 'Commands' + '.md'), 'w') as output:
|
||||
with open(os.path.join(target_root, 'Commands.md'), 'w') as output:
|
||||
with open(commands, 'r') as file:
|
||||
output.writelines('```bash\n' + file.read() + '\n```')
|
||||
|
||||
# --- Errors ---
|
||||
errors = os.path.join(target.scandir, '_errors.log')
|
||||
if os.path.isfile(errors):
|
||||
with open(os.path.join(report, target.address, 'Errors' + '.md'), 'w') as output:
|
||||
with open(os.path.join(target_root, 'Errors.md'), 'w') as output:
|
||||
with open(errors, 'r') as file:
|
||||
output.writelines('```\n' + file.read() + '\n```')
|
||||
|
|
|
|||
|
|
@ -15,6 +15,6 @@ class SMTPUserEnum(ServiceScan):
|
|||
await service.execute('hydra smtp-enum://{addressv6}:{port}/expn -L "' + self.get_global('username_wordlist', default='/usr/share/seclists/Usernames/top-usernames-shortlist.txt') + '" 2>&1', outfile='{protocol}_{port}_smtp_user-enum_hydra_expn.txt')
|
||||
|
||||
def manual(self, service, plugin_was_run):
|
||||
service.add_manual_command('Try User Enumeration using "RCPT TO". Replace <TARGET-DOMAIN> with the target\'s domain name:', [
|
||||
service.add_manual_command('Try User Enumeration using "RCPT TO". Replace <TARGET-DOMAIN> with the target domain name:', [
|
||||
'hydra smtp-enum://{addressv6}:{port}/rcpt -L "' + self.get_global('username_wordlist', default='/usr/share/seclists/Usernames/top-usernames-shortlist.txt') + '" -o "{scandir}/{protocol}_{port}_smtp_user-enum_hydra_rcpt.txt" -p <TARGET-DOMAIN>'
|
||||
])
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import argparse, asyncio, importlib.util, inspect, ipaddress, math, os, re, select, shutil, signal, socket, sys, termios, time, traceback, tty
|
||||
import xml.etree.ElementTree as ET
|
||||
from datetime import datetime
|
||||
|
||||
try:
|
||||
|
|
@ -19,6 +20,87 @@ from autorecon.targets import Target, Service
|
|||
|
||||
VERSION = "2.0.36"
|
||||
|
||||
def latest_mtime(path):
|
||||
"""Recursively get the latest modification time in a directory."""
|
||||
if not os.path.exists(path):
|
||||
return 0
|
||||
if os.path.isfile(path):
|
||||
return os.path.getmtime(path)
|
||||
latest = os.path.getmtime(path)
|
||||
for root, _, files in os.walk(path):
|
||||
for f in files:
|
||||
fpath = os.path.join(root, f)
|
||||
latest = max(latest, os.path.getmtime(fpath))
|
||||
return latest
|
||||
|
||||
def needs_update(src, dst):
|
||||
"""Return True if dst doesn't exist or src contains newer files than dst."""
|
||||
if not os.path.exists(dst):
|
||||
return True
|
||||
return latest_mtime(src) > latest_mtime(dst)
|
||||
|
||||
def parse_nmap_xml(xml_file):
|
||||
"""Parse an nmap XML file and return {ip: [(protocol, port, service_name, secure), ...]}."""
|
||||
result = {}
|
||||
try:
|
||||
tree = ET.parse(xml_file)
|
||||
root = tree.getroot()
|
||||
except ET.ParseError as exc:
|
||||
fail('Error parsing nmap XML file "' + xml_file + '": ' + str(exc))
|
||||
return result
|
||||
|
||||
for host in root.findall('host'):
|
||||
status = host.find('status')
|
||||
if status is not None and status.get('state') != 'up':
|
||||
continue
|
||||
|
||||
address = None
|
||||
for addr_elem in host.findall('address'):
|
||||
addrtype = addr_elem.get('addrtype', '')
|
||||
if addrtype in ('ipv4', 'ipv6'):
|
||||
address = addr_elem.get('addr')
|
||||
break
|
||||
|
||||
if address is None:
|
||||
hostnames = host.find('hostnames')
|
||||
if hostnames is not None:
|
||||
for hn in hostnames.findall('hostname'):
|
||||
address = hn.get('name')
|
||||
break
|
||||
|
||||
if address is None:
|
||||
continue
|
||||
|
||||
services = []
|
||||
ports_elem = host.find('ports')
|
||||
if ports_elem is None:
|
||||
continue
|
||||
|
||||
for port_elem in ports_elem.findall('port'):
|
||||
state = port_elem.find('state')
|
||||
if state is None or state.get('state') != 'open':
|
||||
continue
|
||||
|
||||
protocol = port_elem.get('protocol', 'tcp').lower()
|
||||
port_num = int(port_elem.get('portid', 0))
|
||||
|
||||
svc_elem = port_elem.find('service')
|
||||
if svc_elem is not None:
|
||||
name = svc_elem.get('name', 'unknown')
|
||||
tunnel = svc_elem.get('tunnel', '')
|
||||
secure = tunnel in ('ssl', 'tls') or 'ssl' in name or 'tls' in name
|
||||
else:
|
||||
name = 'unknown'
|
||||
secure = False
|
||||
|
||||
services.append((protocol, port_num, name, secure))
|
||||
|
||||
if services:
|
||||
result[address] = services
|
||||
|
||||
return result
|
||||
|
||||
# ----------------------- CONFIG DIR -----------------------
|
||||
if not os.path.exists(config['config_dir']):
|
||||
shutil.rmtree(config['config_dir'], ignore_errors=True, onerror=None)
|
||||
os.makedirs(config['config_dir'], exist_ok=True)
|
||||
|
|
@ -33,22 +115,35 @@ else:
|
|||
if not os.path.exists(os.path.join(config['config_dir'], 'VERSION-' + VERSION)):
|
||||
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.')
|
||||
|
||||
# ----------------------- DATA DIR -----------------------
|
||||
plugins_src = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'default-plugins')
|
||||
plugins_dst = os.path.join(config['data_dir'], 'plugins')
|
||||
|
||||
wordlists_src = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'wordlists')
|
||||
wordlists_dst = os.path.join(config['data_dir'], 'wordlists')
|
||||
|
||||
version_dir = os.path.join(config['data_dir'], f'VERSION-{VERSION}')
|
||||
|
||||
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'))
|
||||
shutil.copytree(plugins_src, plugins_dst)
|
||||
shutil.copytree(wordlists_src, wordlists_dst)
|
||||
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)):
|
||||
develop = False
|
||||
# Copy plugins if develop mode or changes detected
|
||||
if develop or needs_update(plugins_src, plugins_dst):
|
||||
shutil.copytree(plugins_src, plugins_dst, dirs_exist_ok=True)
|
||||
# Copy wordlists if changes detected
|
||||
if needs_update(wordlists_src, wordlists_dst):
|
||||
shutil.copytree(wordlists_src, wordlists_dst, dirs_exist_ok=True)
|
||||
# Warn if version is outdated
|
||||
if not os.path.exists(version_dir):
|
||||
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.')
|
||||
|
||||
|
||||
|
||||
# Saves current terminal settings so we can restore them.
|
||||
terminal_settings = None
|
||||
|
||||
|
|
@ -329,6 +424,7 @@ async def port_scan(plugin, target):
|
|||
target.running_tasks.pop(plugin.slug, None)
|
||||
|
||||
info('Port scan {bblue}' + plugin.name + ' {green}(' + plugin.slug + '){rst} against {byellow}' + target.address + '{rst} finished in ' + elapsed_time, verbosity=2)
|
||||
os.system ('touch ' + os.path.join(target.scandir, '.port_scans', f".{plugin.slug}"))
|
||||
return {'type':'port', 'plugin':plugin, 'result':result}
|
||||
|
||||
async def service_scan(plugin, service):
|
||||
|
|
@ -453,12 +549,13 @@ async def service_scan(plugin, service):
|
|||
service.target.running_tasks.pop(tag, None)
|
||||
|
||||
info('Service scan {bblue}' + plugin.name + ' {green}(' + tag + '){rst} against {byellow}' + service.target.address + '{rst} finished in ' + elapsed_time, verbosity=2)
|
||||
os.system ('touch ' + os.path.join(scandir, '.service_scans', f".{plugin.slug}"))
|
||||
return {'type':'service', 'plugin':plugin, 'result':result}
|
||||
|
||||
async def generate_report(plugin, targets):
|
||||
semaphore = autorecon.service_scan_semaphore
|
||||
|
||||
if not config['force_services']:
|
||||
if not config['force_services'] and config.get('imported_nmap_services') is None:
|
||||
semaphore = await get_semaphore(autorecon)
|
||||
|
||||
async with semaphore:
|
||||
|
|
@ -485,10 +582,11 @@ async def scan_target(target):
|
|||
os.makedirs(scandir, exist_ok=True)
|
||||
|
||||
os.makedirs(os.path.join(scandir, 'xml'), exist_ok=True)
|
||||
os.makedirs(os.path.join(scandir, '.port_scans'), exist_ok=True)
|
||||
|
||||
if not config['only_scans_dir']:
|
||||
exploitdir = os.path.join(basedir, 'exploit')
|
||||
os.makedirs(exploitdir, exist_ok=True)
|
||||
#exploitdir = os.path.join(basedir, 'exploit')
|
||||
#os.makedirs(exploitdir, exist_ok=True)
|
||||
|
||||
lootdir = os.path.join(basedir, 'loot')
|
||||
os.makedirs(lootdir, exist_ok=True)
|
||||
|
|
@ -496,17 +594,17 @@ async def scan_target(target):
|
|||
reportdir = os.path.join(basedir, 'report')
|
||||
os.makedirs(reportdir, exist_ok=True)
|
||||
|
||||
open(os.path.join(reportdir, 'local.txt'), 'a').close()
|
||||
open(os.path.join(reportdir, 'proof.txt'), 'a').close()
|
||||
#open(os.path.join(reportdir, 'local.txt'), 'a').close()
|
||||
#open(os.path.join(reportdir, 'proof.txt'), 'a').close()
|
||||
|
||||
screenshotdir = os.path.join(reportdir, 'screenshots')
|
||||
os.makedirs(screenshotdir, exist_ok=True)
|
||||
#screenshotdir = os.path.join(reportdir, 'screenshots')
|
||||
#os.makedirs(screenshotdir, exist_ok=True)
|
||||
else:
|
||||
reportdir = scandir
|
||||
|
||||
target.reportdir = reportdir
|
||||
|
||||
pending = []
|
||||
pending = set()
|
||||
|
||||
heartbeat = asyncio.create_task(start_heartbeat(target, period=config['heartbeat']))
|
||||
|
||||
|
|
@ -515,7 +613,7 @@ async def scan_target(target):
|
|||
forced_services = [x.strip().lower() for x in config['force_services']]
|
||||
|
||||
for forced_service in forced_services:
|
||||
match = re.search('(?P<protocol>(tcp|udp))\/(?P<port>\d+)\/(?P<service>[\w\-]+)(\/(?P<secure>secure|insecure))?', forced_service)
|
||||
match = re.search(r'(?P<protocol>(tcp|udp))/(?P<port>\d+)/(?P<service>[\w\-]+)(/(?P<secure>secure|insecure))?', forced_service)
|
||||
if match:
|
||||
protocol = match.group('protocol')
|
||||
if config['proxychains'] and protocol == 'udp':
|
||||
|
|
@ -529,16 +627,64 @@ async def scan_target(target):
|
|||
services.append(service)
|
||||
|
||||
if services:
|
||||
pending.append(asyncio.create_task(asyncio.sleep(0)))
|
||||
pending.add(asyncio.create_task(asyncio.sleep(0)))
|
||||
else:
|
||||
error('No services were defined. Please check your service syntax: [tcp|udp]/<port>/<service-name>/[secure|insecure]')
|
||||
heartbeat.cancel()
|
||||
autorecon.errors = True
|
||||
return
|
||||
elif config.get('imported_nmap_services') is not None:
|
||||
imported_services_data = config['imported_nmap_services'].get(target.ip) or config['imported_nmap_services'].get(target.address)
|
||||
if imported_services_data:
|
||||
for (protocol, port_num, name, secure) in imported_services_data:
|
||||
if config['proxychains'] and protocol == 'udp':
|
||||
warn('Service ' + protocol + '/' + str(port_num) + '/' + name + ' uses UDP and --proxychains is enabled. Skipping.')
|
||||
continue
|
||||
svc = Service(protocol, port_num, name, secure)
|
||||
svc.target = target
|
||||
services.append(svc)
|
||||
if services:
|
||||
pending.add(asyncio.create_task(asyncio.sleep(0)))
|
||||
else:
|
||||
warn('No usable services for ' + target.address + ' in imported nmap data (all skipped).')
|
||||
heartbeat.cancel()
|
||||
return
|
||||
else:
|
||||
warn('Target ' + target.address + ' (' + target.ip + ') was not found in the imported nmap XML. Falling back to port scanning.')
|
||||
for plugin in target.autorecon.plugin_types['port']:
|
||||
if config['proxychains'] and plugin.type == 'udp':
|
||||
continue
|
||||
processed_marker = os.path.join(scandir, '.port_scans', f".{plugin.slug}")
|
||||
if os.path.exists(processed_marker):
|
||||
info(f"Port Plugin {plugin.name} ({plugin.slug}) has already been run against {target.address}. Skipping.")
|
||||
continue
|
||||
if config['port_scans'] and plugin.slug in config['port_scans']:
|
||||
matching_tags = True
|
||||
excluded_tags = False
|
||||
else:
|
||||
plugin_tag_set = set(plugin.tags)
|
||||
matching_tags = False
|
||||
for tag_group in target.autorecon.tags:
|
||||
if set(tag_group).issubset(plugin_tag_set):
|
||||
matching_tags = True
|
||||
break
|
||||
excluded_tags = False
|
||||
for tag_group in target.autorecon.excluded_tags:
|
||||
if set(tag_group).issubset(plugin_tag_set):
|
||||
excluded_tags = True
|
||||
break
|
||||
if matching_tags and not excluded_tags:
|
||||
target.scans['ports'][plugin.slug] = {'plugin': plugin, 'commands': []}
|
||||
pending.add(asyncio.create_task(port_scan(plugin, target)))
|
||||
else:
|
||||
for plugin in target.autorecon.plugin_types['port']:
|
||||
if config['proxychains'] and plugin.type == 'udp':
|
||||
continue
|
||||
processed_marker = os.path.join(scandir, '.port_scans', f".{plugin.slug}")
|
||||
# If the plugin has already been run against this target, skip it.
|
||||
if os.path.exists(processed_marker):
|
||||
info(f"Port Plugin {plugin.name} ({plugin.slug}) has already been run against {target.address}. Skipping.")
|
||||
continue
|
||||
|
||||
if config['port_scans'] and plugin.slug in config['port_scans']:
|
||||
matching_tags = True
|
||||
|
|
@ -560,7 +706,7 @@ async def scan_target(target):
|
|||
|
||||
if matching_tags and not excluded_tags:
|
||||
target.scans['ports'][plugin.slug] = {'plugin':plugin, 'commands':[]}
|
||||
pending.append(asyncio.create_task(port_scan(plugin, target)))
|
||||
pending.add(asyncio.create_task(port_scan(plugin, target)))
|
||||
|
||||
async with autorecon.lock:
|
||||
autorecon.scanning_targets.append(target)
|
||||
|
|
@ -580,7 +726,7 @@ async def scan_target(target):
|
|||
timed_out = True
|
||||
break
|
||||
|
||||
if not config['force_services']:
|
||||
if not config['force_services'] and config.get('imported_nmap_services') is None:
|
||||
# Extract Services
|
||||
services = []
|
||||
|
||||
|
|
@ -627,6 +773,7 @@ async def scan_target(target):
|
|||
scandir = os.path.join(scandir, protocol + str(port))
|
||||
os.makedirs(scandir, exist_ok=True)
|
||||
os.makedirs(os.path.join(scandir, 'xml'), exist_ok=True)
|
||||
os.makedirs(os.path.join(scandir, '.service_scans'), exist_ok=True)
|
||||
|
||||
# Special cases for HTTP.
|
||||
http_scheme = 'https' if 'https' in service.name or service.secure is True else 'http'
|
||||
|
|
@ -656,6 +803,13 @@ async def scan_target(target):
|
|||
plugin_service_match = False
|
||||
plugin_tag = service.tag() + '/' + plugin.slug
|
||||
|
||||
processed_marker = os.path.join(scandir, '.service_scans', f".{plugin.slug}")
|
||||
# If the plugin has already been run against this service, skip it.
|
||||
if os.path.exists(processed_marker):
|
||||
info(f"Service Plugin {plugin.name} ({plugin.slug}) has already been run against {service.name} on {target.address}. Skipping.")
|
||||
continue
|
||||
|
||||
|
||||
for service_dict in plugin.services:
|
||||
if service_dict['protocol'] == protocol and port in service_dict['port']:
|
||||
for name in service_dict['name']:
|
||||
|
|
@ -901,6 +1055,7 @@ async def run():
|
|||
parser.add_argument('--disable-keyboard-control', action='store_true', help='Disables keyboard control ([s]tatus, Up, Down) if you are in SSH or Docker.')
|
||||
parser.add_argument('--ignore-plugin-checks', action='store_true', help='Ignores errors from plugin check functions that would otherwise prevent AutoRecon from running. Default: %(default)s')
|
||||
parser.add_argument('--force-services', action='store', nargs='+', metavar='SERVICE', help='A space separated list of services in the following style: tcp/80/http tcp/443/https/secure')
|
||||
parser.add_argument('--import-nmap', action='store', type=str, metavar='XML_FILE', help='Import hosts and open ports from an existing nmap XML file and run service scans without port scanning. Targets found in the XML are added automatically unless targets are specified explicitly.')
|
||||
parser.add_argument('-mpti', '--max-plugin-target-instances', action='store', nargs='+', metavar='PLUGIN:NUMBER', help='A space separated list of plugin slugs with the max number of instances (per target) in the following style: nmap-http:2 dirbuster:1. Default: %(default)s')
|
||||
parser.add_argument('-mpgi', '--max-plugin-global-instances', action='store', nargs='+', metavar='PLUGIN:NUMBER', help='A space separated list of plugin slugs with the max number of global instances in the following style: nmap-http:2 dirbuster:1. Default: %(default)s')
|
||||
parser.add_argument('--accessible', action='store_true', help='Attempts to make AutoRecon output more accessible to screenreaders. Default: %(default)s')
|
||||
|
|
@ -1250,7 +1405,7 @@ async def run():
|
|||
mode = 'udp'
|
||||
port = port.split('U:')[1]
|
||||
|
||||
match = re.search('^([0-9]+)\-([0-9]+)$', port)
|
||||
match = re.search(r'^([0-9]+)-([0-9]+)$', port)
|
||||
if match:
|
||||
num1 = int(match.group(1))
|
||||
num2 = int(match.group(2))
|
||||
|
|
@ -1336,7 +1491,7 @@ async def run():
|
|||
errors = True
|
||||
|
||||
if not errors:
|
||||
if config['force_services']:
|
||||
if config['force_services'] or config.get('imported_nmap_services') is not None:
|
||||
autorecon.service_scan_semaphore = asyncio.Semaphore(config['max_scans'])
|
||||
else:
|
||||
autorecon.port_scan_semaphore = asyncio.Semaphore(config['max_port_scans'])
|
||||
|
|
@ -1394,6 +1549,21 @@ async def run():
|
|||
error('The target file ' + args.target_file + ' could not be read.')
|
||||
sys.exit(1)
|
||||
|
||||
if args.import_nmap:
|
||||
if not os.path.isfile(args.import_nmap):
|
||||
error('The nmap XML file "' + args.import_nmap + '" was not found.')
|
||||
sys.exit(1)
|
||||
imported = parse_nmap_xml(args.import_nmap)
|
||||
if not imported:
|
||||
error('No hosts with open ports found in nmap XML file "' + args.import_nmap + '".')
|
||||
sys.exit(1)
|
||||
config['imported_nmap_services'] = imported
|
||||
info('Imported ' + str(len(imported)) + ' host(s) from "' + args.import_nmap + '".')
|
||||
# When no explicit targets given, use all hosts from the XML.
|
||||
if not raw_targets:
|
||||
for address in imported:
|
||||
raw_targets.append(address)
|
||||
|
||||
unresolvable_targets = False
|
||||
for target in raw_targets:
|
||||
try:
|
||||
|
|
@ -1493,7 +1663,7 @@ async def run():
|
|||
error('A total of ' + str(len(autorecon.pending_targets)) + ' targets would be scanned. If this is correct, re-run with the --disable-sanity-checks option to suppress this check.')
|
||||
errors = True
|
||||
|
||||
if not config['force_services']:
|
||||
if not config['force_services'] and config.get('imported_nmap_services') is None:
|
||||
port_scan_plugin_count = 0
|
||||
for plugin in autorecon.plugin_types['port']:
|
||||
if config['port_scans'] and plugin.slug in config['port_scans']:
|
||||
|
|
@ -1533,10 +1703,10 @@ async def run():
|
|||
if not config['disable_keyboard_control']:
|
||||
terminal_settings = termios.tcgetattr(sys.stdin.fileno())
|
||||
|
||||
pending = []
|
||||
pending = set()
|
||||
i = 0
|
||||
while autorecon.pending_targets:
|
||||
pending.append(asyncio.create_task(scan_target(autorecon.pending_targets.pop(0))))
|
||||
pending.add(asyncio.create_task(scan_target(autorecon.pending_targets.pop(0))))
|
||||
i+=1
|
||||
if i >= num_initial_targets:
|
||||
break
|
||||
|
|
@ -1572,7 +1742,7 @@ async def run():
|
|||
for targ in autorecon.scanning_targets:
|
||||
for process_list in targ.running_tasks.values():
|
||||
# If we're not scanning ports, count ServiceScans instead.
|
||||
if config['force_services']:
|
||||
if config['force_services'] or config.get('imported_nmap_services') is not None:
|
||||
if issubclass(process_list['plugin'].__class__, ServiceScan): # TODO should we really count ServiceScans? Test...
|
||||
port_scan_task_count += 1
|
||||
else:
|
||||
|
|
|
|||
Loading…
Reference in New Issue