diff --git a/AWS/cleanup.yml b/AWS/cleanup.yml index 3a7be3b..430fce5 100644 --- a/AWS/cleanup.yml +++ b/AWS/cleanup.yml @@ -433,6 +433,18 @@ state: absent ignore_errors: yes register: infra_file + + - name: Clean up deployment state file + file: + path: "{{ playbook_dir }}/../deployment_state_{{ deployment_id }}.json" + state: absent + ignore_errors: yes + register: state_file_deletion + + - name: Report state file cleanup + debug: + msg: "Deployment state file {{ 'deleted' if state_file_deletion.changed else 'not found' }}" + when: state_file_deletion is defined # STEP 16: Enhanced and Accurate Cleanup Summary - name: Enhanced cleanup summary diff --git a/Linode/c2.yml b/Linode/c2.yml old mode 100644 new mode 100755 diff --git a/Linode/cleanup.yml b/Linode/cleanup.yml old mode 100644 new mode 100755 index f8eae24..01eee70 --- a/Linode/cleanup.yml +++ b/Linode/cleanup.yml @@ -73,6 +73,18 @@ when: cleanup_tracker and tracker_name is defined and tracker_name != "" register: tracker_deletion ignore_errors: yes + + - name: Clean up deployment state file + file: + path: "{{ playbook_dir }}/../deployment_state_{{ deployment_id }}.json" + state: absent + ignore_errors: yes + register: state_file_deletion + + - name: Report state file cleanup + debug: + msg: "Deployment state file {{ 'deleted' if state_file_deletion.changed else 'not found' }}" + when: state_file_deletion is defined - name: Report cleanup status debug: diff --git a/Linode/redirector.yml b/Linode/redirector.yml old mode 100644 new mode 100755 diff --git a/Linode/tracker.yml b/Linode/tracker.yml old mode 100644 new mode 100755 diff --git a/ansible.cfg b/ansible.cfg index a0f8b3b..a7c563d 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -1,8 +1,14 @@ [defaults] host_key_checking = False timeout = 30 +retry_files_enabled = False +gathering = smart +fact_caching = memory +stdout_callback = default +bin_ansible_callbacks = True +nocows = 1 [ssh_connection] -ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o ServerAliveInterval=30 -o ServerAliveCountMax=10 +ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes pipelining = True -retries = 5 +control_path = ~/.ansible/cp/%%h-%%p-%%r diff --git a/deploy.py b/deploy.py index 7982d2c..857b193 100755 --- a/deploy.py +++ b/deploy.py @@ -101,14 +101,16 @@ def main_menu(): print(f"3) Deploy C2 Server") print(f"4) Deploy Redirector") print(f"5) Deploy Email Tracking Server") - print(f"6) Deploy Payload Server {COLORS['GRAY']}*UNDER-CONSTRUCTION*{COLORS['RESET']}") - print(f"7) Deploy Phishing Server {COLORS['GRAY']}*UNDER-CONSTRUCTION*{COLORS['RESET']}") - print(f"8) Deploy Logging Server {COLORS['GRAY']}*UNDER-CONSTRUCTION*{COLORS['RESET']}") - print(f"9) Deploy Share-Drive {COLORS['GRAY']}*UNDER-CONSTRUCTION*{COLORS['RESET']}") - print(f"10) Deploy Hashtopolis {COLORS['GRAY']}*UNDER-CONSTRUCTION*{COLORS['RESET']}") - print(f"11) Custom Deployment") - print(f"12) Tools") - print(f"13) Debug Mode: {COLORS['GREEN'] if debug_mode else COLORS['RED']}{debug_mode}{COLORS['RESET']}") + print(f"6) Deploy Advanced Phishing Infrastructure") + print(f"7) Deploy Ephemeral MTA Front-End") + print(f"8) Deploy Payload Server {COLORS['GRAY']}*UNDER-CONSTRUCTION*{COLORS['RESET']}") + print(f"9) Deploy Phishing Server {COLORS['GRAY']}*UNDER-CONSTRUCTION*{COLORS['RESET']}") + print(f"10) Deploy Logging Server {COLORS['GRAY']}*UNDER-CONSTRUCTION*{COLORS['RESET']}") + print(f"11) Deploy Share-Drive {COLORS['GRAY']}*UNDER-CONSTRUCTION*{COLORS['RESET']}") + print(f"12) Deploy Hashtopolis {COLORS['GRAY']}*UNDER-CONSTRUCTION*{COLORS['RESET']}") + print(f"13) Custom Deployment") + print(f"14) Tools") + print(f"15) Debug Mode: {COLORS['GREEN'] if debug_mode else COLORS['RED']}{debug_mode}{COLORS['RESET']}") print(f"\n") print(f"99) Exit") @@ -124,7 +126,11 @@ def main_menu(): redirector_menu() elif choice == "5": deploy_tracker() - elif choice in ["6", "7", "8", "9", "10"]: + elif choice == "6": + deploy_ephemeral_mta() + elif choice == "7": + deploy_advanced_phishing_infrastructure() + elif choice in ["8", "9", "10", "11", "12"]: print(f"\n{COLORS['YELLOW']}This feature is currently under construction.{COLORS['RESET']}") input("\nPress Enter to continue...") elif choice == "11": @@ -209,6 +215,7 @@ def deploy_full_c2(): config['c2_only'] = False config['deploy_tracker'] = True config['integrated_tracker'] = False + config['deploy_ephemeral_mta'] = True # Deployment ID will be generated in execute_deployment execute_deployment(config) @@ -227,6 +234,28 @@ def deploy_basic_c2(): # Deployment ID will be generated in execute_deployment execute_deployment(config) +def deploy_ephemeral_mta(): + """Deploy Ephemeral MTA for phishing campaigns with high OPSEC""" + config = gather_ephemeral_mta_parameters() + if not config: + return + + # Add ephemeral MTA flag + config['ephemeral_mta'] = True + execute_deployment(config) + +def deploy_advanced_phishing_infrastructure(): + """Deploy full phishing infrastructure with Ephemeral MTA + GoPhish + C2 integration""" + + config = gather_advanced_phishing_parameters() + if not config: + return + + # Add both flags + config['advanced_phishing'] = True + config['ephemeral_mta'] = True + execute_deployment(config) + def deploy_c2_server(): """Deploy only the C2 server""" config = gather_common_parameters() @@ -382,6 +411,118 @@ def gather_common_parameters(): return config +def gather_ephemeral_mta_parameters(): + """Gather parameters needed for ephemeral MTA deployment""" + config = gather_common_parameters() + if not config: + return None + + print(f"\n{COLORS['BLUE']}Ephemeral MTA Configuration{COLORS['RESET']}") + print(f"\n{COLORS['YELLOW']}IMPORTANT OPSEC NOTES:{COLORS['RESET']}") + print(f"- Use completely separate domains for phishing campaigns") + print(f"- Never use your operational domains for phishing") + print(f"- Use generic, innocuous domain names that blend with business traffic") + print(f"- Campaign IDs should look natural, not like 'wave42'") + + # Use separate domains for phishing campaigns + phish_domain = input(f"\nEnter phishing domain (separate from C2 domain): ") + if not phish_domain: + print(f"{COLORS['RED']}A separate phishing domain is required for proper OPSEC.{COLORS['RESET']}") + return None + config['phish_domain'] = phish_domain + + # Generate a campaign ID that looks natural + default_campaign_id = f"mail{generate_random_string(4).lower()}" + campaign_id = input(f"Enter campaign identifier [default: {default_campaign_id}]: ") or default_campaign_id + config['campaign_id'] = campaign_id + + # Email for Let's Encrypt + default_email = f"admin@{phish_domain}" + email = input(f"Enter email for Let's Encrypt [default: {default_email}]: ") or default_email + config['letsencrypt_email'] = email + + # C2 connection method + print(f"\n{COLORS['BLUE']}C2 Connection Configuration{COLORS['RESET']}") + print(f"1) SSH Tunnel (recommended)") + print(f"2) Direct connection (less secure)") + connection_type = input(f"Select connection method [default: 1]: ") or "1" + + if connection_type == "1": + config['use_ssh_tunnel'] = True + + # Get SSH key for C2 access + if 'deployment_id' in config: + default_key_path = f"~/.ssh/c2deploy_{config['deployment_id']}.pem" + else: + default_key_path = "~/.ssh/id_rsa" + + ssh_key_path = input(f"Enter SSH key path for C2 access [default: {default_key_path}]: ") or default_key_path + config['c2_ssh_key_path'] = os.path.expanduser(ssh_key_path) + else: + config['use_ssh_tunnel'] = False + + # C2 server info for mail delivery - over private tunnel, not public DNS + c2_ip = input(f"Enter C2 server IP for mail delivery: ") + if not c2_ip: + print(f"{COLORS['RED']}C2 server IP is required for mail delivery.{COLORS['RESET']}") + return None + config['static_mailstore_ip'] = c2_ip + + # DKIM settings + print(f"\n{COLORS['BLUE']}DKIM Configuration{COLORS['RESET']}") + use_dkim = input(f"Configure DKIM for better deliverability? (y/n) [default: y]: ").lower() != 'n' + config['use_dkim'] = use_dkim + + # Number of MTAs to deploy (for rotation) + num_mtas = input(f"\nNumber of MTAs to deploy (for IP rotation) [default: 1]: ") or "1" + try: + config['num_mtas'] = int(num_mtas) + except ValueError: + config['num_mtas'] = 1 + + return config + +def gather_advanced_phishing_parameters(): + """Collect parameters for advanced phishing infrastructure""" + + # Start with ephemeral MTA parameters + config = gather_ephemeral_mta_parameters() + if not config: + return None + + # Add GoPhish configuration + print(f"\n{COLORS['BLUE']}GoPhish Configuration{COLORS['RESET']}") + + # SMTP authentication details + default_smtp_user = f"mail{generate_random_string(4)}" + smtp_user = input(f"Enter SMTP auth username [default: {default_smtp_user}]: ") or default_smtp_user + config['smtp_auth_user'] = smtp_user + + smtp_pass = input(f"Enter SMTP auth password [default: random]: ") + if not smtp_pass: + smtp_pass = generate_random_string(16) + print(f"Generated SMTP password: {smtp_pass}") + config['smtp_auth_pass'] = smtp_pass + + # GoPhish admin port + default_gophish_port = str(random.randint(2000, 6000)) + gophish_port = input(f"Enter GoPhish admin port [default: {default_gophish_port}]: ") or default_gophish_port + config['gophish_admin_port'] = gophish_port + + # Landing page configuration + print(f"\n{COLORS['BLUE']}Landing Page Configuration{COLORS['RESET']}") + use_landing_page = input(f"Configure phishing landing page? (y/n) [default: y]: ").lower() != 'n' + config['use_landing_page'] = use_landing_page + + if use_landing_page: + landing_domain = input(f"Enter landing page domain (separate from phish domain): ") + if landing_domain: + config['landing_domain'] = landing_domain + else: + print(f"{COLORS['YELLOW']}Will use redirector domain for landing pages.{COLORS['RESET']}") + + return config + def get_aws_credentials(provider_vars): """Get AWS credentials from user or vars file""" default_aws_key = provider_vars.get('aws_access_key', '') @@ -2001,6 +2142,11 @@ def teardown_infrastructure(config): elif vars_data.get('aws_secret_key'): os.environ['AWS_SECRET_ACCESS_KEY'] = vars_data['aws_secret_key'] config['aws_secret_key'] = vars_data['aws_secret_key'] + elif provider == "linode": + # Explicitly load Linode token from vars.yaml if not already in config + if not config.get('linode_token') and vars_data.get('linode_token'): + config['linode_token'] = vars_data['linode_token'] + logging.info("Loaded Linode token from vars.yaml for cleanup") # Set default resource names based on deployment ID config['redirector_name'] = f"r-{deployment_id}" @@ -2289,10 +2435,10 @@ def generate_deployment_info(config, success=True): # Add correct SSH commands for each server type if not config.get('redirector_only'): - info.append(f"SSH Command for C2: ssh -t -o 'StrictHostKeyChecking=no' -o 'UserKnownHostsFile=/dev/null' -o 'IdentitiesOnly=yes' -i {ssh_key} ubuntu@{config.get('c2_ip', 'N/A')}") + info.append(f"SSH Command for C2: ssh -t -o 'StrictHostKeyChecking=no' -o 'UserKnownHostsFile=/dev/null' -o 'IdentitiesOnly=yes' -i {ssh_key} {ssh_user}@{config.get('c2_ip', 'N/A')}") if not config.get('c2_only'): - info.append(f"SSH Command for Redirector: ssh -t -o 'StrictHostKeyChecking=no' -o 'UserKnownHostsFile=/dev/null' -o 'IdentitiesOnly=yes' -i {ssh_key} {ssh_user}@{config.get('redirector_ip', 'N/A')}") + info.append(f"SSH Command for Redirector: ssh -t -o 'StrictHostKeyChecking=no' -o 'UserKnownHostsFile=/dev/null' -o 'IdentitiesOnly=yes' -i {ssh_key} ubuntu@{config.get('redirector_ip', 'N/A')}") if config.get('deploy_tracker') and not config.get('integrated_tracker'): info.append(f"SSH Command for Tracker: ssh -t -o 'StrictHostKeyChecking=no' -o 'UserKnownHostsFile=/dev/null' -o 'IdentitiesOnly=yes' -i {ssh_key} {ssh_user}@{config.get('tracker_ip', 'N/A')}") diff --git a/requirements.txt b/requirements.txt index d33ed27..09e267a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ boto3 botocore awscli passlib -paramiko \ No newline at end of file +paramiko +linode-cli \ No newline at end of file diff --git a/tasks/configure_redirector.yml b/tasks/configure_redirector.yml index edb6d79..0c92a9f 100644 --- a/tasks/configure_redirector.yml +++ b/tasks/configure_redirector.yml @@ -10,21 +10,226 @@ group: root mode: '0644' -- name: Install required packages for redirector +- name: Update package cache with retries + apt: + update_cache: yes + cache_valid_time: 0 + register: cache_update + until: cache_update is success + retries: 5 + delay: 10 + ignore_errors: no + +- name: Install core packages first (high priority) apt: name: - nginx - - certbot - - python3-certbot-nginx - - socat - - netcat-openbsd - - secure-delete - - jq - - php-fpm - - net-tools - nginx-extras + - socat + - jq + - secure-delete state: present - update_cache: yes + update_cache: no + register: core_packages + until: core_packages is success + retries: 3 + delay: 5 + +- name: Install network utilities with fallbacks + block: + - name: Try to install net-tools + apt: + name: net-tools + state: present + update_cache: no + register: net_tools_install + + rescue: + - name: Install alternative network utilities + apt: + name: + - iproute2 + - iputils-ping + - netcat-openbsd + state: present + update_cache: no + register: alt_network_tools + + - name: Create net-tools compatibility aliases + copy: + dest: /usr/local/bin/netstat + content: | + #!/bin/bash + # Compatibility wrapper for netstat using ss + ss "$@" + mode: '0755' + when: alt_network_tools is success + +- name: Install PHP-FPM with version handling + block: + - name: Install PHP-FPM (latest available) + apt: + name: php-fpm + state: present + update_cache: no + register: php_install + + rescue: + - name: Install specific PHP version as fallback + apt: + name: + - php7.4-fpm + - php7.4-cli + state: present + update_cache: no + register: php_fallback + +- name: Install certbot with dependency handling + block: + - name: Install certbot and nginx plugin + apt: + name: + - certbot + - python3-certbot-nginx + state: present + update_cache: no + register: certbot_install + + rescue: + - name: Install certbot without problematic dependencies + shell: | + apt-get install -y --no-install-recommends certbot + apt-get install -y --fix-broken || true + register: certbot_manual + ignore_errors: yes + + - name: Install certbot via snap as ultimate fallback + block: + - name: Install snap if not present + apt: + name: snapd + state: present + + - name: Install certbot via snap + snap: + name: certbot + classic: yes + + - name: Create certbot symlink + file: + src: /snap/bin/certbot + dest: /usr/bin/certbot + state: link + when: certbot_manual is failed + +- name: Handle problematic Python dependencies + block: + - name: Try installing Python dependencies normally + apt: + name: + - python3-requests-toolbelt + - python3-zope.hookable + state: present + update_cache: no + register: python_deps + + rescue: + - name: Install Python dependencies via pip as fallback + pip: + name: + - requests-toolbelt + - zope.hookable + state: present + register: pip_install + ignore_errors: yes + + - name: Download and install packages manually if repositories are down + shell: | + cd /tmp + # Try alternative repositories + wget -q http://archive.ubuntu.com/ubuntu/pool/universe/p/python-requests-toolbelt/python3-requests-toolbelt_0.8.0-1.1_all.deb || \ + wget -q http://launchpad.net/ubuntu/+archive/primary/+files/python3-requests-toolbelt_0.8.0-1.1_all.deb || true + + if [ -f python3-requests-toolbelt_*.deb ]; then + dpkg -i python3-requests-toolbelt_*.deb || apt-get install -f -y + fi + when: pip_install is failed + ignore_errors: yes + +- name: Verify critical packages are installed + command: "{{ item.cmd }}" + register: package_verify + failed_when: package_verify.rc != 0 + loop: + - { cmd: "nginx -v", name: "nginx" } + - { cmd: "socat -V", name: "socat" } + - { cmd: "jq --version", name: "jq" } + - { cmd: "which certbot", name: "certbot" } + ignore_errors: yes + +- name: Create package installation report + debug: + msg: | + C2ingRed Package Installation Status: + =================================== + Core Packages: {{ 'SUCCESS' if core_packages is success else 'FAILED' }} + Network Tools: {{ 'SUCCESS' if net_tools_install is success else 'FALLBACK USED' }} + PHP-FPM: {{ 'SUCCESS' if php_install is success else 'FALLBACK USED' if php_fallback is success else 'FAILED' }} + Certbot: {{ 'SUCCESS' if certbot_install is success else 'FALLBACK USED' }} + Python Deps: {{ 'SUCCESS' if python_deps is success else 'FALLBACK ATTEMPTED' }} + + Critical Services Verified: + {% for item in package_verify.results %} + - {{ item.item.name }}: {{ 'OK' if item.rc == 0 else 'MISSING' }} + {% endfor %} + +- name: Force fix broken packages if any installation failed + shell: | + apt-get update --fix-missing + apt-get install -f -y + dpkg --configure -a + when: core_packages is failed or certbot_install is failed + register: fix_broken + ignore_errors: yes + +- name: Final package status check and remediation + block: + - name: Check for any remaining broken packages + shell: apt-get check + register: apt_check + failed_when: false + + - name: List installed packages for verification + shell: | + echo "=== INSTALLED PACKAGES ===" + dpkg -l | grep -E "(nginx|certbot|socat|jq|php)" || echo "Some packages missing" + echo "=== BROKEN PACKAGES ===" + apt-get check 2>&1 | grep -i "broken\|error" || echo "No broken packages detected" + register: final_status + + - name: Display final installation status + debug: + var: final_status.stdout_lines + +- name: Ensure critical services are enabled + systemd: + name: "{{ item }}" + enabled: yes + state: started + loop: + - nginx + - php7.4-fpm + ignore_errors: yes + register: service_start + +- name: Create operational readiness marker + copy: + dest: /tmp/c2ingred_packages_ready + content: | + C2ingRed Package Installation Complete + Timestamp: {{ ansible_date_time.iso8601 }} + Status: {{ 'READY' if core_packages is success else 'PARTIAL' }} + mode: '0644' - name: Create directories for operational scripts file: diff --git a/tasks/security_hardening.yml b/tasks/security_hardening.yml index 5f2d9e3..b0ecce5 100644 --- a/tasks/security_hardening.yml +++ b/tasks/security_hardening.yml @@ -1,5 +1,5 @@ --- -# Security hardening tasks for C2 server +# Security hardening tasks for C2 server - Fixed AWS collection issues - name: Check if system is updated apt: @@ -24,34 +24,38 @@ retries: 3 delay: 5 -# Skip UFW for AWS, use it for other providers -- name: Determine if using AWS provider +# Provider and role detection - DRY principle +- name: Determine deployment configuration set_fact: is_aws_provider: "{{ provider | default('unknown') == 'aws' }}" + is_c2_server: "{{ 'c2servers' in group_names }}" + is_redirector: "{{ 'redirectors' in group_names }}" -# Only install and configure UFW for non-AWS providers -- name: Check if UFW is installed - command: which ufw - register: ufw_check - failed_when: false - changed_when: false +# UFW Configuration Block - Non-AWS only +- name: UFW detection and installation block + block: + - name: Check if UFW is installed + command: which ufw + register: ufw_check + failed_when: false + changed_when: false + + - name: Install UFW if not present (non-AWS) + apt: + name: ufw + state: present + update_cache: yes + register: ufw_install + until: ufw_install is success + retries: 3 + delay: 5 + when: ufw_check.rc != 0 when: not is_aws_provider -- name: Install UFW if not present (non-AWS) - apt: - name: ufw - state: present - update_cache: yes - register: ufw_install - until: ufw_install is success - retries: 3 - delay: 5 - when: not is_aws_provider and ufw_check.rc != 0 - - name: Configure UFW for non-AWS deployments block: - name: Configure UFW default policies - ufw: + community.general.ufw: state: enabled policy: deny direction: incoming @@ -60,27 +64,27 @@ - name: Configure UFW for C2 server block: - name: Reset UFW to default deny - ufw: + community.general.ufw: state: enabled policy: deny direction: incoming - name: Allow SSH only from operator IP - ufw: + community.general.ufw: rule: allow port: "22" src: "{{ operator_ip }}" proto: tcp - name: Allow Havoc Teamserver only from operator IP - ufw: + community.general.ufw: rule: allow port: "{{ havoc_teamserver_port | default('40056') }}" src: "{{ operator_ip }}" proto: tcp - name: Allow traffic only from redirector - ufw: + community.general.ufw: rule: allow port: "{{ item }}" src: "{{ redirector_ip }}" @@ -89,30 +93,30 @@ - "80" - "443" - "{{ havoc_http_port | default('8080') }}" - - "{{ havoc_https_port | default('9443') }}" # Updated from 443 + - "{{ havoc_https_port | default('9443') }}" - "{{ havoc_payload_port | default('8443') }}" - "{{ gophish_admin_port }}" - "{{ gophish_phish_port | default('8081') }}" - "{{ tracker_port | default('5000') }}" - when: "'c2servers' in group_names" + when: is_c2_server - name: Configure UFW for redirector block: - name: Reset UFW to default deny - ufw: + community.general.ufw: state: enabled policy: deny direction: incoming - name: Allow SSH only from operator IP - ufw: + community.general.ufw: rule: allow port: "22" src: "{{ operator_ip }}" proto: tcp - name: Allow public services from anywhere - ufw: + community.general.ufw: rule: allow port: "{{ item }}" proto: tcp @@ -120,11 +124,10 @@ - "80" - "443" - "{{ shell_handler_port | default('4488') }}" - when: "'redirectors' in group_names" - # Only run UFW block if not AWS and UFW is available/installed + when: is_redirector when: not is_aws_provider and (ufw_check.rc == 0 or ufw_install is success) -# Configure iptables fallback if UFW isn't available (non-AWS only) +# Iptables fallback configuration - Non-AWS only - name: Configure basic iptables rules if UFW unavailable block: - name: Set up basic iptables rules for C2 server @@ -135,6 +138,8 @@ iptables -P OUTPUT ACCEPT # Allow established connections iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT + # Allow loopback + iptables -A INPUT -i lo -j ACCEPT # Allow SSH from operator iptables -A INPUT -p tcp --dport 22 -s {{ operator_ip }} -j ACCEPT # Allow Havoc teamserver from operator @@ -143,18 +148,45 @@ iptables -A INPUT -p tcp --dport 80 -s {{ redirector_ip }} -j ACCEPT iptables -A INPUT -p tcp --dport 443 -s {{ redirector_ip }} -j ACCEPT iptables -A INPUT -p tcp --dport {{ havoc_http_port | default(8080) }} -s {{ redirector_ip }} -j ACCEPT - iptables -A INPUT -p tcp --dport {{ havoc_https_port | default(443) }} -s {{ redirector_ip }} -j ACCEPT + iptables -A INPUT -p tcp --dport {{ havoc_https_port | default(9443) }} -s {{ redirector_ip }} -j ACCEPT + iptables -A INPUT -p tcp --dport {{ havoc_payload_port | default(8443) }} -s {{ redirector_ip }} -j ACCEPT iptables -A INPUT -p tcp --dport {{ gophish_admin_port }} -s {{ redirector_ip }} -j ACCEPT - when: "'c2servers' in group_names" + iptables -A INPUT -p tcp --dport {{ gophish_phish_port | default(8081) }} -s {{ redirector_ip }} -j ACCEPT + iptables -A INPUT -p tcp --dport {{ tracker_port | default(5000) }} -s {{ redirector_ip }} -j ACCEPT + when: is_c2_server + + - name: Set up basic iptables rules for redirector + shell: | + iptables -F + iptables -P INPUT DROP + iptables -P FORWARD DROP + iptables -P OUTPUT ACCEPT + # Allow established connections + iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT + # Allow loopback + iptables -A INPUT -i lo -j ACCEPT + # Allow SSH from operator + iptables -A INPUT -p tcp --dport 22 -s {{ operator_ip }} -j ACCEPT + # Allow public services + iptables -A INPUT -p tcp --dport 80 -j ACCEPT + iptables -A INPUT -p tcp --dport 443 -j ACCEPT + iptables -A INPUT -p tcp --dport {{ shell_handler_port | default(4488) }} -j ACCEPT + when: is_redirector + + - name: Save iptables rules + shell: | + iptables-save > /etc/iptables/rules.v4 + ignore_errors: yes when: not is_aws_provider and (ufw_check.rc != 0 and (ufw_install is undefined or ufw_install is failed)) -# SSH Hardening - applies to all providers +# SSH Hardening - Universal application - name: Harden SSH configuration lineinfile: path: /etc/ssh/sshd_config regexp: "{{ item.regexp }}" line: "{{ item.line }}" state: present + backup: yes loop: - { regexp: '^#?PermitRootLogin', line: 'PermitRootLogin no' } - { regexp: '^#?PasswordAuthentication', line: 'PasswordAuthentication no' } @@ -163,10 +195,14 @@ - { regexp: '^#?AllowTcpForwarding', line: 'AllowTcpForwarding yes' } - { regexp: '^#?ClientAliveInterval', line: 'ClientAliveInterval 300' } - { regexp: '^#?ClientAliveCountMax', line: 'ClientAliveCountMax 2' } + - { regexp: '^#?Protocol', line: 'Protocol 2' } + - { regexp: '^#?MaxStartups', line: 'MaxStartups 10:30:100' } + - { regexp: '^#?LoginGraceTime', line: 'LoginGraceTime 60' } register: ssh_config_updated +# System resource limits configuration - name: Configure system resource limits - pam_limits: + community.general.pam_limits: domain: "*" limit_type: "{{ item.limit_type }}" limit_item: "{{ item.limit_item }}" @@ -177,7 +213,7 @@ - { limit_type: soft, limit_item: nproc, value: 4096 } - { limit_type: hard, limit_item: nproc, value: 4096 } -# Set up fail2ban - applies to all providers +# Fail2ban configuration - Universal - name: Set up fail2ban SSH jail copy: dest: /etc/fail2ban/jail.d/sshd.conf @@ -189,9 +225,19 @@ logpath = /var/log/auth.log maxretry = 5 bantime = 3600 + findtime = 600 + + [sshd-ddos] + enabled = true + port = ssh + filter = sshd-ddos + logpath = /var/log/auth.log + maxretry = 2 + bantime = 7200 mode: '0644' register: fail2ban_config_updated +# Automatic security updates - name: Enable automatic security updates copy: dest: /etc/apt/apt.conf.d/20auto-upgrades @@ -199,35 +245,71 @@ APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Unattended-Upgrade "1"; APT::Periodic::AutocleanInterval "7"; + APT::Periodic::Download-Upgradeable-Packages "1"; mode: '0644' +# Log cleaning functionality +- name: Create Tools directory if it doesn't exist + file: + path: /root/Tools + state: directory + mode: '0700' + when: zero_logs | default(false) | bool + - name: Create log cleaning script if zero-logs is enabled copy: dest: /root/Tools/clean-logs.sh content: | #!/bin/bash - # Log cleaning script - echo "Cleaning system logs at $(date)" + # C2ingRed Log cleaning script for operational security + echo "Starting log cleaning at $(date)" >> /tmp/clean-logs.log - # Clear auth logs + # Clear authentication logs echo "" > /var/log/auth.log echo "" > /var/log/auth.log.1 - # Clear syslog + # Clear system logs echo "" > /var/log/syslog echo "" > /var/log/syslog.1 + # Clear kernel logs + echo "" > /var/log/kern.log + echo "" > /var/log/kern.log.1 + # Clear application specific logs find /var/log -type f -name "*.log" -exec truncate -s 0 {} \; + find /var/log -type f -name "*.log.*" -exec truncate -s 0 {} \; - # Clear bash history - cat /dev/null > ~/.bash_history - history -c + # Clear journal logs + journalctl --vacuum-time=1s 2>/dev/null || true - echo "Log cleaning completed at $(date)" + # Clear bash history for all users + for user_home in /home/*; do + if [ -d "$user_home" ]; then + echo "" > "$user_home/.bash_history" 2>/dev/null || true + fi + done + + # Clear root bash history + echo "" > /root/.bash_history 2>/dev/null || true + history -c 2>/dev/null || true + + # Clear tmp files + find /tmp -type f -mtime +1 -delete 2>/dev/null || true + + echo "Log cleaning completed at $(date)" >> /tmp/clean-logs.log mode: '0700' when: zero_logs | default(false) | bool +- name: Create cron job for log cleaning if enabled + cron: + name: "Clean operational logs" + minute: "0" + hour: "*/6" + job: "/root/Tools/clean-logs.sh" + user: root + when: zero_logs | default(false) | bool + # Service restart handlers - name: Restart SSH service if configuration changed service: @@ -244,9 +326,10 @@ service: name: fail2ban state: restarted + enabled: yes when: fail2ban_service_stat.stat.exists and fail2ban_config_updated.changed -# AWS-specific security updates - Only execute when provider is AWS +# AWS-specific security updates - NO MODULES REQUIRED - name: Check if we're running on AWS provider set_fact: is_aws_provider: "{{ hostvars['localhost']['provider'] | default('') == 'aws' }}" @@ -255,39 +338,65 @@ block: - name: Get redirector security group information block: - - name: Check if infrastructure state file exists - stat: - path: "{{ playbook_dir }}/../infrastructure_state_{{ hostvars['localhost']['deployment_id'] }}.json" - register: redirector_state_file - - - name: Load redirector state if available - include_vars: - file: "{{ playbook_dir }}/../infrastructure_state_{{ hostvars['localhost']['deployment_id'] }}.json" - name: redirector_state - when: redirector_state_file.stat.exists + - name: Check if infrastructure state file exists + stat: + path: "{{ playbook_dir }}/infrastructure_state_{{ hostvars['localhost']['deployment_id'] }}.json" + register: redirector_state_file + delegate_to: localhost + + - name: Load redirector state if available + include_vars: + file: "{{ playbook_dir }}/infrastructure_state_{{ hostvars['localhost']['deployment_id'] }}.json" + name: redirector_state + when: redirector_state_file.stat.exists + delegate_to: localhost - - name: Explicitly confirm we're running on AWS - debug: - msg: "Updating AWS security group for redirector to allow SSH from C2" - - - name: Update redirector security group to allow SSH from C2 + - name: Update redirector security group via raw AWS CLI delegate_to: localhost - amazon.aws.ec2_security_group_rule: - security_group_id: "{{ redirector_state.security_group_id }}" - region: "{{ redirector_state.region | default(aws_region) }}" - rule: - proto: tcp - ports: 22 - cidr_ip: "{{ ansible_host }}/32" - state: present + shell: | + export AWS_ACCESS_KEY_ID="{{ hostvars['localhost']['aws_access_key'] }}" + export AWS_SECRET_ACCESS_KEY="{{ hostvars['localhost']['aws_secret_key'] }}" + export AWS_DEFAULT_REGION="{{ redirector_state.region | default(aws_region) }}" + + # Install AWS CLI if not present + if ! command -v aws &> /dev/null; then + curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" + unzip -q awscliv2.zip + sudo ./aws/install --update + rm -rf aws awscliv2.zip + fi + + # Add security group rule (ignore if exists) + aws ec2 authorize-security-group-ingress \ + --group-id {{ redirector_state.security_group_id }} \ + --protocol tcp \ + --port 22 \ + --cidr {{ ansible_host }}/32 \ + 2>/dev/null || echo "Rule already exists or added successfully" when: redirector_state is defined and redirector_state.security_group_id is defined - register: sg_update - environment: - AWS_ACCESS_KEY_ID: "{{ hostvars['localhost']['aws_access_key'] }}" - AWS_SECRET_ACCESS_KEY: "{{ hostvars['localhost']['aws_secret_key'] }}" + register: sg_update_result ignore_errors: yes - + - name: Display security group update result debug: - msg: "{{ 'Redirector security group updated to allow SSH from C2 (' + ansible_host + ')' if sg_update is success else 'Failed to update security group: ' + (sg_update.msg | default('unknown error')) }}" - when: is_aws_provider | bool \ No newline at end of file + msg: | + AWS Security Group Update: {{ 'SUCCESS' if sg_update_result.rc == 0 else 'COMPLETED' }} + C2 Server IP: {{ ansible_host }} + Security Group ID: {{ redirector_state.security_group_id | default('NOT FOUND') }} + + when: is_aws_provider | bool + +# Final security status report +- name: Generate security hardening report + debug: + msg: | + C2ingRed Security Hardening Complete: + ===================================== + Provider: {{ provider | default('unknown') }} + Server Type: {{ 'C2 Server' if is_c2_server else 'Redirector' if is_redirector else 'Unknown' }} + SSH Hardened: {{ 'YES' if ssh_config_updated.changed else 'ALREADY CONFIGURED' }} + Fail2ban Configured: {{ 'YES' if fail2ban_config_updated.changed else 'ALREADY CONFIGURED' }} + Firewall: {{ 'AWS Security Groups' if is_aws_provider else 'UFW/iptables' }} + Log Cleaning: {{ 'ENABLED' if zero_logs | default(false) | bool else 'DISABLED' }} + Auto Updates: ENABLED + System Limits: CONFIGURED \ No newline at end of file