--- # Security hardening tasks for C2 server - name: Check if system is updated apt: update_cache: yes register: apt_update_result until: apt_update_result is success retries: 5 delay: 5 - name: Install security packages (non-UFW) apt: name: - fail2ban - unattended-upgrades - debsums - aide - rkhunter - logrotate state: present register: package_install until: package_install is success retries: 3 delay: 5 # Skip UFW for AWS, use it for other providers - name: Determine if using AWS provider set_fact: is_aws_provider: "{{ provider | default('unknown') == 'aws' }}" # 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 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: state: enabled policy: deny direction: incoming ignore_errors: yes - name: Configure UFW for C2 server block: - name: Reset UFW to default deny ufw: state: enabled policy: deny direction: incoming - name: Allow SSH only from operator IP ufw: rule: allow port: "22" src: "{{ operator_ip }}" proto: tcp - name: Allow Havoc Teamserver only from operator IP ufw: rule: allow port: "{{ havoc_teamserver_port | default('40056') }}" src: "{{ operator_ip }}" proto: tcp - name: Allow traffic only from redirector ufw: rule: allow port: "{{ item }}" src: "{{ redirector_ip }}" proto: tcp loop: - "80" - "443" - "{{ havoc_http_port | default('8080') }}" - "{{ havoc_https_port | default('443') }}" - "{{ gophish_admin_port }}" when: "'c2servers' in group_names" - name: Configure UFW for redirector block: - name: Reset UFW to default deny ufw: state: enabled policy: deny direction: incoming - name: Allow SSH only from operator IP ufw: rule: allow port: "22" src: "{{ operator_ip }}" proto: tcp - name: Allow public services from anywhere ufw: rule: allow port: "{{ item }}" proto: tcp loop: - "80" - "443" - "{{ shell_handler_port | default('4444') }}" when: "'redirectors' in group_names" # Only run UFW block if not AWS and UFW is available/installed 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) - name: Configure basic iptables rules if UFW unavailable block: - name: Set up basic iptables rules for C2 server 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 SSH from operator iptables -A INPUT -p tcp --dport 22 -s {{ operator_ip }} -j ACCEPT # Allow Havoc teamserver from operator iptables -A INPUT -p tcp --dport {{ havoc_teamserver_port | default(40056) }} -s {{ operator_ip }} -j ACCEPT # Allow traffic from redirector 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 {{ gophish_admin_port }} -s {{ redirector_ip }} -j ACCEPT when: "'c2servers' in group_names" 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 - name: Harden SSH configuration lineinfile: path: /etc/ssh/sshd_config regexp: "{{ item.regexp }}" line: "{{ item.line }}" state: present loop: - { regexp: '^#?PermitRootLogin', line: 'PermitRootLogin no' } - { regexp: '^#?PasswordAuthentication', line: 'PasswordAuthentication no' } - { regexp: '^#?X11Forwarding', line: 'X11Forwarding no' } - { regexp: '^#?MaxAuthTries', line: 'MaxAuthTries 3' } - { regexp: '^#?AllowTcpForwarding', line: 'AllowTcpForwarding yes' } - { regexp: '^#?ClientAliveInterval', line: 'ClientAliveInterval 300' } - { regexp: '^#?ClientAliveCountMax', line: 'ClientAliveCountMax 2' } register: ssh_config_updated - name: Configure system resource limits pam_limits: domain: "*" limit_type: "{{ item.limit_type }}" limit_item: "{{ item.limit_item }}" value: "{{ item.value }}" loop: - { limit_type: soft, limit_item: nofile, value: 65535 } - { limit_type: hard, limit_item: nofile, value: 65535 } - { limit_type: soft, limit_item: nproc, value: 4096 } - { limit_type: hard, limit_item: nproc, value: 4096 } # Set up fail2ban - applies to all providers - name: Set up fail2ban SSH jail copy: dest: /etc/fail2ban/jail.d/sshd.conf content: | [sshd] enabled = true port = ssh filter = sshd logpath = /var/log/auth.log maxretry = 5 bantime = 3600 mode: '0644' register: fail2ban_config_updated - name: Enable automatic security updates copy: dest: /etc/apt/apt.conf.d/20auto-upgrades content: | APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Unattended-Upgrade "1"; APT::Periodic::AutocleanInterval "7"; mode: '0644' - 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)" # Clear auth logs echo "" > /var/log/auth.log echo "" > /var/log/auth.log.1 # Clear syslog echo "" > /var/log/syslog echo "" > /var/log/syslog.1 # Clear application specific logs find /var/log -type f -name "*.log" -exec truncate -s 0 {} \; # Clear bash history cat /dev/null > ~/.bash_history history -c echo "Log cleaning completed at $(date)" mode: '0700' when: zero_logs | default(false) | bool # Service restart handlers - name: Restart SSH service if configuration changed service: name: ssh state: restarted when: ssh_config_updated.changed - name: Check if fail2ban service exists stat: path: "/etc/init.d/fail2ban" register: fail2ban_service_stat - name: Restart fail2ban service if installed and configuration changed service: name: fail2ban state: restarted when: fail2ban_service_stat.stat.exists and fail2ban_config_updated.changed # AWS-specific security updates - Only execute when provider is AWS - name: Check if we're running on AWS provider set_fact: is_aws_provider: "{{ hostvars['localhost']['provider'] | default('') == 'aws' }}" - name: AWS-specific security updates 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: 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 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 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'] }}" 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