From ca77242c8661f5b5a083b28ead31bf8f4908478b Mon Sep 17 00:00:00 2001 From: MrMatch246 Date: Mon, 4 Aug 2025 17:14:44 +0200 Subject: [PATCH 1/7] added continue functionality for port and service plugins --- autorecon/main.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/autorecon/main.py b/autorecon/main.py index bbc0ddd..c5abcb4 100644 --- a/autorecon/main.py +++ b/autorecon/main.py @@ -329,6 +329,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,6 +454,7 @@ 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): @@ -485,6 +487,7 @@ 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') @@ -539,6 +542,11 @@ async def scan_target(target): 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 @@ -627,6 +635,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 +665,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']: From 8bfedde7b319866e4ea9ce4ce8c3f2a9f47736a7 Mon Sep 17 00:00:00 2001 From: MrMatch246 Date: Wed, 6 Aug 2025 11:43:19 +0200 Subject: [PATCH 2/7] coherent usage of sets --- autorecon/main.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/autorecon/main.py b/autorecon/main.py index c5abcb4..5de2cdf 100644 --- a/autorecon/main.py +++ b/autorecon/main.py @@ -509,7 +509,7 @@ async def scan_target(target): target.reportdir = reportdir - pending = [] + pending = set() heartbeat = asyncio.create_task(start_heartbeat(target, period=config['heartbeat'])) @@ -532,7 +532,7 @@ 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]///[secure|insecure]') heartbeat.cancel() @@ -568,7 +568,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) @@ -1549,10 +1549,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 From ec2283ba45853804ae9d0172ee51a659ace063bf Mon Sep 17 00:00:00 2001 From: MrMatch246 Date: Wed, 6 Aug 2025 11:43:47 +0200 Subject: [PATCH 3/7] use of raw strings for regex --- autorecon/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autorecon/main.py b/autorecon/main.py index 5de2cdf..64c767e 100644 --- a/autorecon/main.py +++ b/autorecon/main.py @@ -518,7 +518,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(tcp|udp))\/(?P\d+)\/(?P[\w\-]+)(\/(?Psecure|insecure))?', forced_service) + match = re.search(r'(?P(tcp|udp))/(?P\d+)/(?P[\w\-]+)(/(?Psecure|insecure))?', forced_service) if match: protocol = match.group('protocol') if config['proxychains'] and protocol == 'udp': @@ -1266,7 +1266,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)) From 220cb06ae0997efef70db7aa6bac18eb657c2fa4 Mon Sep 17 00:00:00 2001 From: MrMatch246 Date: Tue, 12 Aug 2025 17:18:59 +0200 Subject: [PATCH 4/7] added nuclei --- README.md | 2 +- autorecon/default-plugins/nuclei.py | 36 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 autorecon/default-plugins/nuclei.py diff --git a/README.md b/README.md index 74ade16..c88e90f 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/autorecon/default-plugins/nuclei.py b/autorecon/default-plugins/nuclei.py new file mode 100644 index 0000000..7d99198 --- /dev/null +++ b/autorecon/default-plugins/nuclei.py @@ -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, + ) \ No newline at end of file From 3d82bb70e53701632393a48387197b0a4c13e364 Mon Sep 17 00:00:00 2001 From: MrMatch246 Date: Wed, 13 Aug 2025 16:46:05 +0200 Subject: [PATCH 5/7] fixed reporting-markdown.py --- .../default-plugins/reporting-markdown.py | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/autorecon/default-plugins/reporting-markdown.py b/autorecon/default-plugins/reporting-markdown.py index bba0330..86f8f19 100644 --- a/autorecon/default-plugins/reporting-markdown.py +++ b/autorecon/default-plugins/reporting-markdown.py @@ -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```') From 823aa059dfdf3f738aade9b295690773b4d38514 Mon Sep 17 00:00:00 2001 From: MrMatch246 Date: Thu, 4 Sep 2025 15:53:23 +0200 Subject: [PATCH 6/7] Added option for plugin renewals --- autorecon/main.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/autorecon/main.py b/autorecon/main.py index 64c767e..24ceba7 100644 --- a/autorecon/main.py +++ b/autorecon/main.py @@ -41,8 +41,9 @@ if not os.path.exists(config['data_dir']): 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')) + develop =False + if not os.path.exists(os.path.join(config['data_dir'], 'plugins')) or develop: + shutil.copytree(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'default-plugins'), os.path.join(config['data_dir'], 'plugins'), dirs_exist_ok=True) 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)): @@ -499,8 +500,8 @@ 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) From 8af0595cd62e48191690386d00beef6f34027648 Mon Sep 17 00:00:00 2001 From: MrMatch246 <50702646+MrMatch246@users.noreply.github.com> Date: Wed, 17 Sep 2025 11:49:16 +0200 Subject: [PATCH 7/7] Fixed ' that breaks md highlighting in obsidian --- autorecon/default-plugins/bruteforce-http.py | 2 +- autorecon/default-plugins/smtp-user-enum.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/autorecon/default-plugins/bruteforce-http.py b/autorecon/default-plugins/bruteforce-http.py index d735ac4..1b7ff7f 100644 --- a/autorecon/default-plugins/bruteforce-http.py +++ b/autorecon/default-plugins/bruteforce-http.py @@ -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"', diff --git a/autorecon/default-plugins/smtp-user-enum.py b/autorecon/default-plugins/smtp-user-enum.py index 5a995b2..18a8c02 100644 --- a/autorecon/default-plugins/smtp-user-enum.py +++ b/autorecon/default-plugins/smtp-user-enum.py @@ -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 with the target\'s domain name:', [ + service.add_manual_command('Try User Enumeration using "RCPT TO". Replace 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 ' ])