fe519c9fcf
- 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
319 lines
12 KiB
Python
319 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Cleanup and teardown engine for C2ingRed deployments
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
import logging
|
|
import json
|
|
import glob
|
|
|
|
# Add project root to path
|
|
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
|
|
|
|
from utils.common import COLORS, PROVIDER_DIRS, confirm_action
|
|
|
|
def teardown_by_deployment_id(deployment_id):
|
|
"""Teardown infrastructure by deployment ID"""
|
|
print(f"\n{COLORS['BLUE']}Teardown Infrastructure by ID: {deployment_id}{COLORS['RESET']}")
|
|
|
|
# Look for deployment logs to determine provider and configuration
|
|
log_files = glob.glob(f"logs/deployment_{deployment_id}*.log")
|
|
|
|
if not log_files:
|
|
print(f"{COLORS['RED']}No deployment logs found for ID: {deployment_id}{COLORS['RESET']}")
|
|
return False
|
|
|
|
# Try to find deployment info files
|
|
info_files = glob.glob(f"logs/deployment_info_{deployment_id}*.txt")
|
|
|
|
if info_files:
|
|
config = parse_deployment_info(info_files[0])
|
|
if config:
|
|
return execute_teardown(config)
|
|
|
|
print(f"{COLORS['YELLOW']}Could not determine deployment configuration from logs{COLORS['RESET']}")
|
|
print(f"{COLORS['YELLOW']}Manual cleanup may be required{COLORS['RESET']}")
|
|
return False
|
|
|
|
def teardown_all_infrastructure():
|
|
"""Teardown all deployed infrastructure"""
|
|
print(f"\n{COLORS['RED']}⚠️ WARNING: This will attempt to destroy ALL infrastructure!{COLORS['RESET']}")
|
|
|
|
if not confirm_action("Are you absolutely sure?", default=False):
|
|
return False
|
|
|
|
# Find all deployment info files
|
|
info_files = glob.glob("logs/deployment_info_*.txt")
|
|
|
|
if not info_files:
|
|
print(f"{COLORS['GREEN']}No deployment info files found - nothing to teardown{COLORS['RESET']}")
|
|
return True
|
|
|
|
print(f"Found {len(info_files)} deployments to teardown:")
|
|
|
|
success_count = 0
|
|
for info_file in info_files:
|
|
config = parse_deployment_info(info_file)
|
|
if config:
|
|
deployment_id = config.get('deployment_id', 'unknown')
|
|
print(f"\nTearing down deployment: {deployment_id}")
|
|
if execute_teardown(config):
|
|
success_count += 1
|
|
|
|
print(f"\n{COLORS['GREEN']}Successfully tore down {success_count}/{len(info_files)} deployments{COLORS['RESET']}")
|
|
return success_count == len(info_files)
|
|
|
|
def parse_deployment_info(info_file):
|
|
"""Parse deployment info from file to extract configuration"""
|
|
try:
|
|
config = {}
|
|
with open(info_file, 'r') as f:
|
|
lines = f.readlines()
|
|
|
|
in_config_section = False
|
|
for line in lines:
|
|
line = line.strip()
|
|
|
|
if line == "Configuration:":
|
|
in_config_section = True
|
|
continue
|
|
elif line.startswith("-" * 30):
|
|
continue
|
|
elif line.startswith("Access Information:"):
|
|
break
|
|
|
|
if in_config_section and ": " in line:
|
|
key, value = line.split(": ", 1)
|
|
|
|
# Convert string values back to appropriate types
|
|
if value.lower() == 'true':
|
|
value = True
|
|
elif value.lower() == 'false':
|
|
value = False
|
|
elif value.lower() == 'none':
|
|
value = None
|
|
|
|
config[key] = value
|
|
|
|
return config
|
|
except Exception as e:
|
|
logging.error(f"Failed to parse deployment info from {info_file}: {e}")
|
|
return None
|
|
|
|
def execute_teardown(config):
|
|
"""Execute teardown based on configuration"""
|
|
provider = config.get('provider')
|
|
deployment_id = config.get('deployment_id')
|
|
|
|
if not provider or not deployment_id:
|
|
print(f"{COLORS['RED']}Missing provider or deployment ID{COLORS['RESET']}")
|
|
return False
|
|
|
|
print(f"{COLORS['BLUE']}Executing teardown for {provider} deployment {deployment_id}...{COLORS['RESET']}")
|
|
|
|
# Set up logging for teardown
|
|
from utils.common import setup_logging
|
|
setup_logging(deployment_id, "teardown")
|
|
|
|
try:
|
|
# Set provider-specific environment variables
|
|
set_provider_environment_for_teardown(config)
|
|
|
|
# Get correct provider directory
|
|
provider_dir = PROVIDER_DIRS.get(provider, provider.capitalize())
|
|
|
|
# Execute teardown playbook
|
|
playbook = f"providers/{provider_dir}/cleanup.yml"
|
|
|
|
if not os.path.exists(playbook):
|
|
print(f"{COLORS['YELLOW']}Teardown playbook not found: {playbook}{COLORS['RESET']}")
|
|
print(f"{COLORS['YELLOW']}Manual cleanup required{COLORS['RESET']}")
|
|
return manual_cleanup_guidance(config)
|
|
|
|
success = run_teardown_playbook(playbook, config)
|
|
|
|
if success:
|
|
# Clean up local SSH keys
|
|
cleanup_ssh_keys_for_deployment(deployment_id)
|
|
|
|
# Move deployment logs to cleanup folder
|
|
archive_deployment_logs(deployment_id)
|
|
|
|
print(f"{COLORS['GREEN']}Teardown completed successfully{COLORS['RESET']}")
|
|
else:
|
|
print(f"{COLORS['RED']}Teardown failed - manual cleanup may be required{COLORS['RESET']}")
|
|
|
|
return success
|
|
|
|
except Exception as e:
|
|
logging.error(f"Teardown execution failed: {e}")
|
|
print(f"{COLORS['RED']}Teardown execution failed: {e}{COLORS['RESET']}")
|
|
return False
|
|
|
|
def set_provider_environment_for_teardown(config):
|
|
"""Set provider-specific environment variables for teardown"""
|
|
provider = config.get('provider')
|
|
|
|
if provider == "aws":
|
|
if config.get('aws_access_key'):
|
|
os.environ['AWS_ACCESS_KEY_ID'] = config['aws_access_key']
|
|
if config.get('aws_secret_key'):
|
|
os.environ['AWS_SECRET_ACCESS_KEY'] = config['aws_secret_key']
|
|
elif provider == "linode":
|
|
# Try to get token from config first, then from vars file
|
|
if config.get('linode_token'):
|
|
os.environ['LINODE_TOKEN'] = config['linode_token']
|
|
else:
|
|
# Load token from provider vars file
|
|
try:
|
|
from utils.common import load_vars_file, PROVIDER_DIRS
|
|
vars_data = load_vars_file(provider)
|
|
if vars_data and vars_data.get('linode_token'):
|
|
os.environ['LINODE_TOKEN'] = vars_data['linode_token']
|
|
config['linode_token'] = vars_data['linode_token'] # Add to config for later use
|
|
else:
|
|
logging.warning("Linode token not found in vars file - cleanup may fail")
|
|
except Exception as e:
|
|
logging.warning(f"Failed to load Linode token from vars file: {e}")
|
|
|
|
def run_teardown_playbook(playbook, config):
|
|
"""Run the teardown Ansible playbook"""
|
|
try:
|
|
cmd = [
|
|
'ansible-playbook',
|
|
playbook,
|
|
'--extra-vars', create_teardown_vars(config)
|
|
]
|
|
|
|
if config.get('debug'):
|
|
cmd.append('-vvv')
|
|
|
|
logging.info(f"Executing teardown: {' '.join(cmd)}")
|
|
|
|
result = subprocess.run(
|
|
cmd,
|
|
capture_output=True,
|
|
text=True,
|
|
cwd=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
)
|
|
|
|
if result.returncode == 0:
|
|
logging.info("Teardown playbook executed successfully")
|
|
return True
|
|
else:
|
|
logging.error(f"Teardown playbook failed with return code {result.returncode}")
|
|
logging.error(f"STDOUT: {result.stdout}")
|
|
logging.error(f"STDERR: {result.stderr}")
|
|
return False
|
|
|
|
except Exception as e:
|
|
logging.error(f"Error executing teardown playbook: {e}")
|
|
return False
|
|
|
|
def create_teardown_vars(config):
|
|
"""Create Ansible extra vars for teardown"""
|
|
teardown_vars = {
|
|
'deployment_id': config.get('deployment_id'),
|
|
'provider': config.get('provider'),
|
|
'operation': 'teardown'
|
|
}
|
|
|
|
# Add instance names for proper cleanup
|
|
if config.get('redirector_name'):
|
|
teardown_vars['redirector_name'] = config['redirector_name']
|
|
if config.get('c2_name'):
|
|
teardown_vars['c2_name'] = config['c2_name']
|
|
if config.get('tracker_name'):
|
|
teardown_vars['tracker_name'] = config['tracker_name']
|
|
if config.get('attack_box_name'):
|
|
teardown_vars['attack_box_name'] = config['attack_box_name']
|
|
|
|
# Add provider-specific vars if present
|
|
if config.get('aws_access_key'):
|
|
teardown_vars['aws_access_key'] = config['aws_access_key']
|
|
if config.get('aws_secret_key'):
|
|
teardown_vars['aws_secret_key'] = config['aws_secret_key']
|
|
if config.get('aws_region'):
|
|
teardown_vars['aws_region'] = config['aws_region']
|
|
if config.get('linode_token'):
|
|
teardown_vars['linode_token'] = config['linode_token']
|
|
if config.get('linode_region'):
|
|
teardown_vars['linode_region'] = config['linode_region']
|
|
|
|
return json.dumps(teardown_vars)
|
|
|
|
def cleanup_ssh_keys_for_deployment(deployment_id):
|
|
"""Clean up SSH keys for a specific deployment"""
|
|
ssh_dir = os.path.expanduser("~/.ssh")
|
|
key_pattern = f"c2deploy_{deployment_id}*"
|
|
|
|
for key_file in glob.glob(os.path.join(ssh_dir, key_pattern)):
|
|
try:
|
|
os.remove(key_file)
|
|
logging.info(f"Removed SSH key: {key_file}")
|
|
except Exception as e:
|
|
logging.warning(f"Failed to remove SSH key {key_file}: {e}")
|
|
|
|
def archive_deployment_logs(deployment_id):
|
|
"""Archive deployment logs after successful teardown"""
|
|
try:
|
|
# Create archive directory
|
|
archive_dir = "logs/archive"
|
|
os.makedirs(archive_dir, exist_ok=True)
|
|
|
|
# Move all files related to this deployment
|
|
log_patterns = [
|
|
f"logs/deployment_{deployment_id}*",
|
|
f"logs/deployment_info_{deployment_id}*",
|
|
f"logs/teardown_{deployment_id}*"
|
|
]
|
|
|
|
for pattern in log_patterns:
|
|
for file_path in glob.glob(pattern):
|
|
archive_path = os.path.join(archive_dir, os.path.basename(file_path))
|
|
os.rename(file_path, archive_path)
|
|
logging.info(f"Archived: {file_path} -> {archive_path}")
|
|
|
|
except Exception as e:
|
|
logging.warning(f"Failed to archive logs for {deployment_id}: {e}")
|
|
|
|
def manual_cleanup_guidance(config):
|
|
"""Provide manual cleanup guidance when automated teardown isn't available"""
|
|
provider = config.get('provider')
|
|
deployment_id = config.get('deployment_id')
|
|
|
|
print(f"\n{COLORS['YELLOW']}Manual Cleanup Required{COLORS['RESET']}")
|
|
print(f"{COLORS['YELLOW']}========================{COLORS['RESET']}")
|
|
print(f"Deployment ID: {deployment_id}")
|
|
print(f"Provider: {provider}")
|
|
|
|
if provider == "aws":
|
|
print(f"\nAWS Resources to check:")
|
|
print(f"- EC2 instances with tags containing: {deployment_id}")
|
|
print(f"- Security groups with names containing: {deployment_id}")
|
|
print(f"- Key pairs with names containing: {deployment_id}")
|
|
print(f"- EIPs associated with the deployment")
|
|
|
|
elif provider == "linode":
|
|
print(f"\nLinode Resources to check:")
|
|
print(f"- Linodes with labels containing: {deployment_id}")
|
|
print(f"- NodeBalancers with labels containing: {deployment_id}")
|
|
print(f"- Firewalls with labels containing: {deployment_id}")
|
|
|
|
elif provider == "flokinet":
|
|
print(f"\nFlokiNET Resources to check:")
|
|
print(f"- Check your FlokiNET control panel for resources created")
|
|
print(f"- Look for servers with the deployment ID: {deployment_id}")
|
|
|
|
print(f"\nLocal cleanup:")
|
|
print(f"- SSH keys: ~/.ssh/c2deploy_{deployment_id}*")
|
|
print(f"- Log files: logs/*{deployment_id}*")
|
|
|
|
return False # Manual cleanup required
|
|
|
|
if __name__ == "__main__":
|
|
print("Cleanup engine loaded")
|