From 7f2d236c49d2f6dfd97ef9704a00bf18a349ed57 Mon Sep 17 00:00:00 2001 From: cschantz Date: Tue, 6 Jan 2026 21:55:58 -0500 Subject: [PATCH] Add auto-blocking for distributed attacks When 5+ IPs perform same attack type (RCE, SQL_INJECTION, XSS, PATH_TRAVERSAL, BRUTEFORCE) within 2 minutes: - Block all individual attacking IPs immediately via IPset - If 25+ IPs from same /24 subnet, block entire subnet Uses batch_block_ips() for efficient IPset operations. All blocking is kernel-level via IPset (no CSF commands). --- modules/security/live-attack-monitor-v2.sh | 64 ++++++++++++++++------ 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/modules/security/live-attack-monitor-v2.sh b/modules/security/live-attack-monitor-v2.sh index 676acb0..ac691a2 100755 --- a/modules/security/live-attack-monitor-v2.sh +++ b/modules/security/live-attack-monitor-v2.sh @@ -3263,34 +3263,66 @@ detect_distributed_attacks() { # Check for same attack type from 5+ different IPs (use awk for performance) for attack_type in RCE SQL_INJECTION XSS PATH_TRAVERSAL BRUTEFORCE; do - # Single AWK pass to count attacks and unique IPs - local result=$(echo "$recent" | awk -v pattern="$attack_type" ' + # Single AWK pass to extract all attacking IPs + local attacking_ips=$(echo "$recent" | awk -v pattern="$attack_type" ' $0 ~ pattern { - count++ # Extract IP (first field matching IP pattern) for(i=1; i<=NF; i++) { if($i ~ /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/) { - ips[$i]=1 + print $i break } } } - END { - print count "|" length(ips) - } - ') - IFS='|' read -r attack_count unique_ips <<< "$result" + ' | sort -u) - if [ "${attack_count:-0}" -ge 5 ]; then + # Count unique IPs + local unique_ips=$(echo "$attacking_ips" | grep -c "^[0-9]" 2>/dev/null || echo "0") - if [ "$unique_ips" -ge 5 ]; then - # Distributed attack detected! - local time_str=$(date +"%H:%M:%S") - echo -e "${CRITICAL_COLOR}[${time_str}] DISTRIBUTED_ATTACK | ${attack_type} from ${unique_ips} IPs in last 2min | Possible botnet${NC}" >> "$TEMP_DIR/recent_events" + if [ "${unique_ips:-0}" -ge 5 ]; then + # Distributed attack detected! + local time_str=$(date +"%H:%M:%S") - # Mark in a file for Quick Actions to see - echo "${attack_type}|${unique_ips}|$(date +%s)" >> "$TEMP_DIR/distributed_attacks" + # BLOCK ALL INDIVIDUAL IPs IN THE ATTACK + local -a batch_ips=() + while IFS= read -r ip; do + [ -n "$ip" ] && batch_ips+=("$ip") + done <<< "$attacking_ips" + + if [ ${#batch_ips[@]} -gt 0 ]; then + batch_block_ips "${batch_ips[@]}" + echo -e "${CRITICAL_COLOR}[${time_str}] DISTRIBUTED_ATTACK | ${attack_type} from ${unique_ips} IPs | BLOCKED ALL${NC}" >> "$TEMP_DIR/recent_events" fi + + # Check for subnet-level coordination (25+ IPs from same /24) + declare -A subnet_counts + while IFS= read -r ip; do + [ -z "$ip" ] && continue + local subnet="${ip%.*}" # Get /24 subnet (bash built-in) + ((subnet_counts[$subnet]++)) + done <<< "$attacking_ips" + + # Block entire subnets with 25+ attacking IPs + for subnet in "${!subnet_counts[@]}"; do + local subnet_ip_count=${subnet_counts[$subnet]} + if [ "$subnet_ip_count" -ge 25 ]; then + local subnet_cidr="${subnet}.0/24" + + # Check if not already blocked + if ! grep -q "^${subnet_cidr}\$" "$TEMP_DIR/blocked_subnets" 2>/dev/null; then + echo "$subnet_cidr" >> "$TEMP_DIR/blocked_subnets" + + # Add to IPset (kernel-level blocking) + if [ "$IPSET_AVAILABLE" -eq 1 ]; then + ipset add "$IPSET_NAME" "$subnet_cidr" -exist 2>/dev/null + echo -e "${CRITICAL_COLOR}[${time_str}] SUBNET_BLOCK | $subnet_cidr | ${attack_type} from ${subnet_ip_count} IPs | BLOCKED${NC}" >> "$TEMP_DIR/recent_events" + fi + fi + fi + done + + # Mark in a file for Quick Actions to see + echo "${attack_type}|${unique_ips}|$(date +%s)" >> "$TEMP_DIR/distributed_attacks" fi done fi