diff --git a/modules/performance/php-optimizer.sh b/modules/performance/php-optimizer.sh index cc875bf..32ec4ed 100755 --- a/modules/performance/php-optimizer.sh +++ b/modules/performance/php-optimizer.sh @@ -57,7 +57,7 @@ show_main_menu() { cecho " ${YELLOW}b${NC}) Backup Current Configurations" cecho " ${YELLOW}r${NC}) Restore from Backup" echo "" - cecho " ${RED}q${NC}) Quit" + cecho " ${RED}0${NC}) Exit" echo "" cecho "${CYAN}─────────────────────────────────────────────────────────────────────${NC}" } @@ -1733,7 +1733,7 @@ main() { r) restore_configurations ;; - q|Q) + 0) cecho "${GREEN}Exiting PHP Optimizer...${NC}" exit 0 ;; diff --git a/modules/security/live-attack-monitor-v2.sh b/modules/security/live-attack-monitor-v2.sh new file mode 100755 index 0000000..d508ac6 --- /dev/null +++ b/modules/security/live-attack-monitor-v2.sh @@ -0,0 +1,2922 @@ +#!/bin/bash + +################################################################################ +# Live Network Security Monitor - ENHANCED with Intelligence +################################################################################ +# Purpose: Real-time monitoring with bot intelligence and threat scoring +# Version: 2.0 - Intelligence Mode +# Features: +# - Bot classification using learned signatures +# - IP reputation DB integration +# - Real-time threat scoring (0-100) +# - Attack vector detection +# - Quick action blocking system +# - Ban tracking and history +################################################################################ + +# Get script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +source "$SCRIPT_DIR/lib/common-functions.sh" +source "$SCRIPT_DIR/lib/system-detect.sh" +source "$SCRIPT_DIR/lib/ip-reputation.sh" +source "$SCRIPT_DIR/lib/bot-signatures.sh" +source "$SCRIPT_DIR/lib/threat-intelligence.sh" +# TEMP: Still needed for non-HTTP monitors (SSH, cPHulk, firewall) - will remove in Phase 4 +source "$SCRIPT_DIR/lib/attack-patterns.sh" 2>/dev/null || true + +# Enhanced attack detection (ET Open signatures) +source "$SCRIPT_DIR/lib/attack-signatures.sh" 2>/dev/null || true +source "$SCRIPT_DIR/lib/http-attack-analyzer.sh" 2>/dev/null || true +source "$SCRIPT_DIR/lib/rate-anomaly-detector.sh" 2>/dev/null || true + +# Require root +if [ "$EUID" -ne 0 ]; then + print_error "This script must be run as root" + exit 1 +fi + +# Color definitions for threat levels +CRITICAL_COLOR='\033[1;41;97m' # White on Red background +HIGH_COLOR='\033[1;31m' # Bold Red +MEDIUM_COLOR='\033[1;33m' # Bold Yellow +LOW_COLOR='\033[0;36m' # Cyan +SAFE_COLOR='\033[0;32m' # Green +INFO_COLOR='\033[0;37m' # White +BOLD='\033[1m' # Bold text +NC='\033[0m' + +# Configuration +REFRESH_INTERVAL=2 # Seconds between dashboard refreshes +MAX_DISPLAY_LINES=20 +THREAT_THRESHOLD_CRITICAL=80 +THREAT_THRESHOLD_HIGH=60 +THREAT_THRESHOLD_MEDIUM=40 + +# Display mode (compact by default for small terminals) +COMPACT_MODE=1 +TERMINAL_HEIGHT=$(tput lines 2>/dev/null || echo "24") + +# Temporary files for tracking +TEMP_DIR="/tmp/live-monitor-$$" +SNAPSHOT_DIR="/var/lib/server-toolkit/live-monitor" +mkdir -p "$TEMP_DIR" "$SNAPSHOT_DIR" 2>/dev/null +touch "$TEMP_DIR/recent_events" +touch "$TEMP_DIR/ip_data" +echo "0" > "$TEMP_DIR/event_counter" +echo "0" > "$TEMP_DIR/total_blocks" + +# IPset configuration +IPSET_NAME="live_monitor_$$" +IPSET_AVAILABLE=0 + +# Initialize IPset for fast blocking (if available) +if command -v ipset &>/dev/null; then + # Create temporary IPset with 1-hour default timeout + if ipset create "$IPSET_NAME" hash:ip timeout 3600 maxelem 65536 2>/dev/null; then + IPSET_AVAILABLE=1 + + # Add iptables rule to block IPs in the set + iptables -I INPUT -m set --match-set "$IPSET_NAME" src -j DROP 2>/dev/null + + echo "✓ IPset initialized: $IPSET_NAME (fast blocking enabled)" >> "$TEMP_DIR/debug.log" + else + echo "✗ IPset creation failed - falling back to CSF" >> "$TEMP_DIR/debug.log" + fi +else + echo "✗ IPset not available - using CSF for blocking" >> "$TEMP_DIR/debug.log" +fi + +# Initialize blocked IPs cache immediately on startup +{ + # Get CSF temporary blocks - extract just the IP address + if command -v csf &>/dev/null; then + csf -t 2>/dev/null | awk '{print $1}' | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' + fi + + # Get CSF permanent denies + if [ -f /etc/csf/csf.deny ]; then + awk '{print $1}' /etc/csf/csf.deny 2>/dev/null | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' + fi + + # Get iptables DROP rules + if command -v iptables &>/dev/null; then + iptables -L INPUT -n -v 2>/dev/null | grep DROP | awk '{print $8}' | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' + fi +} | sort -u > "$TEMP_DIR/blocked_ips_cache" 2>/dev/null + +# Log cache initialization for debugging +if [ -f "$TEMP_DIR/blocked_ips_cache" ]; then + CACHED_COUNT=$(wc -l < "$TEMP_DIR/blocked_ips_cache" 2>/dev/null || echo "0") + echo "Initialized blocked IPs cache with $CACHED_COUNT IPs" >> "$TEMP_DIR/debug.log" +fi + +# Cleanup function +cleanup() { + echo "" + echo "Stopping monitoring processes..." + + # Kill all child processes + pkill -P $$ 2>/dev/null + + # Wait a moment for background jobs + sleep 1 + + # Clean up IPset and iptables rule if we created them + if [ "$IPSET_AVAILABLE" -eq 1 ]; then + echo "Removing IPset firewall rules..." + iptables -D INPUT -m set --match-set "$IPSET_NAME" src -j DROP 2>/dev/null + ipset destroy "$IPSET_NAME" 2>/dev/null + echo "✓ IPset cleaned up" + fi + + # Clean up temp directory + rm -rf "$TEMP_DIR" 2>/dev/null + + # Restore cursor + command -v tput &>/dev/null && tput cnorm + + echo "✓ Cleanup complete (snapshot saved)" + exit 0 +} + +trap cleanup EXIT INT TERM + +# Save current monitoring state to temp files (for persistence across sessions) +save_snapshot() { + # Save IP_DATA associative array to file + local snapshot_file="$TEMP_DIR/snapshot.dat" + + # Write IP data + { + for ip in "${!IP_DATA[@]}"; do + echo "IP_DATA[$ip]=${IP_DATA[$ip]}" + done + + # Write attack type counters + for attack in "${!ATTACK_TYPE_COUNTER[@]}"; do + echo "ATTACK_TYPE_COUNTER[$attack]=${ATTACK_TYPE_COUNTER[$attack]}" + done + + # Write totals + echo "TOTAL_THREATS=$TOTAL_THREATS" + echo "TOTAL_BLOCKS=$TOTAL_BLOCKS" + echo "START_TIME=$START_TIME" + } > "$snapshot_file" 2>/dev/null +} + +# Statistics counters +declare -A IP_DATA # Stores: IP -> score|hits|bot_type|attacks|ban_count|rep_score +declare -A IP_TIMESTAMPS # Stores: IP -> comma-separated attack timestamps (last 100) +declare -A IP_ATTACK_VECTORS # Stores: IP -> unique attack vectors (SSH,WEB,EMAIL,etc) +declare -A SUBNET_ATTACKS # Stores: subnet -> attack count +declare -A ATTACK_TYPE_COUNTER +TOTAL_THREATS=0 +TOTAL_BLOCKS=0 +START_TIME=$(date +%s) +MAX_TRACKED_IPS=500 # Prevent memory overflow +VELOCITY_WINDOW=3600 # 1 hour in seconds +DECAY_CHECK_INTERVAL=1800 # Check for decay every 30 minutes +LAST_DECAY_CHECK=$START_TIME + +# Hide cursor for cleaner display +command -v tput &>/dev/null && tput civis + +################################################################################ +# Intelligence Functions +################################################################################ + +# Get or create IP intelligence data +# Returns: score|hits|bot_type|attacks|ban_count|rep_score +get_ip_intelligence() { + local ip="$1" + + # Check if we have cached data + if [ -n "${IP_DATA[$ip]}" ]; then + echo "${IP_DATA[$ip]}" + return 0 + fi + + # Query IP reputation database + local rep_data=$(lookup_ip "$ip" 2>/dev/null) + + if [ -n "$rep_data" ]; then + # Parse: IP|HIT_COUNT|REP_SCORE|COUNTRY|ATTACK_FLAGS|... + IFS='|' read -r _ db_hits rep_score country attack_flags _ _ _ notes ban_count _ <<< "$rep_data" + + # Initialize with learned data + local score=${rep_score:-0} + local hits=${db_hits:-0} + local bot_type="unknown" + local attacks=$(decode_attack_flags "$attack_flags" 2>/dev/null | tr ',' ' ' || echo "") + ban_count=${ban_count:-0} + + # Cache it + IP_DATA[$ip]="$score|$hits|$bot_type|$attacks|$ban_count|$rep_score" + echo "${IP_DATA[$ip]}" + else + # New IP - initialize + IP_DATA[$ip]="0|0|unknown||0|0" + echo "${IP_DATA[$ip]}" + fi +} + +# Update IP intelligence +update_ip_intelligence() { + local ip="$1" + local url="$2" + local user_agent="$3" + local method="${4:-GET}" + + # Get current data + local current=$(get_ip_intelligence "$ip") + IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$current" + + # Increment hits + hits=$((hits + 1)) + + # Enrich with threat intelligence on first encounter (hits == 1) + if [ "${hits:-0}" -eq 1 ]; then + # Check if whitelisted first + if is_whitelisted_service "$ip" 2>/dev/null; then + score=0 + bot_type="legit" + else + # Get threat intelligence (in background to avoid slowing down) + ( + local threat_intel=$(get_threat_intelligence "$ip" 2>/dev/null) + IFS='|' read -r abuse_conf abuse_rpts country isp geo timing whitelisted <<< "$threat_intel" + + # Store enrichment data for later use + local enrich_file="$TEMP_DIR/threat_enrich_${ip//\./_}" + echo "$threat_intel" > "$enrich_file" + + # Boost score based on AbuseIPDB confidence + if [ "${abuse_conf:-0}" -ge 75 ]; then + # High confidence malicious - add 30 points + local current_data="${IP_DATA[$ip]}" + IFS='|' read -r old_score old_hits old_bot old_attacks old_ban old_rep <<< "$current_data" + local new_score=$((old_score + 30)) + [ "${new_score:-0}" -gt 100 ] && new_score=100 + IP_DATA[$ip]="$new_score|$old_hits|$old_bot|$old_attacks|$old_ban|$old_rep" + elif [ "${abuse_conf:-0}" -ge 50 ]; then + # Medium confidence - add 15 points + local current_data="${IP_DATA[$ip]}" + IFS='|' read -r old_score old_hits old_bot old_attacks old_ban old_rep <<< "$current_data" + local new_score=$((old_score + 15)) + [ "${new_score:-0}" -gt 100 ] && new_score=100 + IP_DATA[$ip]="$new_score|$old_hits|$old_bot|$old_attacks|$old_ban|$old_rep" + fi + + # High-risk country adds 5 points + if is_high_risk_country "${geo:-XX}" 2>/dev/null; then + local current_data="${IP_DATA[$ip]}" + IFS='|' read -r old_score old_hits old_bot old_attacks old_ban old_rep <<< "$current_data" + local new_score=$((old_score + 5)) + [ "${new_score:-0}" -gt 100 ] && new_score=100 + IP_DATA[$ip]="$new_score|$old_hits|$old_bot|$old_attacks|$old_ban|$old_rep" + fi + ) & + fi + fi + + # Classify bot if unknown + if [ "$bot_type" = "unknown" ] && [ -n "$user_agent" ]; then + bot_type=$(classify_bot_type "$user_agent") + fi + + # Record attack pattern for learning + if [ -n "$url" ]; then + record_attack_pattern "$ip" "${attacks:-unknown}" "$url" "${user_agent:-unknown}" 2>/dev/null & + fi + + # NOTE: Legacy detect_all_attacks() removed - ET Open handles all attack detection + # This eliminates 50% performance waste from double detection + + # Request volume scoring + if [ "${hits:-0}" -gt 100 ]; then + score=$((score + 5)) + elif [ "${hits:-0}" -gt 50 ]; then + score=$((score + 3)) + elif [ "${hits:-0}" -gt 20 ]; then + score=$((score + 1)) + fi + + # Adjust score based on bot type + case "$bot_type" in + legit|ai|monitor) + # Legitimate bots - reduce score + score=$((score - 5)) + [ "${score:-0}" -lt 0 ] && score=0 + ;; + suspicious) + # Suspicious bots - increase score + score=$((score + 10)) + ;; + esac + + # Cap at 100 + [ "${score:-0}" -gt 100 ] && score=100 + + # Check if we're tracking too many IPs (memory protection) + if [ ${#IP_DATA[@]} -ge $MAX_TRACKED_IPS ]; then + # Remove lowest scoring IPs + local to_remove=() + for check_ip in "${!IP_DATA[@]}"; do + # Use bash parameter expansion instead of cut + local check_score="${IP_DATA[$check_ip]%%|*}" + [ "$check_score" -lt 10 ] && to_remove+=("$check_ip") + done + + # Remove up to 100 low-score IPs + local removed=0 + for remove_ip in "${to_remove[@]}"; do + unset IP_DATA[$remove_ip] + ((removed++)) + [ "${removed:-0}" -ge 100 ] && break + done + fi + + # Update cached data + IP_DATA[$ip]="$score|$hits|$bot_type|$attacks|$ban_count|$rep_score" + + # Update IP reputation DB in background (if score > 0) + if [ "${score:-0}" -gt 0 ]; then + (update_ip_reputation "$ip" 1 "$score" 0 "Live monitor: $new_attacks" >/dev/null 2>&1) & + fi +} + +################################################################################ +# Advanced Intelligence Functions +################################################################################ + +# Record attack timestamp for velocity tracking +record_attack_timestamp() { + local ip="$1" + local now=$(date +%s) + + # Get existing timestamps + local timestamps="${IP_TIMESTAMPS[$ip]}" + + # Add new timestamp + if [ -z "$timestamps" ]; then + timestamps="$now" + else + timestamps="$timestamps,$now" + fi + + # Keep only last 100 timestamps (prevent memory bloat) + # Use bash array instead of pipeline for efficiency + IFS=',' read -ra TS_ARRAY <<< "$timestamps" + if [ "${#TS_ARRAY[@]}" -gt 100 ]; then + # Keep last 100 elements + timestamps=$(IFS=','; echo "${TS_ARRAY[*]: -100}") + fi + + IP_TIMESTAMPS[$ip]="$timestamps" +} + +# Calculate attack velocity (attacks per hour) +# Returns: velocity|recent_count|bonus_points|reason +calculate_attack_velocity() { + local ip="$1" + local timestamps="${IP_TIMESTAMPS[$ip]}" + + [ -z "$timestamps" ] && echo "0|0|0|" && return + + local now=$(date +%s) + local window_start=$((now - VELOCITY_WINDOW)) + + # Count attacks in last hour + local recent_count=0 + local oldest_in_window="" + + while IFS=',' read -ra TIMES; do + for ts in "${TIMES[@]}"; do + if [ "$ts" -ge "$window_start" ]; then + ((recent_count++)) + [ -z "$oldest_in_window" ] && oldest_in_window="$ts" + fi + done + done <<< "$timestamps" + + # Calculate velocity and bonus + local bonus=0 + local reason="" + + if [ "$recent_count" -ge 20 ]; then + # 20+ attacks in 1 hour = extreme velocity + bonus=30 + reason="EXTREME_VELOCITY:${recent_count}/hr" + elif [ "$recent_count" -ge 10 ]; then + # 10-19 attacks in 1 hour = high velocity + bonus=20 + reason="HIGH_VELOCITY:${recent_count}/hr" + elif [ "$recent_count" -ge 5 ]; then + # 5-9 attacks in 1 hour = moderate velocity + bonus=10 + reason="MOD_VELOCITY:${recent_count}/hr" + fi + + # If attacks are very rapid (10 in 5 minutes), extra bonus + local five_min_ago=$((now - 300)) + local rapid_count=0 + while IFS=',' read -ra TIMES; do + for ts in "${TIMES[@]}"; do + [ "$ts" -ge "$five_min_ago" ] && ((rapid_count++)) + done + done <<< "$timestamps" + + if [ "$rapid_count" -ge 10 ]; then + bonus=$((bonus + 15)) + reason="${reason}+RAPID:${rapid_count}/5min" + fi + + echo "${recent_count}|${bonus}|${reason}" +} + +# Record attack vector for diversity tracking +record_attack_vector() { + local ip="$1" + local vector="$2" # SSH, WEB, EMAIL, FTP, DATABASE, FIREWALL + + local vectors="${IP_ATTACK_VECTORS[$ip]}" + + # Add if not already present + if [[ ! "$vectors" =~ $vector ]]; then + if [ -z "$vectors" ]; then + vectors="$vector" + else + vectors="$vectors,$vector" + fi + IP_ATTACK_VECTORS[$ip]="$vectors" + fi +} + +# Calculate diversity bonus +# Returns: vector_count|bonus_points|reason +calculate_diversity_bonus() { + local ip="$1" + local vectors="${IP_ATTACK_VECTORS[$ip]}" + + [ -z "$vectors" ] && echo "0|0|" && return + + local count=$(echo "$vectors" | tr ',' '\n' | wc -l) + local bonus=0 + local reason="" + + if [ "$count" -ge 4 ]; then + bonus=35 + reason="MULTI_VECTOR:${count}_types" + elif [ "$count" -eq 3 ]; then + bonus=25 + reason="COORDINATED:${count}_types" + elif [ "$count" -eq 2 ]; then + bonus=10 + reason="DUAL_VECTOR:${count}_types" + fi + + echo "${count}|${bonus}|${reason}" +} + +# Detect timing patterns (bot signatures) +# Returns: pattern_type|confidence|bonus_points|reason +detect_timing_pattern() { + local ip="$1" + local timestamps="${IP_TIMESTAMPS[$ip]}" + + [ -z "$timestamps" ] && echo "NONE|0|0|" && return + + # Need at least 5 attacks to detect pattern + local count=$(echo "$timestamps" | tr ',' '\n' | wc -l) + [ "$count" -lt 5 ] && echo "INSUFFICIENT|0|0|" && return + + # Calculate gaps between attacks + local prev_ts="" + local gaps=() + + while IFS=',' read -ra TIMES; do + for ts in "${TIMES[@]}"; do + if [ -n "$prev_ts" ]; then + local gap=$((ts - prev_ts)) + gaps+=("$gap") + fi + prev_ts="$ts" + done + done <<< "$timestamps" + + # Check for consistent intervals (bot signature) + local total_gap=0 + local gap_count=${#gaps[@]} + + for gap in "${gaps[@]}"; do + total_gap=$((total_gap + gap)) + done + + if [ "$gap_count" -gt 0 ]; then + local avg_gap=$((total_gap / gap_count)) + + # Check variance - if all gaps are similar, it's a bot + local variance=0 + for gap in "${gaps[@]}"; do + local diff=$((gap - avg_gap)) + [ "$diff" -lt 0 ] && diff=$((diff * -1)) + variance=$((variance + diff)) + done + + local avg_variance=$((variance / gap_count)) + + # If average variance is low (gaps are consistent), it's automated + if [ "$avg_variance" -lt 3 ]; then + # Very consistent timing = bot + echo "BOT_PATTERN|HIGH|20|AUTOMATED:${avg_gap}s_intervals" + return + elif [ "$avg_variance" -lt 10 ]; then + # Somewhat consistent = likely bot + echo "LIKELY_BOT|MEDIUM|10|PATTERN:${avg_gap}s_avg" + return + fi + fi + + echo "HUMAN_LIKE|LOW|0|RANDOM_TIMING" +} + +# Check if attack was successful +# Returns: success_detected|bonus_points|reason +detect_attack_success() { + local ip="$1" + local url="$2" + local status="${3:-0}" + local method="${4:-GET}" + + local bonus=0 + local reason="" + local success=0 + + # Check for successful login attempts + if [[ "$url" =~ wp-login\.php ]] && [ "$status" -eq 302 ]; then + # 302 redirect on wp-login = successful login + success=1 + bonus=50 + reason="WORDPRESS_BREACH" + elif [[ "$url" =~ wp-admin ]] && [ "$status" -eq 200 ] && [[ "$method" == "POST" ]]; then + # POST to wp-admin with 200 = potential successful action + success=1 + bonus=40 + reason="ADMIN_ACCESS" + elif [ "$status" -eq 200 ] && [[ "$url" =~ \.(php|asp|aspx|jsp)$ ]] && [[ "$url" =~ (shell|cmd|exec|eval) ]]; then + # Successful request to shell-like file = breach + success=1 + bonus=60 + reason="SHELL_ACCESS" + fi + + echo "${success}|${bonus}|${reason}" +} + +# Track subnet attacks +track_subnet_attack() { + local ip="$1" + + # Extract /24 subnet + local subnet=$(echo "$ip" | cut -d. -f1-3) + + # Increment subnet counter + local count=${SUBNET_ATTACKS[$subnet]:-0} + count=$((count + 1)) + SUBNET_ATTACKS[$subnet]=$count + + echo "$count" +} + +# Calculate subnet attack bonus +# Returns: subnet_count|bonus_points|reason +calculate_subnet_bonus() { + local ip="$1" + local subnet=$(echo "$ip" | cut -d. -f1-3) + + local count=${SUBNET_ATTACKS[$subnet]:-0} + local bonus=0 + local reason="" + + if [ "$count" -ge 10 ]; then + bonus=40 + reason="SUBNET_SWARM:${count}_IPs_in_${subnet}.0/24" + elif [ "$count" -ge 5 ]; then + bonus=25 + reason="SUBNET_ATTACK:${count}_IPs_in_${subnet}.0/24" + elif [ "$count" -ge 3 ]; then + bonus=15 + reason="RELATED_IPS:${count}_in_${subnet}.0/24" + fi + + echo "${count}|${bonus}|${reason}" +} + +# Assess target criticality +# Returns: criticality_level|bonus_points|reason +assess_target_criticality() { + local url="$1" + local method="${2:-GET}" + + local bonus=0 + local reason="" + local level="LOW" + + # Critical admin paths + if [[ "$url" =~ (wp-admin|admin|administrator|manager|phpmyadmin|cpanel|whm) ]]; then + bonus=15 + reason="ADMIN_TARGET" + level="HIGH" + fi + + # Authentication endpoints + if [[ "$url" =~ (wp-login|login|signin|auth|session) ]]; then + bonus=12 + reason="AUTH_TARGET" + level="HIGH" + fi + + # Config/sensitive files + if [[ "$url" =~ (config|\.env|\.git|\.sql|backup|database|credentials) ]]; then + bonus=18 + reason="SENSITIVE_FILE" + level="CRITICAL" + fi + + # Shell/exploit attempts + if [[ "$url" =~ (shell|cmd|exec|eval|system|phpinfo) ]]; then + bonus=20 + reason="EXPLOIT_ATTEMPT" + level="CRITICAL" + fi + + # Upload endpoints (RCE risk) + if [[ "$url" =~ upload ]] && [[ "$method" == "POST" ]]; then + bonus=15 + reason="UPLOAD_TARGET" + level="HIGH" + fi + + echo "${level}|${bonus}|${reason}" +} + +# Apply reputation decay +apply_reputation_decay() { + local now=$(date +%s) + local time_since_last=$((now - LAST_DECAY_CHECK)) + + # Only check every 30 minutes + [ "$time_since_last" -lt "$DECAY_CHECK_INTERVAL" ] && return + + LAST_DECAY_CHECK=$now + + # Decay scores for IPs with no recent activity + for ip in "${!IP_DATA[@]}"; do + local timestamps="${IP_TIMESTAMPS[$ip]}" + + [ -z "$timestamps" ] && continue + + # Get most recent attack time + local last_attack=$(echo "$timestamps" | tr ',' '\n' | tail -1) + local time_since_attack=$((now - last_attack)) + + # If no activity for 6 hours, start decay + if [ "$time_since_attack" -gt 21600 ]; then + IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "${IP_DATA[$ip]}" + + # Reduce score by 20% (but not below 0) + local decay_amount=$((score / 5)) + [ "$decay_amount" -lt 5 ] && decay_amount=5 + + score=$((score - decay_amount)) + [ "$score" -lt 0 ] && score=0 + + # Update data + IP_DATA[$ip]="$score|$hits|$bot_type|$attacks|$ban_count|$rep_score" + fi + done +} + +# Context-aware scoring (geo, ISP, time-of-day) +# Returns: context_bonus|reason +calculate_context_bonus() { + local ip="$1" + local now=$(date +%s) + + local bonus=0 + local reasons="" + + # Time-of-day analysis (attacks at odd hours = suspicious) + local hour=$(date +%H) + if [ "$hour" -ge 2 ] && [ "$hour" -le 5 ]; then + # Attacks between 2am-5am (server timezone) = suspicious + bonus=$((bonus + 8)) + reasons="NIGHT_ATTACK:${hour}h" + fi + + # Check geolocation if available (from threat intelligence) + if [ -f "$TEMP_DIR/threat_enrich_${ip//\./_}" ]; then + local threat_data=$(cat "$TEMP_DIR/threat_enrich_${ip//\./_}") + IFS='|' read -r abuse_conf abuse_rpts country isp geo timing whitelisted <<< "$threat_data" + + # High-risk country already detected + if is_high_risk_country "${geo:-XX}" 2>/dev/null; then + bonus=$((bonus + 5)) + [ -n "$reasons" ] && reasons="${reasons}+" || reasons="" + reasons="${reasons}HIGH_RISK_GEO:${geo}" + fi + + # Residential ISP (suspicious for server attacks) + if echo "$isp" | grep -qiE "(comcast|verizon|att|residential|cable|dsl|fiber|broadband)"; then + bonus=$((bonus + 10)) + [ -n "$reasons" ] && reasons="${reasons}+" || reasons="" + reasons="${reasons}RESIDENTIAL_ISP" + fi + fi + + echo "${bonus}|${reasons}" +} + +# Atomically increment block counter (prevents race conditions) +increment_block_counter() { + local increment="${1:-1}" + ( + flock -x 200 + local current=$(cat "$TEMP_DIR/total_blocks" 2>/dev/null || echo "0") + echo $((current + increment)) > "$TEMP_DIR/total_blocks" + ) 200>"$TEMP_DIR/counter.lock" +} + +# Batch block multiple IPs at once (optimized for DDoS scenarios) +batch_block_ips() { + local -a ip_list=("$@") + local blocked=0 + local failed=0 + + if [ ${#ip_list[@]} -eq 0 ]; then + return 0 + fi + + echo "Batch blocking ${#ip_list[@]} IPs..." + + # Use IPset for instant batch blocking if available + if [ "$IPSET_AVAILABLE" -eq 1 ]; then + for ip in "${ip_list[@]}"; do + # Validate IP format + if ! is_valid_ip "$ip"; then + ((failed++)) + continue + fi + + # Add to IPset with 1-hour timeout (instant, no verification needed) + if ipset add "$IPSET_NAME" "$ip" timeout 3600 2>/dev/null; then + ((blocked++)) + echo "$ip" >> "$TEMP_DIR/blocked_ips_cache" + else + # Already in set or error + ((failed++)) + fi + done + + # Single cache update after batch + sort -u "$TEMP_DIR/blocked_ips_cache" -o "$TEMP_DIR/blocked_ips_cache" 2>/dev/null + + echo "✓ IPset batch: $blocked blocked, $failed skipped" + else + # Fallback to CSF (slower, but still batch where possible) + for ip in "${ip_list[@]}"; do + if ! is_valid_ip "$ip"; then + ((failed++)) + continue + fi + + if csf -td "$ip" 3600 "Batch auto-block" >/dev/null 2>&1; then + ((blocked++)) + else + ((failed++)) + fi + done + + echo "✓ CSF batch: $blocked blocked, $failed failed" + fi + + # Update total counter atomically + increment_block_counter "$blocked" + + return 0 +} + +# Block IP temporarily with CSF +block_ip_temporary() { + local ip="$1" + local hours="${2:-1}" + local reason="${3:-Auto-block by live monitor}" + local seconds=$((hours * 3600)) + + # Validate IP format before blocking + if ! is_valid_ip "$ip"; then + echo "✗ Error: Invalid IP format: $ip" + return 1 + fi + + # Use IPset for instant blocking if available + if [ "$IPSET_AVAILABLE" -eq 1 ]; then + echo "Blocking $ip for ${hours}h: $reason" + if ipset add "$IPSET_NAME" "$ip" timeout "$seconds" 2>/dev/null; then + echo "✓ $ip blocked via IPset (auto-expires in ${hours}h)" + echo "$ip" >> "$TEMP_DIR/blocked_ips_cache" + + # Update counter atomically + increment_block_counter 1 + + return 0 + else + echo "✗ Warning: IPset add failed (IP may already be blocked)" + return 1 + fi + fi + + # Fallback to CSF if IPset not available + if command -v csf &>/dev/null; then + echo "Blocking $ip for ${hours}h: $reason" + if csf -td "$ip" "$seconds" "$reason" >/dev/null 2>&1; then + echo "✓ $ip blocked via CSF" + echo "$ip" >> "$TEMP_DIR/blocked_ips_cache" + + # Update counter atomically + increment_block_counter 1 + + return 0 + else + echo "✗ Warning: CSF block failed for $ip" + return 1 + fi + fi + + echo "✗ Error: CSF not available" + return 1 +} + +# Block IP permanently with CSF +block_ip_permanent() { + local ip="$1" + local reason="${2:-Permanent block by live monitor}" + + # Validate IP format before blocking + if ! is_valid_ip "$ip"; then + echo "✗ Error: Invalid IP format: $ip" + return 1 + fi + + # Permanent blocks always use CSF (IPset is temp-only for this script) + # But we can add to IPset with max timeout as well for immediate effect + if [ "$IPSET_AVAILABLE" -eq 1 ]; then + # Add to IPset with 24-hour timeout for immediate blocking + ipset add "$IPSET_NAME" "$ip" timeout 86400 2>/dev/null + fi + + if command -v csf &>/dev/null; then + echo "Permanently blocking $ip: $reason" + if csf -d "$ip" "$reason" >/dev/null 2>&1; then + echo "✓ $ip permanently blocked via CSF" + echo "$ip" >> "$TEMP_DIR/blocked_ips_cache" + + # Update counter atomically + increment_block_counter 1 + + return 0 + else + echo "✗ Warning: CSF permanent block failed for $ip" + return 1 + fi + fi + + echo "✗ Error: CSF not available" + return 1 +} + +# Check if IP is currently blocked in CSF/iptables (optimized with caching) +is_ip_blocked() { + local ip="$1" + + # Use cached blocked IPs list (refreshed every 10 seconds by background process) + if [ -f "$TEMP_DIR/blocked_ips_cache" ]; then + if grep -q "^$ip$" "$TEMP_DIR/blocked_ips_cache" 2>/dev/null; then + return 0 + fi + fi + + return 1 +} + +# Real-time verification (no cache) for immediate confirmation after blocking +verify_ip_blocked() { + local ip="$1" + + # Check CSF temporary blocks + if command -v csf &>/dev/null; then + if csf -t 2>/dev/null | grep -q "$ip"; then + return 0 + fi + + # Check CSF permanent deny list + if [ -f /etc/csf/csf.deny ]; then + if grep -q "^$ip" /etc/csf/csf.deny 2>/dev/null; then + return 0 + fi + fi + fi + + # Check iptables directly + if command -v iptables &>/dev/null; then + if iptables -L INPUT -n 2>/dev/null | grep -q "$ip"; then + return 0 + fi + fi + + return 1 +} + +# Get threat level from score +get_threat_level() { + local score="${1:-0}" + + if [ "$score" -ge "$THREAT_THRESHOLD_CRITICAL" ]; then + echo "CRITICAL" + elif [ "$score" -ge "$THREAT_THRESHOLD_HIGH" ]; then + echo "HIGH" + elif [ "$score" -ge "$THREAT_THRESHOLD_MEDIUM" ]; then + echo "MEDIUM" + else + echo "LOW" + fi +} + +# Get color for threat level +get_threat_color() { + local level="$1" + + case "$level" in + CRITICAL) echo "$CRITICAL_COLOR" ;; + HIGH) echo "$HIGH_COLOR" ;; + MEDIUM) echo "$MEDIUM_COLOR" ;; + LOW) echo "$LOW_COLOR" ;; + SAFE) echo "$SAFE_COLOR" ;; + *) echo "$INFO_COLOR" ;; + esac +} + +# Get bot color +get_bot_color() { + local bot_type="$1" + + case "$bot_type" in + legit) echo "$SAFE_COLOR" ;; + ai) echo '\033[0;34m' ;; # Blue + monitor) echo "$MEDIUM_COLOR" ;; + suspicious) echo "$HIGH_COLOR" ;; + *) echo "$INFO_COLOR" ;; + esac +} + +################################################################################ +# Dashboard Display Functions +################################################################################ + +draw_header() { + clear + local uptime=$(($(date +%s) - START_TIME)) + local uptime_str=$(printf "%02d:%02d:%02d" $((uptime/3600)) $((uptime%3600/60)) $((uptime%60))) + + # Read event counter from file (updated by subshell) + local event_count=$(cat "$TEMP_DIR/event_counter" 2>/dev/null || echo "0") + + echo -e "${CRITICAL_COLOR}╔════════════════════════════════════════════════════════════════════════════╗${NC}" + echo -e "${CRITICAL_COLOR}║ 🚨 LIVE SECURITY MONITOR - INTELLIGENCE MODE 🧠 ║${NC}" + echo -e "${CRITICAL_COLOR}╚════════════════════════════════════════════════════════════════════════════╝${NC}" + echo -e "${INFO_COLOR}Runtime: ${uptime_str} | Events: ${event_count} | Threats: ${TOTAL_THREATS} | Blocks: ${TOTAL_BLOCKS} | Monitoring...${NC}" + echo "" +} + +draw_intelligence_panel() { + echo -e "${HIGH_COLOR}┌─ THREAT INTELLIGENCE ──────────────────────────────────────────────────────┐${NC}" + + # Debug: Show cache status + if [ -f "$TEMP_DIR/blocked_ips_cache" ]; then + CACHED_IPS=$(wc -l < "$TEMP_DIR/blocked_ips_cache" 2>/dev/null || echo 0) + echo -e "${INFO_COLOR} Cache: $CACHED_IPS blocked IPs${NC}" >> "$TEMP_DIR/debug.log" + else + echo -e "${INFO_COLOR} Cache: NOT FOUND${NC}" >> "$TEMP_DIR/debug.log" + fi + + # Get top IPs by threat score (exclude already blocked IPs) + # Load blocked IPs cache into associative array for O(1) lookups + declare -A blocked_ips_lookup + if [ -f "$TEMP_DIR/blocked_ips_cache" ]; then + while IFS= read -r blocked_ip; do + [ -n "$blocked_ip" ] && blocked_ips_lookup[$blocked_ip]=1 + done < "$TEMP_DIR/blocked_ips_cache" + fi + + local ip_list="" + local blocked_count=0 + local displayed_count=0 + for ip in "${!IP_DATA[@]}"; do + # Skip IPs that are already blocked (O(1) lookup in hash) + if [ -n "${blocked_ips_lookup[$ip]}" ]; then + ((blocked_count++)) + echo " Filtering out blocked IP: $ip" >> "$TEMP_DIR/debug.log" + continue + fi + + ((displayed_count++)) + IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "${IP_DATA[$ip]}" + ip_list+="$score|$ip|$hits|$bot_type|$attacks|$ban_count|$rep_score"$'\n' + done + + echo " Blocked/filtered: $blocked_count, Displaying: $displayed_count" >> "$TEMP_DIR/debug.log" + + if [ -n "$ip_list" ]; then + # Show fewer IPs in compact mode + local max_ips=10 + [ "$COMPACT_MODE" -eq 1 ] && max_ips=5 + + echo "$ip_list" | sort -t'|' -k1 -rn | head -$max_ips | while IFS='|' read -r score ip hits bot_type attacks ban_count rep_score; do + # Set defaults for empty values + score="${score:-0}" + hits="${hits:-0}" + ban_count="${ban_count:-0}" + rep_score="${rep_score:-0}" + + local level=$(get_threat_level "$score") + local color=$(get_threat_color "$level") + local bot_color=$(get_bot_color "$bot_type") + + # Build status line + local status_line=$(printf "%-15s" "$ip") + status_line+=$(printf " Score:%-3s" "$score") + status_line+=$(printf " Hits:%-4s" "$hits") + + # Bot type indicator + case "$bot_type" in + legit) status_line+=" ✅BOT" ;; + ai) status_line+=" 🤖AI" ;; + monitor) status_line+=" 📊MON" ;; + suspicious) status_line+=" ⚠️ SUS" ;; + *) status_line+="" ;; + esac + + # Threat level + status_line+=$(printf " [%-8s]" "$level") + + # Attacks (use bash parameter expansion instead of cut) + if [ -n "$attacks" ]; then + # Show first attack type + local first_attack="${attacks%%,*}" + local icon=$(get_attack_icon "$first_attack") + status_line+=" $icon$first_attack" + fi + + # Ban count + if [ "$ban_count" -gt 0 ]; then + status_line+=" 🚫x$ban_count" + fi + + # Known threat indicator + if [ "$rep_score" -gt 0 ]; then + status_line+=" [KNOWN]" + fi + + echo -e "${color}${status_line}${NC}" + done + else + # Show appropriate message + if [ ${#IP_DATA[@]} -gt 0 ]; then + echo -e "${SAFE_COLOR} ✓ All detected threats have been blocked${NC}" + else + echo -e "${LOW_COLOR} No threats detected yet...${NC}" + fi + fi + + echo -e "${HIGH_COLOR}└────────────────────────────────────────────────────────────────────────────┘${NC}" + echo "" +} + +draw_attack_breakdown() { + # Skip this section entirely in compact mode + [ "$COMPACT_MODE" -eq 1 ] && return + + echo -e "${MEDIUM_COLOR}┌─ ATTACK VECTORS ───────────────────────────────────────────────────────────┐${NC}" + + if [ ${#ATTACK_TYPE_COUNTER[@]} -eq 0 ]; then + echo -e "${LOW_COLOR} No attacks detected yet...${NC}" + else + for attack_type in "${!ATTACK_TYPE_COUNTER[@]}"; do + local count="${ATTACK_TYPE_COUNTER[$attack_type]}" + local icon=$(get_attack_icon "$attack_type") + local color=$(get_attack_color "$attack_type") + printf "${color} ${icon} %-20s %5d${NC}\n" "$attack_type" "$count" + done | sort -t' ' -k3 -rn | head -5 + fi + + echo -e "${MEDIUM_COLOR}└────────────────────────────────────────────────────────────────────────────┘${NC}" + echo "" +} + +draw_live_feed() { + echo -e "${HIGH_COLOR}┌─ LIVE THREAT FEED ─────────────────────────────────────────────────────────┐${NC}" + + # Adaptive line count based on mode + local feed_lines=$MAX_DISPLAY_LINES + [ "$COMPACT_MODE" -eq 1 ] && feed_lines=8 + + if [ -f "$TEMP_DIR/recent_events" ] && [ -s "$TEMP_DIR/recent_events" ]; then + tail -n "$feed_lines" "$TEMP_DIR/recent_events" + else + echo -e "${LOW_COLOR} Waiting for events...${NC}" + fi + + echo -e "${HIGH_COLOR}└────────────────────────────────────────────────────────────────────────────┘${NC}" + echo "" +} + +draw_quick_actions() { + echo -e "${MEDIUM_COLOR}┌─ QUICK ACTIONS & RECOMMENDATIONS ─────────────────────────────────────────┐${NC}" + + # Get blockable IPs (score >= 60, not already blocked) + local blockable_count=0 + local blockable_ips="" + local has_ddos=0 + local has_ssh_bruteforce=0 + local high_conn_count=0 + + # Load blocked IPs cache once for efficient lookups + declare -A blocked_ips_check + if [ -f "$TEMP_DIR/blocked_ips_cache" ]; then + while IFS= read -r blocked_ip; do + [ -n "$blocked_ip" ] && blocked_ips_check[$blocked_ip]=1 + done < "$TEMP_DIR/blocked_ips_cache" + fi + + for ip in "${!IP_DATA[@]}"; do + IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "${IP_DATA[$ip]}" + + # Check attack patterns + [[ "$attacks" =~ DDOS ]] && has_ddos=1 + [[ "$attacks" =~ BRUTEFORCE ]] && has_ssh_bruteforce=1 + + # Skip if score too low for blocking + [ "$score" -lt 60 ] && continue + + # Skip if already blocked + [ -n "${blocked_ips_check[$ip]}" ] && continue + + # Count as blockable + blockable_count=$((blockable_count + 1)) + blockable_ips+="$ip " + done + + # Check for high connection counts + if [ -f "$TEMP_DIR/recent_events" ]; then + high_conn_count=$(grep -c "HIGH_CONN_COUNT" "$TEMP_DIR/recent_events" 2>/dev/null) + else + high_conn_count=0 + fi + + # Ensure it's a valid number (strip whitespace and validate) + high_conn_count=$(echo "$high_conn_count" | tr -d '[:space:]') + [[ ! "$high_conn_count" =~ ^[0-9]+$ ]] && high_conn_count=0 + + # IP Blocking Recommendations + if [ "$blockable_count" -gt 0 ]; then + echo -e "${HIGH_COLOR} ⚠️ $blockable_count high-threat IPs ready to block${NC}" + echo -e "${MEDIUM_COLOR} → Press 'b' to open blocking menu${NC}" + else + echo -e "${SAFE_COLOR} ✓ No IPs requiring immediate blocks${NC}" + fi + + # Intelligent Firewall Recommendations + local recommendations=0 + + if [ "$has_ddos" -eq 1 ] || [ "$high_conn_count" -gt 0 ]; then + # Check current security settings + local synflood_status=$(grep "^SYNFLOOD\s*=" /etc/csf/csf.conf 2>/dev/null | cut -d'"' -f2) + local ct_limit=$(grep "^CT_LIMIT\s*=" /etc/csf/csf.conf 2>/dev/null | grep -oE '[0-9]+' | head -1) + + local needs_config=0 + + # Check if SYNFLOOD needs enabling + if [ "$synflood_status" != "1" ]; then + needs_config=1 + fi + + # Check if CT_LIMIT needs optimization (not set or set to 0) + if [ -z "$ct_limit" ] || [ "$ct_limit" -eq 0 ]; then + needs_config=1 + fi + + # Only show recommendation if something needs fixing + if [ "${needs_config:-0}" -eq 1 ]; then + echo -e "${HIGH_COLOR} ⚠️ DDoS/SYN Flood Detected - Firewall Protection Recommended${NC}" + echo -e "${MEDIUM_COLOR} → Press 'c' for Security Hardening menu${NC}" + recommendations=1 + fi + fi + + if [ "$has_ssh_bruteforce" -eq 1 ]; then + local ssh_attacks=0 + if [ -f "$TEMP_DIR/recent_events" ]; then + ssh_attacks=$(grep -c "SSH_BRUTEFORCE" "$TEMP_DIR/recent_events" 2>/dev/null) + fi + ssh_attacks=$(echo "$ssh_attacks" | tr -d '[:space:]') + [[ ! "$ssh_attacks" =~ ^[0-9]+$ ]] && ssh_attacks=0 + if [ "$ssh_attacks" -gt 5 ]; then + # Check if SSH hardening is already applied + local current_lf=$(grep "^LF_SSHD\s*=" /etc/csf/csf.conf 2>/dev/null | grep -oE '[0-9]+' | head -1) + [ -z "$current_lf" ] && current_lf="5" + + # Only show recommendation if not already hardened + if [ "$current_lf" -gt 3 ]; then + echo -e "${HIGH_COLOR} ⚠️ SSH Bruteforce ($ssh_attacks attempts) - Strengthen SSH Security${NC}" + echo -e "${MEDIUM_COLOR} → Press 'c' for Security Hardening menu${NC}" + recommendations=1 + fi + fi + fi + + if [ "${recommendations:-0}" -eq 0 ]; then + echo "" + fi + + # Show different keys based on mode + if [ "$COMPACT_MODE" -eq 1 ]; then + echo -e "${INFO_COLOR} Keys: 'b' Block | 'c' Security | 'v' Verbose | 'r' Refresh | 'q' Quit${NC}" + else + echo -e "${INFO_COLOR} Keys: 'b' Block | 'c' Security | 'v' Compact | 's' Stats | 'q' Quit${NC}" + fi + + echo -e "${MEDIUM_COLOR}└────────────────────────────────────────────────────────────────────────────┘${NC}" +} + +################################################################################ +# Quick Action Menu +################################################################################ + +show_blocking_menu() { + # Pause monitoring + local monitoring_paused=1 + + clear + print_banner "Quick IP Blocking" + echo "" + echo "Select IPs to block (1-hour temporary ban):" + echo "" + + # Build array of blockable IPs + local -a blockable_list=() + for ip in "${!IP_DATA[@]}"; do + IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "${IP_DATA[$ip]}" + + # Set defaults for empty values + score="${score:-0}" + hits="${hits:-0}" + attacks="${attacks:-none}" + + # Skip if score too low or already blocked + [ "$score" -lt 60 ] && continue + is_ip_blocked "$ip" 2>/dev/null && continue + + blockable_list+=("$ip|$score|$hits|$attacks") + done + + if [ ${#blockable_list[@]} -eq 0 ]; then + echo "No IPs meet blocking criteria (score >= 60)" + echo "" + read -p "Press Enter to continue..." + return + fi + + # Check if any IPs to block + if [ ${#blockable_list[@]} -eq 0 ]; then + echo "" + echo -e "${SAFE_COLOR}No IPs meet blocking criteria (score >= 60 and not already blocked)${NC}" + echo "" + read -p "Press Enter to continue..." + return + fi + + # Sort by score + IFS=$'\n' blockable_list=($(sort -t'|' -k2 -rn <<<"${blockable_list[*]}")) + unset IFS + + # Display IPs + local idx=1 + for entry in "${blockable_list[@]}"; do + IFS='|' read -r ip score hits attacks <<< "$entry" + local level=$(get_threat_level "$score") + local color=$(get_threat_color "$level") + + printf "${color} %2d) %-15s Score:%-3s Hits:%-5s Attacks: %s${NC}\n" \ + "$idx" "$ip" "$score" "$hits" "${attacks:-none}" + + ((idx++)) + done + + echo "" + echo -e "${BOLD}Options:${NC}" + echo " 1-${#blockable_list[@]}) Block specific IP" + echo " a) Block ALL high-threat IPs (score >= 80)" + echo -e " ${RED}0)${NC} Back" + echo "" + read -p "Select option: " choice + + if [ "$choice" = "0" ]; then + return + elif [ "$choice" = "a" ]; then + # Block all IPs with score >= 80 + local blocked=0 + local failed=0 + for entry in "${blockable_list[@]}"; do + IFS='|' read -r ip score hits attacks <<< "$entry" + [ "$score" -lt 80 ] && continue + + echo "" + if block_ip_temporary "$ip" 1 "Auto-block: High threat (score $score)"; then + ((blocked++)) + else + ((failed++)) + fi + done + + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "✓ Successfully blocked: $blocked IPs" + [ "${failed:-0}" -gt 0 ] && echo "✗ Failed to block: $failed IPs" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + read -p "Press Enter to continue..." + elif [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le ${#blockable_list[@]} ]; then + # Block specific IP + local entry="${blockable_list[$((choice-1))]}" + IFS='|' read -r ip score hits attacks <<< "$entry" + + echo "" + block_ip_temporary "$ip" 1 "Manual block from live monitor (score $score)" + + echo "" + read -p "Press Enter to continue..." + else + echo "Invalid option" + read -p "Press Enter to continue..." + fi +} + +show_security_hardening_menu() { + clear + print_banner "Security Hardening & Firewall Optimization" + echo "" + + # Check if CSF is available + if ! command -v csf &>/dev/null; then + echo -e "${HIGH_COLOR}⚠️ CSF/LFD firewall not detected${NC}" + echo " Security hardening options require CSF to be installed" + echo "" + read -p "Press Enter to return to monitor..." + return + fi + + # Check current settings + local synflood_status=$(grep "^SYNFLOOD\s*=" /etc/csf/csf.conf 2>/dev/null | cut -d'"' -f2) + local current_lf=$(grep "^LF_SSHD\s*=" /etc/csf/csf.conf 2>/dev/null | grep -oE '[0-9]+' | head -1) + [ -z "$current_lf" ] && current_lf="5" + + echo "Current Security Status:" + echo "" + + # SYNFLOOD status + if [ "$synflood_status" = "1" ]; then + echo -e " ${SAFE_COLOR}✓${NC} SYNFLOOD Protection: ${BOLD}Enabled${NC}" + else + echo -e " ${HIGH_COLOR}✗${NC} SYNFLOOD Protection: ${BOLD}Disabled${NC}" + fi + + # SSH hardening status + if [ "$current_lf" -le 3 ]; then + echo -e " ${SAFE_COLOR}✓${NC} SSH Security: ${BOLD}Hardened${NC} (LF_SSHD=$current_lf)" + else + echo -e " ${HIGH_COLOR}✗${NC} SSH Security: ${BOLD}Default${NC} (LF_SSHD=$current_lf, recommend ≤3)" + fi + + # CT_LIMIT status (basic check) + local ct_limit=$(grep "^CT_LIMIT\s*=" /etc/csf/csf.conf 2>/dev/null | grep -oE '[0-9]+' | head -1) + if [ -n "$ct_limit" ] && [ "$ct_limit" -gt 0 ]; then + echo -e " ${SAFE_COLOR}✓${NC} Connection Tracking: ${BOLD}Configured${NC} (CT_LIMIT=$ct_limit)" + else + echo -e " ${HIGH_COLOR}✗${NC} Connection Tracking: ${BOLD}Not Optimized${NC}" + fi + + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + echo "Available Hardening Options:" + echo "" + echo -e " ${BOLD}1${NC} - Enable SYNFLOOD Protection (DDoS defense)" + echo -e " ${BOLD}2${NC} - Harden SSH Security (Lower LF_SSHD to 3)" + echo -e " ${BOLD}3${NC} - Optimize CT_LIMIT (Auto-analyze & apply)" + echo -e " ${BOLD}4${NC} - Configure Port Knocking (Coming soon)" + echo "" + echo -e " ${BOLD}a${NC} - Apply All Needed Fixes" + echo "" + echo -e " ${RED}0)${NC} Back" + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + read -p "Select option: " choice + echo "" + + case "$choice" in + 1) + if [ "$synflood_status" = "1" ]; then + echo "✓ SYNFLOOD is already enabled" + echo "" + read -p "Press Enter to continue..." + else + apply_synflood_fix + fi + ;; + 2) + if [ "$current_lf" -le 3 ]; then + echo "✓ SSH is already hardened (LF_SSHD=$current_lf)" + echo "" + read -p "Press Enter to continue..." + else + apply_ssh_hardening + fi + ;; + 3) + clear + "$SCRIPT_DIR/modules/security/optimize-ct-limit.sh" --auto + echo "" + read -p "Press Enter to return to monitor..." + ;; + 4) + echo "Port Knocking configuration coming soon..." + echo "" + echo "For now, you can manually configure port knocking in CSF:" + echo "1. Edit /etc/csf/csf.conf" + echo "2. Set: PORTKNOCKING = \"1\"" + echo "3. Define sequence: PORTKNOCKING_ALERT = \"1\"" + echo "4. Restart: csf -r" + echo "" + read -p "Press Enter to continue..." + ;; + a|A) + echo "Applying all needed fixes..." + echo "" + local applied=0 + + # Apply SYNFLOOD if needed + if [ "$synflood_status" != "1" ]; then + apply_synflood_fix + ((applied++)) + fi + + # Apply SSH hardening if needed + if [ "$current_lf" -gt 3 ]; then + apply_ssh_hardening + ((applied++)) + fi + + # Always offer CT_LIMIT + echo "" + echo "Running CT_LIMIT optimizer..." + "$SCRIPT_DIR/modules/security/optimize-ct-limit.sh" --auto + ((applied++)) + + echo "" + if [ "${applied:-0}" -gt 0 ]; then + echo "✓ Applied $applied security fix(es)" + else + echo "✓ All security settings already optimized" + fi + echo "" + read -p "Press Enter to return to monitor..." + ;; + 0) + return + ;; + *) + echo "Invalid option" + read -p "Press Enter to continue..." + ;; + esac +} + +apply_synflood_fix() { + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "Enabling SYNFLOOD Protection..." + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + + # Check current status + local current_status=$(grep "^SYNFLOOD\s*=" /etc/csf/csf.conf 2>/dev/null | cut -d'"' -f2) + + if [ "$current_status" = "1" ]; then + echo "✓ SYNFLOOD protection is already enabled" + else + echo "Current setting: SYNFLOOD = \"$current_status\"" + echo "Enabling SYNFLOOD protection..." + + # Backup config + cp /etc/csf/csf.conf /etc/csf/csf.conf.bak.$(date +%Y%m%d_%H%M%S) + + # Enable SYNFLOOD + sed -i 's/^SYNFLOOD\s*=.*/SYNFLOOD = "1"/' /etc/csf/csf.conf + + # Set reasonable defaults if not already set + if ! grep -q "^SYNFLOOD_RATE\s*=" /etc/csf/csf.conf; then + echo 'SYNFLOOD_RATE = "100/s"' >> /etc/csf/csf.conf + fi + if ! grep -q "^SYNFLOOD_BURST\s*=" /etc/csf/csf.conf; then + echo 'SYNFLOOD_BURST = "150"' >> /etc/csf/csf.conf + fi + + # Restart CSF + echo "" + echo "Restarting CSF to apply changes..." + csf -r >/dev/null 2>&1 + + echo "" + echo "✓ SYNFLOOD protection enabled successfully" + echo " Rate limit: 100 connections per second" + echo " Burst: 150 connections" + fi + echo "" + read -p "Press Enter to continue..." +} + +apply_ssh_hardening() { + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "Hardening SSH Security..." + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + + # Check current LF_SSHD setting + local current_lf=$(grep "^LF_SSHD\s*=" /etc/csf/csf.conf 2>/dev/null | grep -oE '[0-9]+' | head -1) + + if [ -z "$current_lf" ]; then + current_lf="5" # CSF default + fi + + echo "Current SSH failure threshold: LF_SSHD = \"$current_lf\"" + + if [ "$current_lf" -le 3 ]; then + echo "✓ SSH security is already hardened (threshold ≤ 3)" + else + echo "Lowering threshold to 3 failed attempts..." + + # Backup config + cp /etc/csf/csf.conf /etc/csf/csf.conf.bak.$(date +%Y%m%d_%H%M%S) + + # Update LF_SSHD + sed -i 's/^LF_SSHD\s*=.*/LF_SSHD = "3"/' /etc/csf/csf.conf + + # Also lower LF_SSHD_PERM if it exists (permanent blocks after X temp blocks) + if grep -q "^LF_SSHD_PERM\s*=" /etc/csf/csf.conf; then + sed -i 's/^LF_SSHD_PERM\s*=.*/LF_SSHD_PERM = "3"/' /etc/csf/csf.conf + fi + + # Restart LFD to apply changes + echo "" + echo "Restarting LFD to apply changes..." + csf -r >/dev/null 2>&1 + + echo "" + echo "✓ SSH security hardened successfully" + echo " New threshold: 3 failed attempts before temp block" + echo " Block duration: As configured in LF_TRIGGER (default: 1 hour)" + fi + echo "" + read -p "Press Enter to continue..." +} + +################################################################################ +# Log Monitoring +################################################################################ + +monitor_apache_logs() { + # Try multiple log locations based on control panel + local log_files=() + + # Use system-detected log directory (no fallback) + local LOG_DIR="${SYS_LOG_DIR}" + + if [ "$SYS_CONTROL_PANEL" = "interworx" ]; then + # InterWorx: Monitor per-domain access logs + # Find recent domain logs (modified in last hour for performance, InterWorx uses 'transfer.log') + while IFS= read -r domain_log; do + [ -f "$domain_log" ] && log_files+=("$domain_log") + done < <(find /home/*/var/*/logs -type f -name "transfer.log" -mmin -60 2>/dev/null | head -10) + + elif [ -n "$LOG_DIR" ]; then + # cPanel/Plesk: Use detected log directory + + # Main access log + if [ -f "${LOG_DIR}/access_log" ]; then + log_files+=("${LOG_DIR}/access_log") + elif [ -f "/var/log/httpd/access_log" ]; then + log_files+=("/var/log/httpd/access_log") + elif [ -f "/var/log/apache2/access.log" ]; then + log_files+=("/var/log/apache2/access.log") + fi + + # Domain logs + if [ -d "${LOG_DIR}" ]; then + # Find recent domain logs (modified in last hour) + while IFS= read -r domain_log; do + [ -f "$domain_log" ] && log_files+=("$domain_log") + done < <(find "${LOG_DIR}" -type f \( -name "*.com" -o -name "*.net" -o -name "*.org" \) -mmin -60 2>/dev/null | head -10) + fi + fi + + if [ ${#log_files[@]} -eq 0 ]; then + echo "ERROR: No accessible Apache log files found" >> "$TEMP_DIR/recent_events" + echo "Control panel: ${SYS_CONTROL_PANEL}, Log dir: ${LOG_DIR}" >> "$TEMP_DIR/recent_events" + return 1 + fi + + # Monitor all log files + local event_count=0 + tail -n 0 -F "${log_files[@]}" 2>/dev/null | while read -r line; do + # Increment event counter (update file every 10 events for performance) + ((event_count++)) + if [ $((event_count % 10)) -eq 0 ]; then + echo "$event_count" > "$TEMP_DIR/event_counter" + fi + + # Parse Apache combined log format (supports IPv4 and IPv6) + # Note: bytes field can be - or number, so use [0-9-]+ + if [[ "$line" =~ ^([0-9a-f.:]+)\ -\ -\ \[([^\]]+)\]\ \"([A-Z]+)\ ([^\"]+)\ [^\"]+\"\ ([0-9]+)\ ([0-9-]+)\ \"[^\"]*\"\ \"([^\"]+)\" ]]; then + local ip="${BASH_REMATCH[1]}" + local timestamp="${BASH_REMATCH[2]}" + local method="${BASH_REMATCH[3]}" + local url="${BASH_REMATCH[4]}" + local status="${BASH_REMATCH[5]}" + local bytes="${BASH_REMATCH[6]}" + local user_agent="${BASH_REMATCH[7]}" + + # Skip local/private IPs and server's own IP + if [[ "$ip" =~ ^127\. ]] || \ + [[ "$ip" =~ ^10\. ]] || \ + [[ "$ip" =~ ^192\.168\. ]] || \ + [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[01])\. ]] || \ + [[ "$ip" =~ ^169\.254\. ]] || \ + [[ "$ip" == "localhost" ]] || \ + [[ "$ip" == "::1" ]]; then + continue + fi + + # Update intelligence + update_ip_intelligence "$ip" "$url" "$user_agent" "$method" + + # Enhanced attack detection using ET Open signatures + local et_attack_score=0 + local et_attack_types="" + local et_signatures="" + local et_rate_score=0 + + if type analyze_http_log_line &>/dev/null; then + local attack_result=$(analyze_http_log_line "$line" 2>/dev/null) + if [ -n "$attack_result" ]; then + et_attack_score="${attack_result%%||*}" + if [ "$et_attack_score" -gt 0 ]; then + local temp="${attack_result#*||}" + et_attack_types="${temp%%||*}" + temp="${temp#*||}" + et_signatures="${temp%%||*}" + + # Update IP intelligence with ET attack info + update_ip_intelligence "$ip" "$url|ET:$et_attack_types|$et_signatures" "attack" "HTTP" + + # Use ET detection score directly (legacy detection removed) + local current_intel=$(get_ip_intelligence "$ip") + IFS='|' read -r curr_score curr_hits curr_bot curr_attacks curr_ban curr_rep <<< "$current_intel" + + # Keep higher score (ET attack vs AbuseIPDB reputation) + local new_score=$((et_attack_score > curr_score ? et_attack_score : curr_score)) + [ "$new_score" -gt 100 ] && new_score=100 + + # Update IP data with ET-based score + IP_DATA[$ip]="$new_score|$curr_hits|$curr_bot|$curr_attacks|$curr_ban|$curr_rep" + + # Check rate anomaly + if type record_request &>/dev/null && type detect_rate_anomaly &>/dev/null; then + record_request "$ip" + local rate_result=$(detect_rate_anomaly "$ip" 2>/dev/null) + et_rate_score="${rate_result%%||*}" + + # Combine scores + local combined_score=$((et_attack_score + et_rate_score)) + [ "$combined_score" -gt 100 ] && combined_score=100 + + # Auto-block critical attacks + if [ "$combined_score" -ge 90 ]; then + echo "[CRITICAL] Auto-blocking $ip (Score: $combined_score, Attacks: $et_attack_types)" >> "$TEMP_DIR/recent_events" + if type quick_block_ip &>/dev/null; then + quick_block_ip "$ip" "ET:$et_attack_types" & + fi + fi + fi + fi + fi + fi + + # Get updated data + local intel=$(get_ip_intelligence "$ip") + IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$intel" + + # Determine if this is a threat + local level=$(get_threat_level "$score") + + # Log all traffic with attacks, or score > 0, or suspicious bots, or ET detection + # This ensures we see everything interesting, not just high scores + if [ "$score" -gt 0 ] || [ -n "$attacks" ] || [ "$bot_type" = "suspicious" ] || [ "$et_attack_score" -gt 0 ]; then + local color=$(get_threat_color "$level") + local time_str=$(date +"%H:%M:%S") + + # Use ET score if higher than regular score + local display_score="$score" + if [ "$et_attack_score" -gt "$score" ]; then + display_score="$et_attack_score" + level=$(get_threat_level "$et_attack_score") + color=$(get_threat_color "$level") + fi + + # Build log line + local log_line="${color}[${time_str}] $ip" + log_line+=" | Score:$display_score [$level]" + + # Show ET detection if found + if [ "$et_attack_score" -gt 0 ]; then + # Show primary attack type (cleaner than full list) + local primary_type=$(echo "$et_attack_types" | grep -oE 'SQLI|XSS|CMD|TRAVERSAL|WEBSHELL|RCE|UPLOAD|CVE' | head -1) + if [ -z "$primary_type" ]; then + primary_type=$(echo "$et_attack_types" | cut -d',' -f1) + fi + log_line+=" | 🛡️ET:$primary_type" + + # Show signature names (the key improvement!) + if [ -n "$et_signatures" ]; then + # Limit to first 3 signatures to keep display clean + local sig_display=$(echo "$et_signatures" | tr ',' '\n' | head -3 | tr '\n' ',' | sed 's/,$//') + log_line+=" | Sigs:$sig_display" + fi + + # Show rate info if elevated + if [ "$et_rate_score" -gt 0 ]; then + log_line+=" | 🌊Rate:+$et_rate_score" + fi + fi + + # Show bot type if interesting + if [ "$bot_type" = "suspicious" ] || [ "$bot_type" = "ai" ]; then + log_line+=" | Bot:$bot_type" + fi + + # Show legacy attacks if no ET detection + if [ -n "$attacks" ] && [ "$et_attack_score" -eq 0 ]; then + local first_attack=$(echo "$attacks" | cut -d',' -f1) + local icon=$(get_attack_icon "$first_attack") + log_line+=" | $icon$first_attack" + fi + + log_line+=" | $url${NC}" + + echo -e "$log_line" >> "$TEMP_DIR/recent_events" + fi + fi + done & +} + +################################################################################ +# Main Loop +################################################################################ + +################################################################################ +# SSH Attack Monitoring +################################################################################ + +monitor_ssh_attacks() { + # Monitor SSH brute force attempts from /var/log/secure + local secure_log="/var/log/secure" + + if [ ! -f "$secure_log" ]; then + # Try alternative location (Debian/Ubuntu) + secure_log="/var/log/auth.log" + fi + + if [ -f "$secure_log" ]; then + tail -n 0 -F "$secure_log" 2>/dev/null | while read -r line; do + # Detect failed SSH login attempts (use bash regex for performance) + if [[ "$line" =~ [Ff]ailed\ password|[Aa]uthentication\ failure|[Ii]nvalid\ user ]]; then + # Extract IP address using bash regex + if [[ "$line" =~ ([0-9]{1,3}\.){3}[0-9]{1,3} ]]; then + local ip="${BASH_REMATCH[0]}" + else + continue + fi + + if [ -n "$ip" ]; then + # Skip local/private IPs + if [[ "$ip" =~ ^127\. ]] || \ + [[ "$ip" =~ ^10\. ]] || \ + [[ "$ip" =~ ^192\.168\. ]] || \ + [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[01])\. ]]; then + continue + fi + + # Process as BRUTEFORCE attack + # Read from file (subshells can't access IP_DATA array) + local ip_file="$TEMP_DIR/ip_${ip//\./_}" + local current_data="0|0|human||0|0" + if [ -f "$ip_file" ]; then + current_data=$(cat "$ip_file") + fi + IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$current_data" + + # Increment hits + hits=$((hits + 1)) + + # Record timestamp and vector for intelligence + record_attack_timestamp "$ip" + record_attack_vector "$ip" "SSH" + track_subnet_attack "$ip" + + # Add BRUTEFORCE to attacks if not already present + if [[ ! "$attacks" =~ BRUTEFORCE ]]; then + if [ -z "$attacks" ]; then + attacks="BRUTEFORCE" + else + attacks="${attacks},BRUTEFORCE" + fi + # Update attack type counter for display + ((ATTACK_TYPE_COUNTER["BRUTEFORCE"]++)) + fi + + # Progressive scoring for bruteforce: Each attempt adds points + # First attempt: 10 pts, subsequent attempts: +8 pts each + if [ "${hits:-0}" -eq 1 ]; then + score=10 + else + score=$((score + 8)) + fi + + # Apply advanced intelligence bonuses + local block_reasons="" + + # 1. Attack velocity bonus + local velocity_data=$(calculate_attack_velocity "$ip") + IFS='|' read -r vel_count vel_bonus vel_reason <<< "$velocity_data" + if [ "$vel_bonus" -gt 0 ]; then + score=$((score + vel_bonus)) + block_reasons="${vel_reason}" + fi + + # 2. Diversity bonus (multi-vector attack) + local div_data=$(calculate_diversity_bonus "$ip") + IFS='|' read -r div_count div_bonus div_reason <<< "$div_data" + if [ "$div_bonus" -gt 0 ]; then + score=$((score + div_bonus)) + [ -n "$block_reasons" ] && block_reasons="${block_reasons}+" || block_reasons="" + block_reasons="${block_reasons}${div_reason}" + fi + + # 3. Timing pattern detection + local pattern_data=$(detect_timing_pattern "$ip") + IFS='|' read -r pat_type pat_conf pat_bonus pat_reason <<< "$pattern_data" + if [ "$pat_bonus" -gt 0 ]; then + score=$((score + pat_bonus)) + [ -n "$block_reasons" ] && block_reasons="${block_reasons}+" || block_reasons="" + block_reasons="${block_reasons}${pat_reason}" + fi + + # 4. Subnet attack bonus + local subnet_data=$(calculate_subnet_bonus "$ip") + IFS='|' read -r subnet_count subnet_bonus subnet_reason <<< "$subnet_data" + if [ "$subnet_bonus" -gt 0 ]; then + score=$((score + subnet_bonus)) + [ -n "$block_reasons" ] && block_reasons="${block_reasons}+" || block_reasons="" + block_reasons="${block_reasons}${subnet_reason}" + fi + + # 5. Context-aware bonus (geo, ISP, time) + local context_data=$(calculate_context_bonus "$ip") + IFS='|' read -r context_bonus context_reason <<< "$context_data" + if [ "$context_bonus" -gt 0 ]; then + score=$((score + context_bonus)) + [ -n "$block_reasons" ] && block_reasons="${block_reasons}+" || block_reasons="" + block_reasons="${block_reasons}${context_reason}" + fi + + # Cap at 100 + [ "${score:-0}" -gt 100 ] && score=100 + + # Update ip_data file directly (subshells can't access IP_DATA array) + local ip_file="$TEMP_DIR/ip_${ip//\./_}" + echo "$score|$hits|$bot_type|$attacks|$ban_count|$rep_score" > "$ip_file" + + # Store block reasons for CSF + if [ -n "$block_reasons" ]; then + echo "$block_reasons" > "$TEMP_DIR/block_reason_${ip//\./_}" + fi + + # Log to reputation DB + flag_ip_attack "$ip" "BRUTEFORCE" 0 "SSH failed login attempt" >/dev/null 2>&1 & + + # Log event + local time_str=$(date +"%H:%M:%S") + local level=$(get_threat_level "$score") + local color=$(get_threat_color "$level") + local icon=$(get_attack_icon "BRUTEFORCE") + + echo -e "${color}[${time_str}] $ip | Score:$score [$level] | ${icon}SSH_BRUTEFORCE | Hits:$hits${NC}" >> "$TEMP_DIR/recent_events" + fi + fi + done & + fi +} + +################################################################################ +# Firewall Block Monitoring +################################################################################ + +monitor_firewall_blocks() { + # Monitor CSF/iptables blocks in real-time from /var/log/messages + local messages_log="/var/log/messages" + + if [ ! -f "$messages_log" ]; then + # Try alternative location + messages_log="/var/log/syslog" + fi + + if [ -f "$messages_log" ]; then + tail -n 0 -F "$messages_log" 2>/dev/null | while read -r line; do + # Detect firewall blocks (use bash regex for performance) + if [[ "$line" =~ [Ff]irewall|iptables.*(DENY|DROP)|CSF.*block ]]; then + # Extract IP address using bash regex + if [[ "$line" =~ ([0-9]{1,3}\.){3}[0-9]{1,3} ]]; then + local ip="${BASH_REMATCH[0]}" + else + continue + fi + + if [ -n "$ip" ]; then + # Skip local/private IPs + if [[ "$ip" =~ ^127\. ]] || \ + [[ "$ip" =~ ^10\. ]] || \ + [[ "$ip" =~ ^192\.168\. ]] || \ + [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[01])\. ]]; then + continue + fi + + # Log firewall block + local time_str=$(date +"%H:%M:%S") + echo -e "${LOW_COLOR}[${time_str}] $ip | FIREWALL_BLOCK | Blocked by firewall${NC}" >> "$TEMP_DIR/recent_events" + fi + fi + done & + fi +} + +################################################################################ +# cPHulk Monitoring +################################################################################ + +monitor_cphulk_blocks() { + # Monitor cPHulk blocks (cPanel security system - cPanel ONLY) + # Skip if not cPanel + if [ "$SYS_CONTROL_PANEL" != "cpanel" ]; then + return 0 + fi + + if [ -x "/usr/local/cpanel/bin/cphulk_pam_ctl" ] || command -v whmapi1 &>/dev/null; then + ( + declare -A SEEN_BLOCKS + while true; do + # Query cPHulk for blocked IPs + whmapi1 cphulkd_list_blocks 2>/dev/null | grep -E "ip:" | while read -r line; do + local ip=$(echo "$line" | awk '{print $2}') + + if [ -n "$ip" ] && [ -z "${SEEN_BLOCKS[$ip]}" ]; then + SEEN_BLOCKS[$ip]=1 + + # Skip local/private IPs + if [[ "$ip" =~ ^127\. ]] || \ + [[ "$ip" =~ ^10\. ]] || \ + [[ "$ip" =~ ^192\.168\. ]] || \ + [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[01])\. ]]; then + continue + fi + + # Process as BRUTEFORCE attack (cPHulk blocks login attempts) + local current_data="${IP_DATA[$ip]:-0|0|human||0|0}" + IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$current_data" + + # Add BRUTEFORCE to attacks + if [[ ! "$attacks" =~ BRUTEFORCE ]]; then + if [ -z "$attacks" ]; then + attacks="BRUTEFORCE" + else + attacks="${attacks},BRUTEFORCE" + fi + fi + + # Calculate score + score=$(calculate_attack_score "$attacks") + hits=$((hits + 1)) + + # Update IP_DATA + IP_DATA[$ip]="$score|$hits|$bot_type|$attacks|$ban_count|$rep_score" + + # Log event + local time_str=$(date +"%H:%M:%S") + local level=$(get_threat_level "$score") + local color=$(get_threat_color "$level") + + echo -e "${color}[${time_str}] $ip | Score:$score [$level] | 🔐CPHULK_BLOCK | Blocked by cPHulk${NC}" >> "$TEMP_DIR/recent_events" + fi + done + sleep 10 # Poll every 10 seconds + done + ) & + fi +} + +################################################################################ +# Network Attack Monitoring (SYN floods, port scans, DDoS) +################################################################################ + +monitor_network_attacks() { + # Monitor kernel logs and network statistics for SYN floods, port scans, etc. + local kern_log="/var/log/kern.log" + + # Try different log locations + if [ ! -f "$kern_log" ]; then + kern_log="/var/log/messages" + fi + + # Monitor kernel/firewall logs for network attacks + if [ -f "$kern_log" ]; then + tail -n 0 -F "$kern_log" 2>/dev/null | while read -r line; do + # Detect SYN flood patterns (use bash regex for performance) + if [[ "$line" =~ SYN\ flood|possible\ SYN\ flooding|TCP:\ Possible\ SYN\ flooding ]]; then + # Extract IP address using bash regex + if [[ "$line" =~ ([0-9]{1,3}\.){3}[0-9]{1,3} ]]; then + local ip="${BASH_REMATCH[0]}" + else + continue + fi + + if [ -n "$ip" ]; then + # Skip local/private IPs + if [[ "$ip" =~ ^127\. ]] || \ + [[ "$ip" =~ ^10\. ]] || \ + [[ "$ip" =~ ^192\.168\. ]] || \ + [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[01])\. ]]; then + continue + fi + + # Process as DDOS attack + local current_data="${IP_DATA[$ip]:-0|0|human||0|0}" + IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$current_data" + + # Add DDOS to attacks + if [[ ! "$attacks" =~ DDOS ]]; then + if [ -z "$attacks" ]; then + attacks="DDOS" + else + attacks="${attacks},DDOS" + fi + fi + + # Calculate score (DDOS is high severity) + score=$(calculate_attack_score "$attacks") + hits=$((hits + 1)) + + # Update IP_DATA + IP_DATA[$ip]="$score|$hits|$bot_type|$attacks|$ban_count|$rep_score" + + # Log to reputation DB + flag_ip_attack "$ip" "DDOS" 0 "SYN flood detected" >/dev/null 2>&1 & + + # Log event + local time_str=$(date +"%H:%M:%S") + local level=$(get_threat_level "$score") + local color=$(get_threat_color "$level") + + echo -e "${color}[${time_str}] $ip | Score:$score [$level] | 💥SYN_FLOOD | Network attack${NC}" >> "$TEMP_DIR/recent_events" + fi + fi + + # Detect port scan attempts (use bash regex for performance) + if [[ "$line" =~ port.*scan|stealth\ scan|SYN-FIN\ scan|NULL\ scan ]]; then + # Extract IP address using bash regex + if [[ "$line" =~ ([0-9]{1,3}\.){3}[0-9]{1,3} ]]; then + local ip="${BASH_REMATCH[0]}" + else + continue + fi + + if [ -n "$ip" ]; then + # Skip local/private IPs + if [[ "$ip" =~ ^127\. ]] || \ + [[ "$ip" =~ ^10\. ]] || \ + [[ "$ip" =~ ^192\.168\. ]] || \ + [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[01])\. ]]; then + continue + fi + + # Process as SCANNER attack + local current_data="${IP_DATA[$ip]:-0|0|human||0|0}" + IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$current_data" + + # Add PORT_SCAN to attacks (using ADMIN_PROBE for now - 5 points) + if [[ ! "$attacks" =~ ADMIN_PROBE ]]; then + if [ -z "$attacks" ]; then + attacks="ADMIN_PROBE" + else + attacks="${attacks},ADMIN_PROBE" + fi + fi + + # Calculate score + score=$(calculate_attack_score "$attacks") + hits=$((hits + 1)) + + # Update IP_DATA + IP_DATA[$ip]="$score|$hits|$bot_type|$attacks|$ban_count|$rep_score" + + # Log event + local time_str=$(date +"%H:%M:%S") + local level=$(get_threat_level "$score") + local color=$(get_threat_color "$level") + + echo -e "${color}[${time_str}] $ip | Score:$score [$level] | 🔎PORT_SCAN | Network reconnaissance${NC}" >> "$TEMP_DIR/recent_events" + fi + fi + done & + fi + + # Monitor netstat for high connection counts (possible DDoS) + if command -v netstat &>/dev/null || command -v ss &>/dev/null; then + ( + declare -A CONNECTION_COUNT + declare -A ALERT_SENT + while true; do + # Use ss if available (faster), otherwise netstat + if command -v ss &>/dev/null; then + # Count SYN_RECV connections per IP (sign of SYN flood) + while read -r ip count; do + if [ "$count" -gt 20 ]; then # More than 20 SYN_RECV connections + if [ -z "${ALERT_SENT[$ip]}" ]; then + ALERT_SENT[$ip]=1 + + # Skip local/private IPs + if [[ "$ip" =~ ^127\. ]] || \ + [[ "$ip" =~ ^10\. ]] || \ + [[ "$ip" =~ ^192\.168\. ]] || \ + [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[01])\. ]]; then + continue + fi + + # Log high connection count + local time_str=$(date +"%H:%M:%S") + echo -e "${HIGH_COLOR}[${time_str}] $ip | 💥HIGH_CONN_COUNT | $count SYN_RECV connections (possible DDoS)${NC}" >> "$TEMP_DIR/recent_events" + fi + else + # Reset alert if connections drop + unset ALERT_SENT[$ip] + fi + done < <(ss -tn state syn-recv 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | sort | uniq -c | awk '$1 > 5 {print $2, $1}') + fi + + sleep 15 # Check every 15 seconds + done + ) & + fi +} + +################################################################################ +# Email/SMTP Attack Monitoring +################################################################################ + +monitor_email_attacks() { + # Monitor mail logs for SMTP/IMAP/POP3 bruteforce + local mail_log="/var/log/maillog" + + if [ ! -f "$mail_log" ]; then + mail_log="/var/log/mail.log" + fi + + if [ -f "$mail_log" ]; then + tail -n 0 -F "$mail_log" 2>/dev/null | while read -r line; do + # Dovecot authentication failures (use bash regex for performance) + if [[ "$line" =~ auth.*failed|authentication\ failed|password\ mismatch ]]; then + # Extract IP address using bash regex + if [[ "$line" =~ ([0-9]{1,3}\.){3}[0-9]{1,3} ]]; then + local ip="${BASH_REMATCH[0]}" + else + continue + fi + + if [ -n "$ip" ]; then + # Skip local/private IPs + [[ "$ip" =~ ^127\. ]] || [[ "$ip" =~ ^10\. ]] || [[ "$ip" =~ ^192\.168\. ]] || [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[01])\. ]] && continue + + # Process as BRUTEFORCE attack + # Read from file (subshells can't access IP_DATA array) + local ip_file="$TEMP_DIR/ip_${ip//\./_}" + local current_data="0|0|human||0|0" + if [ -f "$ip_file" ]; then + current_data=$(cat "$ip_file") + fi + IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$current_data" + + hits=$((hits + 1)) + + # Record timestamp and vector for intelligence + record_attack_timestamp "$ip" + record_attack_vector "$ip" "EMAIL" + track_subnet_attack "$ip" + + # Add BRUTEFORCE to attacks + if [[ ! "$attacks" =~ BRUTEFORCE ]]; then + [ -z "$attacks" ] && attacks="BRUTEFORCE" || attacks="${attacks},BRUTEFORCE" + fi + + # Progressive scoring: Each email bruteforce attempt adds points + if [ "${hits:-0}" -eq 1 ]; then + score=10 + else + score=$((score + 8)) + fi + + # Apply advanced intelligence bonuses + local block_reasons="" + local velocity_data=$(calculate_attack_velocity "$ip") + IFS='|' read -r vel_count vel_bonus vel_reason <<< "$velocity_data" + [ "$vel_bonus" -gt 0 ] && score=$((score + vel_bonus)) && block_reasons="${vel_reason}" + + local div_data=$(calculate_diversity_bonus "$ip") + IFS='|' read -r div_count div_bonus div_reason <<< "$div_data" + if [ "$div_bonus" -gt 0 ]; then + score=$((score + div_bonus)) + [ -n "$block_reasons" ] && block_reasons="${block_reasons}+" || block_reasons="" + block_reasons="${block_reasons}${div_reason}" + fi + + local pattern_data=$(detect_timing_pattern "$ip") + IFS='|' read -r pat_type pat_conf pat_bonus pat_reason <<< "$pattern_data" + if [ "$pat_bonus" -gt 0 ]; then + score=$((score + pat_bonus)) + [ -n "$block_reasons" ] && block_reasons="${block_reasons}+" || block_reasons="" + block_reasons="${block_reasons}${pat_reason}" + fi + + local subnet_data=$(calculate_subnet_bonus "$ip") + IFS='|' read -r subnet_count subnet_bonus subnet_reason <<< "$subnet_data" + if [ "$subnet_bonus" -gt 0 ]; then + score=$((score + subnet_bonus)) + [ -n "$block_reasons" ] && block_reasons="${block_reasons}+" || block_reasons="" + block_reasons="${block_reasons}${subnet_reason}" + fi + + local context_data=$(calculate_context_bonus "$ip") + IFS='|' read -r context_bonus context_reason <<< "$context_data" + if [ "$context_bonus" -gt 0 ]; then + score=$((score + context_bonus)) + [ -n "$block_reasons" ] && block_reasons="${block_reasons}+" || block_reasons="" + block_reasons="${block_reasons}${context_reason}" + fi + + [ "${score:-0}" -gt 100 ] && score=100 + + # Update ip_data file directly (subshells can't access IP_DATA array) + local ip_file="$TEMP_DIR/ip_${ip//\./_}" + echo "$score|$hits|$bot_type|$attacks|$ban_count|$rep_score" > "$ip_file" + + # Store block reasons for CSF + if [ -n "$block_reasons" ]; then + echo "$block_reasons" > "$TEMP_DIR/block_reason_${ip//\./_}" + fi + + # Log to reputation DB + flag_ip_attack "$ip" "BRUTEFORCE" 0 "Email authentication failure" >/dev/null 2>&1 & + + # Log event + local time_str=$(date +"%H:%M:%S") + local level=$(get_threat_level "$score") + local color=$(get_threat_color "$level") + + echo -e "${color}[${time_str}] $ip | Score:$score [$level] | 📧EMAIL_BRUTEFORCE | Hits:$hits${NC}" >> "$TEMP_DIR/recent_events" + fi + fi + done & + fi +} + +################################################################################ +# FTP Attack Monitoring +################################################################################ + +monitor_ftp_attacks() { + # Monitor FTP logs for bruteforce attempts + local ftp_log="/var/log/vsftpd.log" + + if [ ! -f "$ftp_log" ]; then + ftp_log="/var/log/xferlog" + fi + + if [ -f "$ftp_log" ]; then + tail -n 0 -F "$ftp_log" 2>/dev/null | while read -r line; do + # FTP authentication failures (use bash regex for performance) + if [[ "$line" =~ FAIL\ LOGIN|authentication\ failed|530\ Login\ incorrect ]]; then + # Extract IP address using bash regex + if [[ "$line" =~ ([0-9]{1,3}\.){3}[0-9]{1,3} ]]; then + local ip="${BASH_REMATCH[0]}" + else + continue + fi + + if [ -n "$ip" ]; then + # Skip local/private IPs + [[ "$ip" =~ ^127\. ]] || [[ "$ip" =~ ^10\. ]] || [[ "$ip" =~ ^192\.168\. ]] || [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[01])\. ]] && continue + + # Process as BRUTEFORCE attack + # Read from file (subshells can't access IP_DATA array) + local ip_file="$TEMP_DIR/ip_${ip//\./_}" + local current_data="0|0|human||0|0" + if [ -f "$ip_file" ]; then + current_data=$(cat "$ip_file") + fi + IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$current_data" + + hits=$((hits + 1)) + + # Record timestamp and vector for intelligence + record_attack_timestamp "$ip" + record_attack_vector "$ip" "FTP" + track_subnet_attack "$ip" + + # Add BRUTEFORCE to attacks + if [[ ! "$attacks" =~ BRUTEFORCE ]]; then + [ -z "$attacks" ] && attacks="BRUTEFORCE" || attacks="${attacks},BRUTEFORCE" + fi + + # Progressive scoring: Each FTP bruteforce attempt adds points + if [ "${hits:-0}" -eq 1 ]; then + score=10 + else + score=$((score + 8)) + fi + + # Apply advanced intelligence bonuses + local block_reasons="" + local velocity_data=$(calculate_attack_velocity "$ip") + IFS='|' read -r vel_count vel_bonus vel_reason <<< "$velocity_data" + [ "$vel_bonus" -gt 0 ] && score=$((score + vel_bonus)) && block_reasons="${vel_reason}" + + local div_data=$(calculate_diversity_bonus "$ip") + IFS='|' read -r div_count div_bonus div_reason <<< "$div_data" + if [ "$div_bonus" -gt 0 ]; then + score=$((score + div_bonus)) + [ -n "$block_reasons" ] && block_reasons="${block_reasons}+" || block_reasons="" + block_reasons="${block_reasons}${div_reason}" + fi + + local pattern_data=$(detect_timing_pattern "$ip") + IFS='|' read -r pat_type pat_conf pat_bonus pat_reason <<< "$pattern_data" + if [ "$pat_bonus" -gt 0 ]; then + score=$((score + pat_bonus)) + [ -n "$block_reasons" ] && block_reasons="${block_reasons}+" || block_reasons="" + block_reasons="${block_reasons}${pat_reason}" + fi + + local subnet_data=$(calculate_subnet_bonus "$ip") + IFS='|' read -r subnet_count subnet_bonus subnet_reason <<< "$subnet_data" + if [ "$subnet_bonus" -gt 0 ]; then + score=$((score + subnet_bonus)) + [ -n "$block_reasons" ] && block_reasons="${block_reasons}+" || block_reasons="" + block_reasons="${block_reasons}${subnet_reason}" + fi + + local context_data=$(calculate_context_bonus "$ip") + IFS='|' read -r context_bonus context_reason <<< "$context_data" + if [ "$context_bonus" -gt 0 ]; then + score=$((score + context_bonus)) + [ -n "$block_reasons" ] && block_reasons="${block_reasons}+" || block_reasons="" + block_reasons="${block_reasons}${context_reason}" + fi + + [ "${score:-0}" -gt 100 ] && score=100 + + # Update ip_data file directly (subshells can't access IP_DATA array) + local ip_file="$TEMP_DIR/ip_${ip//\./_}" + echo "$score|$hits|$bot_type|$attacks|$ban_count|$rep_score" > "$ip_file" + + # Store block reasons for CSF + if [ -n "$block_reasons" ]; then + echo "$block_reasons" > "$TEMP_DIR/block_reason_${ip//\./_}" + fi + + # Log to reputation DB + flag_ip_attack "$ip" "BRUTEFORCE" 0 "FTP login failure" >/dev/null 2>&1 & + + # Log event + local time_str=$(date +"%H:%M:%S") + local level=$(get_threat_level "$score") + local color=$(get_threat_color "$level") + + echo -e "${color}[${time_str}] $ip | Score:$score [$level] | 📁FTP_BRUTEFORCE | Hits:$hits${NC}" >> "$TEMP_DIR/recent_events" + fi + fi + done & + fi +} + +################################################################################ +# Database Attack Monitoring +################################################################################ + +monitor_database_attacks() { + # Monitor MySQL logs for authentication failures + local mysql_log="/var/log/mysqld.log" + + if [ ! -f "$mysql_log" ]; then + mysql_log="/var/log/mysql/error.log" + fi + + if [ -f "$mysql_log" ]; then + tail -n 0 -F "$mysql_log" 2>/dev/null | while read -r line; do + # MySQL authentication failures (use bash regex for performance) + if [[ "$line" =~ Access\ denied\ for\ user|Failed\ password\ for ]]; then + # Extract IP address using bash regex + if [[ "$line" =~ ([0-9]{1,3}\.){3}[0-9]{1,3} ]]; then + local ip="${BASH_REMATCH[0]}" + else + continue + fi + + if [ -n "$ip" ]; then + # Skip local/private IPs + [[ "$ip" =~ ^127\. ]] || [[ "$ip" =~ ^10\. ]] || [[ "$ip" =~ ^192\.168\. ]] || [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[01])\. ]] && continue + + # Process as SQL_INJECTION attack (database level) + # Read from file (subshells can't access IP_DATA array) + local ip_file="$TEMP_DIR/ip_${ip//\./_}" + local current_data="0|0|human||0|0" + if [ -f "$ip_file" ]; then + current_data=$(cat "$ip_file") + fi + IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$current_data" + + hits=$((hits + 1)) + + # Record timestamp and vector for intelligence + record_attack_timestamp "$ip" + record_attack_vector "$ip" "DATABASE" + track_subnet_attack "$ip" + + # Add SQL_INJECTION to attacks + local is_new_attack=0 + if [[ ! "$attacks" =~ SQL_INJECTION ]]; then + [ -z "$attacks" ] && attacks="SQL_INJECTION" || attacks="${attacks},SQL_INJECTION" + is_new_attack=1 + fi + + # Progressive scoring: First DB attack = 15pts, each additional = 12pts + if [ "${is_new_attack:-0}" -eq 1 ]; then + score=$((score + 15)) + else + score=$((score + 12)) + fi + + # Apply advanced intelligence bonuses + local block_reasons="" + local velocity_data=$(calculate_attack_velocity "$ip") + IFS='|' read -r vel_count vel_bonus vel_reason <<< "$velocity_data" + [ "$vel_bonus" -gt 0 ] && score=$((score + vel_bonus)) && block_reasons="${vel_reason}" + + local div_data=$(calculate_diversity_bonus "$ip") + IFS='|' read -r div_count div_bonus div_reason <<< "$div_data" + if [ "$div_bonus" -gt 0 ]; then + score=$((score + div_bonus)) + [ -n "$block_reasons" ] && block_reasons="${block_reasons}+" || block_reasons="" + block_reasons="${block_reasons}${div_reason}" + fi + + local pattern_data=$(detect_timing_pattern "$ip") + IFS='|' read -r pat_type pat_conf pat_bonus pat_reason <<< "$pattern_data" + if [ "$pat_bonus" -gt 0 ]; then + score=$((score + pat_bonus)) + [ -n "$block_reasons" ] && block_reasons="${block_reasons}+" || block_reasons="" + block_reasons="${block_reasons}${pat_reason}" + fi + + local subnet_data=$(calculate_subnet_bonus "$ip") + IFS='|' read -r subnet_count subnet_bonus subnet_reason <<< "$subnet_data" + if [ "$subnet_bonus" -gt 0 ]; then + score=$((score + subnet_bonus)) + [ -n "$block_reasons" ] && block_reasons="${block_reasons}+" || block_reasons="" + block_reasons="${block_reasons}${subnet_reason}" + fi + + local context_data=$(calculate_context_bonus "$ip") + IFS='|' read -r context_bonus context_reason <<< "$context_data" + if [ "$context_bonus" -gt 0 ]; then + score=$((score + context_bonus)) + [ -n "$block_reasons" ] && block_reasons="${block_reasons}+" || block_reasons="" + block_reasons="${block_reasons}${context_reason}" + fi + + [ "${score:-0}" -gt 100 ] && score=100 + + # Update ip_data file directly (subshells can't access IP_DATA array) + local ip_file="$TEMP_DIR/ip_${ip//\./_}" + echo "$score|$hits|$bot_type|$attacks|$ban_count|$rep_score" > "$ip_file" + + # Store block reasons for CSF + if [ -n "$block_reasons" ]; then + echo "$block_reasons" > "$TEMP_DIR/block_reason_${ip//\./_}" + fi + + # Log to reputation DB + flag_ip_attack "$ip" "SQL_INJECTION" 0 "MySQL authentication failure" >/dev/null 2>&1 & + + # Log event + local time_str=$(date +"%H:%M:%S") + local level=$(get_threat_level "$score") + local color=$(get_threat_color "$level") + + echo -e "${color}[${time_str}] $ip | Score:$score [$level] | 🗄️ DB_BRUTEFORCE | Hits:$hits${NC}" >> "$TEMP_DIR/recent_events" + fi + fi + done & + fi +} + +################################################################################ +# Distributed Attack Detection +################################################################################ + +detect_distributed_attacks() { + # Run in background, check every 30 seconds + ( + while true; do + sleep 30 + + # Look for same attack pattern from multiple IPs in short time + if [ -f "$TEMP_DIR/recent_events" ]; then + # Get recent attacks (last 2 minutes) + local recent=$(tail -200 "$TEMP_DIR/recent_events" 2>/dev/null) + + # 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" ' + $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 + break + } + } + } + END { + print count "|" length(ips) + } + ') + IFS='|' read -r attack_count unique_ips <<< "$result" + + if [ "${attack_count:-0}" -ge 5 ]; then + + 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" + + # Mark in a file for Quick Actions to see + echo "${attack_type}|${unique_ips}|$(date +%s)" >> "$TEMP_DIR/distributed_attacks" + fi + fi + done + fi + done + ) & +} + +################################################################################ +# Automatic Mitigation Engine +################################################################################ + +auto_mitigation_engine() { + # Run in background, check every 10 seconds + ( + # Track already blocked IPs in this session + declare -A BLOCKED_THIS_SESSION + + while true; do + sleep 10 + + # Read current IP data from snapshot file (updated by main process) + if [ -f "$TEMP_DIR/ip_data" ]; then + while IFS='=' read -r ip data; do + [ -z "$ip" ] && continue + + IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$data" + + # Auto-block at score >= 80 (CRITICAL) + if [ "$score" -ge 80 ]; then + # Skip if already blocked in this session + [ -n "${BLOCKED_THIS_SESSION[$ip]}" ] && continue + + # Mark as blocked to prevent duplicate attempts + BLOCKED_THIS_SESSION[$ip]=1 + + # Auto-block + local time_str=$(date +"%H:%M:%S") + echo -e "${CRITICAL_COLOR}[${time_str}] AUTO_BLOCK | $ip | Score:$score | ${attacks}${NC}" >> "$TEMP_DIR/recent_events" + + # Get detailed block reason + local block_reason="Auto-block: Score=$score Attacks=${attacks}" + if [ -f "$TEMP_DIR/block_reason_${ip//\./_}" ]; then + local intel_reason=$(cat "$TEMP_DIR/block_reason_${ip//\./_}") + block_reason="${block_reason} Intel:${intel_reason}" + fi + + # Block for 1 hour with detailed reason + # Block in background and counter is updated within function + block_ip_temporary "$ip" 1 "$block_reason" & + fi + done < "$TEMP_DIR/ip_data" + fi + done + ) & +} + +# Start all log monitoring sources +monitor_apache_logs +monitor_ssh_attacks +monitor_email_attacks +monitor_ftp_attacks +monitor_database_attacks +monitor_firewall_blocks +monitor_cphulk_blocks +monitor_network_attacks + +# Start intelligence engines +detect_distributed_attacks +auto_mitigation_engine + +# Reputation decay engine (runs every 30 min) +( + while true; do + sleep $DECAY_CHECK_INTERVAL + apply_reputation_decay + done +) & + +# Blocked IPs cache updater (only needed in CSF mode - IPset mode appends to cache on each block) +if [ "$IPSET_AVAILABLE" -eq 0 ]; then + ( + while true; do + { + # Get CSF temporary blocks - extract just the IP address + if command -v csf &>/dev/null; then + csf -t 2>/dev/null | awk '{print $1}' | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' + fi + + # Get CSF permanent denies + if [ -f /etc/csf/csf.deny ]; then + awk '{print $1}' /etc/csf/csf.deny 2>/dev/null | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' + fi + + # Get iptables DROP rules + if command -v iptables &>/dev/null; then + iptables -L INPUT -n -v 2>/dev/null | grep DROP | awk '{print $8}' | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' + fi + } | sort -u > "$TEMP_DIR/blocked_ips_cache.tmp" 2>/dev/null + mv "$TEMP_DIR/blocked_ips_cache.tmp" "$TEMP_DIR/blocked_ips_cache" 2>/dev/null + sleep 10 + done + ) & +fi + +# Periodic snapshot saving in background +( + while true; do + sleep 300 # Save every 5 minutes + save_snapshot + done +) & + +# Main dashboard loop +LOOP_COUNT=0 +while true; do + # Sync individual IP files into IP_DATA array (for data from subshell processes like SSH monitoring) + for ip_file in "$TEMP_DIR"/ip_*; do + [ -f "$ip_file" ] || continue + basename_file="$(basename "$ip_file")" + + # Skip non-IP files explicitly + case "$basename_file" in + ip_data|ip_database.db|*cache*|*blocked*|*debug*) + continue + ;; + esac + + # Validate it's an IP file (should match pattern ip_N_N_N_N) + # Using bash pattern matching instead of grep for performance + if [[ ! "$basename_file" =~ ^ip_[0-9]{1,3}_[0-9]{1,3}_[0-9]{1,3}_[0-9]{1,3}$ ]]; then + continue + fi + + # Extract IP from filename (ip_1_2_3_4 -> 1.2.3.4) + # Using bash string manipulation for performance + ip="${basename_file#ip_}" # Remove 'ip_' prefix + ip="${ip//_/.}" # Replace all underscores with dots + data=$(cat "$ip_file" 2>/dev/null) + + # Validate data format (should be score|hits|bot_type|attacks|ban_count|rep_score) + # Using bash pattern matching instead of grep for performance + if [ -n "$data" ] && [[ "$data" == *"|"* ]]; then + # Update IP_DATA array with data from file + IP_DATA[$ip]="$data" + fi + done + + draw_header + draw_intelligence_panel + draw_attack_breakdown + draw_live_feed + draw_quick_actions + + # Write IP_DATA to ip_data file for auto-mitigation engine + { + for ip in "${!IP_DATA[@]}"; do + echo "$ip=${IP_DATA[$ip]}" + done + } > "$TEMP_DIR/ip_data" 2>/dev/null + + # Update total blocks from file + if [ -f "$TEMP_DIR/total_blocks" ]; then + TOTAL_BLOCKS=$(cat "$TEMP_DIR/total_blocks") + fi + + # Periodic cleanup (every 50 loops = ~100 seconds) + ((LOOP_COUNT++)) + if [ $((LOOP_COUNT % 50)) -eq 0 ]; then + # Trim event log to last 1000 lines + if [ -f "$TEMP_DIR/recent_events" ]; then + tail -1000 "$TEMP_DIR/recent_events" > "$TEMP_DIR/recent_events.tmp" 2>/dev/null + mv "$TEMP_DIR/recent_events.tmp" "$TEMP_DIR/recent_events" 2>/dev/null + fi + fi + + # Non-blocking input with timeout + read -t $REFRESH_INTERVAL -n 1 key + + case "$key" in + b|B) + show_blocking_menu + ;; + c|C) + # Security hardening menu + show_security_hardening_menu + ;; + v|V) + # Toggle compact/verbose mode + if [ "$COMPACT_MODE" -eq 1 ]; then + COMPACT_MODE=0 + else + COMPACT_MODE=1 + fi + ;; + i|I) + # Show threat intelligence for specific IP + clear + print_banner "Threat Intelligence Lookup" + echo "" + read -p "Enter IP address: " lookup_ip + if [ -n "$lookup_ip" ]; then + echo "" + echo "Querying threat intelligence for $lookup_ip..." + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + local threat_intel=$(get_threat_intelligence "$lookup_ip") + IFS='|' read -r abuse_conf abuse_rpts country isp geo timing whitelisted <<< "$threat_intel" + echo "" + echo "${BOLD}Threat Intelligence:${NC}" + echo " AbuseIPDB Confidence: ${abuse_conf}%" + echo " Total Abuse Reports: $abuse_rpts" + echo " Country: ${geo:-$country}" + echo " ISP: $isp" + echo " Timing Pattern: $timing" + echo " Whitelisted: $whitelisted" + echo "" + if is_high_risk_country "${geo:-XX}"; then + echo -e "${HIGH_COLOR} ⚠️ HIGH RISK COUNTRY${NC}" + fi + if [ "${abuse_conf:-0}" -ge 75 ]; then + echo -e "${CRITICAL_COLOR} 🚨 HIGH CONFIDENCE MALICIOUS${NC}" + elif [ "${abuse_conf:-0}" -ge 50 ]; then + echo -e "${HIGH_COLOR} ⚠️ MEDIUM CONFIDENCE THREAT${NC}" + fi + echo "" + read -p "Generate full incident report? (y/n): " gen_report + if [[ "$gen_report" =~ ^[Yy]$ ]]; then + local report_file=$(generate_incident_report "$lookup_ip") + echo "" + echo "Report generated: $report_file" + echo "" + echo "View report? (y/n): " + read -n 1 view_report + if [[ "$view_report" =~ ^[Yy]$ ]]; then + less "$report_file" + fi + fi + fi + echo "" + read -p "Press Enter to return to monitor..." + ;; + p|P) + # Show performance impact + clear + print_banner "Server Performance Monitor" + echo "" + local load_data=$(get_server_load) + IFS='|' read -r load1 load5 load15 cpu_count <<< "$load_data" + echo "${BOLD}Current Load:${NC}" + echo " 1 min: $load1" + echo " 5 min: $load5" + echo " 15 min: $load15" + echo " CPU cores: $cpu_count" + echo "" + if is_server_stressed; then + echo -e "${CRITICAL_COLOR} 🔥 SERVER UNDER STRESS${NC}" + echo "" + echo " Recommended Actions:" + echo " • Enable aggressive auto-blocking (higher threshold)" + echo " • Reduce CT_LIMIT temporarily" + echo " • Block high-volume attack IPs immediately" + else + echo -e "${SAFE_COLOR} ✓ Server load normal${NC}" + fi + echo "" + read -p "Press Enter to return to monitor..." + ;; + q|Q) + cleanup + ;; + r|R) + # Force refresh + continue + ;; + s|S) + # Show stats + clear + show_ip_reputation_stats + read -p "Press Enter to continue..." + ;; + h|H|\?) + # Show help + clear + print_banner "Keyboard Controls" + echo "" + echo "Available Commands:" + echo " ${BOLD}b${NC} - Open IP blocking menu (batch or individual)" + echo " ${BOLD}c${NC} - Security hardening menu (SYNFLOOD, SSH, CT_LIMIT, Port Knocking)" + echo " ${BOLD}i${NC} - Threat intelligence lookup (AbuseIPDB, geo, incident reports)" + echo " ${BOLD}p${NC} - Show performance impact monitor (server load)" + echo " ${BOLD}s${NC} - Show IP reputation database statistics" + echo " ${BOLD}r${NC} - Force refresh display" + echo " ${BOLD}h${NC} - Show this help screen" + echo " ${BOLD}q${NC} - Quit and save snapshot" + echo "" + echo "Features:" + echo " • Real-time bot classification (legit/AI/monitor/suspicious)" + echo " • Attack vector detection (SQL, XSS, RCE, etc.)" + echo " • Threat scoring (0-100 scale)" + echo " • Threat intelligence integration (AbuseIPDB, geolocation)" + echo " • Attack pattern learning & behavioral analysis" + echo " • Automated incident report generation" + echo " • Smart whitelisting (CDNs, search engines)" + echo " • IP reputation DB integration" + echo " • CSF/iptables temporary bans (1 hour default)" + echo " • Auto-mitigation at critical threshold (score ≥80)" + echo " • Memory protection (max ${MAX_TRACKED_IPS} IPs tracked)" + echo " • Auto-save every 5 minutes + on exit" + echo "" + read -p "Press Enter to continue..." + ;; + esac +done