#!/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/attack-patterns.sh" source "$SCRIPT_DIR/lib/threat-intelligence.sh" # 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 # Detect attacks in URL (pass user_agent and ip for enhanced detection) local new_attacks=$(detect_all_attacks "$url" "$method" "$user_agent" "$ip") if [ -n "$new_attacks" ]; then # Add to attack list (unique) if [ -z "$attacks" ]; then attacks="$new_attacks" else attacks="$attacks,$new_attacks" fi # Remove duplicates using associative array (faster than sort -u pipeline) local -A unique_attacks IFS=',' read -ra ATTACK_LIST <<< "$attacks" for atk in "${ATTACK_LIST[@]}"; do [ -n "$atk" ] && unique_attacks[$atk]=1 done attacks=$(IFS=','; echo "${!unique_attacks[*]}") # Update attack type counter IFS=',' read -ra ATTACK_ARRAY <<< "$new_attacks" for attack in "${ATTACK_ARRAY[@]}"; do ((ATTACK_TYPE_COUNTER["$attack"]++)) done # Calculate attack score local attack_score=$(calculate_attack_score "$new_attacks") score=$((score + attack_score)) ((TOTAL_THREATS++)) fi # 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 } # Quick block IP (wrapper for background auto-blocking) # Used by ET detection and auto-mitigation engine quick_block_ip() { local ip="$1" local reason="${2:-Auto-block: Critical threat}" # Validate IP if ! is_valid_ip "$ip"; then return 1 fi # Block for 1 hour using IPset or CSF block_ip_temporary "$ip" 1 "$reason" >/dev/null 2>&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" # Replace IP threat score with ET detection score # Note: We use ET score instead of adding it to avoid double-counting # (update_ip_intelligence already detected the same attack via legacy patterns) local current_intel=$(get_ip_intelligence "$ip") IFS='|' read -r curr_score curr_hits curr_bot curr_attacks curr_ban curr_rep <<< "$current_intel" # Use ET score if it's higher than current score local new_score="$et_attack_score" if [ "$curr_score" -gt "$et_attack_score" ]; then # Keep higher score (e.g., from AbuseIPDB reputation boost) new_score="$curr_score" fi [ "$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 # Get total SYN_RECV count for distributed attack detection local total_syn=$(ss -tn state syn-recv 2>/dev/null | wc -l) local attack_severity=0 local unique_ips=0 # Multi-tier distributed DDoS detection with adaptive learning if [ "$total_syn" -gt 500 ]; then attack_severity=4 # Critical DDoS (new tier) elif [ "$total_syn" -gt 300 ]; then attack_severity=3 # Severe DDoS elif [ "$total_syn" -gt 150 ]; then attack_severity=2 # Major DDoS elif [ "$total_syn" -gt 75 ]; then attack_severity=1 # Moderate DDoS fi # Attack momentum tracking: Check if attack is growing local prev_total="${PREV_TOTAL_SYN:-0}" local attack_momentum=0 if [ "$total_syn" -gt "$prev_total" ] && [ "$prev_total" -gt 0 ]; then local growth=$((total_syn - prev_total)) if [ "$growth" -gt 100 ]; then attack_momentum=2 # Rapidly accelerating elif [ "$growth" -gt 30 ]; then attack_momentum=1 # Accelerating fi fi PREV_TOTAL_SYN=$total_syn # Count unique attacker IPs and track /24 subnets declare -A subnet_counts local attacker_ips=$(ss -tn state syn-recv 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | sort -u) while IFS= read -r attacker_ip; do [ -z "$attacker_ip" ] && continue ((unique_ips++)) # Track /24 subnets to detect coordinated attacks local subnet=$(echo "$attacker_ip" | cut -d. -f1-3) ((subnet_counts[$subnet]++)) done <<< "$attacker_ips" # Coordinated botnet detection: 3+ IPs from same /24 local coordinated_attack=0 declare -A hostile_subnets for subnet in "${!subnet_counts[@]}"; do if [ "${subnet_counts[$subnet]}" -ge 3 ]; then coordinated_attack=1 hostile_subnets[$subnet]=${subnet_counts[$subnet]} fi done # Subnet-level auto-blocking for severe attacks # If attack_severity >= 3 AND subnet has 10+ attacking IPs, block entire /24 if [ "$attack_severity" -ge 3 ]; then for subnet in "${!hostile_subnets[@]}"; do local subnet_ip_count=${hostile_subnets[$subnet]} if [ "$subnet_ip_count" -ge 10 ]; then # Block entire /24 subnet via IPset local subnet_cidr="${subnet}.0/24" if ! grep -q "^${subnet_cidr}\$" "$TEMP_DIR/blocked_subnets" 2>/dev/null; then echo "$subnet_cidr" >> "$TEMP_DIR/blocked_subnets" ( # Add to IPset if available if command -v ipset &>/dev/null && ipset list blocklist &>/dev/null 2>&1; then ipset add blocklist "$subnet_cidr" -exist 2>/dev/null fi # Also add to CSF if command -v csf &>/dev/null; then csf -d "$subnet_cidr" "SUBNET_DDOS:${subnet_ip_count}IPs" 2>/dev/null fi ) & local time_str=$(date +"%H:%M:%S") echo -e "${CRITICAL_COLOR}[${time_str}] SUBNET_BLOCK | $subnet_cidr | IPs:${subnet_ip_count} | Severity:${attack_severity}${NC}" >> "$TEMP_DIR/recent_events" fi fi done fi # Count SYN_RECV connections per IP (sign of SYN flood) while read -r ip count; do # Skip local/private IPs first if [[ "$ip" =~ ^127\. ]] || \ [[ "$ip" =~ ^10\. ]] || \ [[ "$ip" =~ ^192\.168\. ]] || \ [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[01])\. ]]; then continue fi # Track connection count for this IP CONNECTION_COUNT[$ip]=$count # Dynamic threshold based on attack severity + momentum: # Tier 0: >20 connections (normal, focused attack) # Tier 1: >10 connections (75-150 total, moderate DDoS) # Tier 2: >6 connections (150-300 total, major DDoS) # Tier 3: >4 connections (300-500 total, severe DDoS) # Tier 4: >3 connections (500+ total, CRITICAL DDoS) local threshold=20 case "$attack_severity" in 4) threshold=3 ;; # Critical: Very aggressive (safe for production) 3) threshold=4 ;; # Severe: Aggressive 2) threshold=6 ;; # Major: Balanced 1) threshold=10 ;; # Moderate: Conservative esac # Attack momentum adaptation: Lower threshold if attack is growing if [ "$attack_momentum" -eq 2 ] && [ "$threshold" -gt 3 ]; then threshold=$((threshold - 2)) # Rapidly accelerating attack elif [ "$attack_momentum" -eq 1 ] && [ "$threshold" -gt 3 ]; then threshold=$((threshold - 1)) # Accelerating attack fi # Coordinated attack bonus: Lower threshold by 1 (stacks with momentum) if [ "$coordinated_attack" -eq 1 ] && [ "$threshold" -gt 3 ]; then threshold=$((threshold - 1)) fi # Minimum threshold of 3 to prevent false positives on busy web servers [ "$threshold" -lt 3 ] && threshold=3 if [ "$count" -gt "$threshold" ]; then # Only process once per detection window if [ -z "${ALERT_SENT[$ip]}" ]; then ALERT_SENT[$ip]=1 # Update IP reputation via file (subshell 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)) # Smart whitelisting: Skip IPs with successful established connections local established_conns=$(ss -tn state established 2>/dev/null | grep -c "$ip" || echo "0") if [ "$established_conns" -ge 5 ]; then # IP has 5+ established connections = legitimate traffic continue fi # Enhanced threat intelligence on first detection if [ "${hits:-0}" -eq 1 ]; then # Check if whitelisted service first if is_whitelisted_service "$ip" 2>/dev/null; then continue # Skip whitelisted IPs fi # Get threat intelligence in background to avoid slowdown ( 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 for later use echo "$threat_intel" > "$TEMP_DIR/threat_enrich_${ip//\./_}" # Geographic clustering detection if [ -n "$geo" ] && [ "$geo" != "XX" ]; then echo "$geo" >> "$TEMP_DIR/attack_countries" # Check if this country has 5+ attacking IPs local country_count=$(grep -c "^${geo}$" "$TEMP_DIR/attack_countries" 2>/dev/null || echo "0") if [ "$country_count" -ge 5 ]; then # Coordinated attack from same country - boost all IPs from there echo "$geo" >> "$TEMP_DIR/hostile_countries" fi fi # ASN clustering detection if [ -n "$isp" ]; then # Extract ASN number from ISP string local asn=$(echo "$isp" | grep -oP 'AS\K\d+' | head -1) if [ -n "$asn" ]; then echo "$asn" >> "$TEMP_DIR/attack_asns" local asn_count=$(grep -c "^${asn}$" "$TEMP_DIR/attack_asns" 2>/dev/null || echo "0") if [ "$asn_count" -ge 3 ]; then # Same ASN/hosting provider used by 3+ attackers echo "$asn" >> "$TEMP_DIR/hostile_asns" fi fi fi # Apply reputation boosts based on AbuseIPDB if [ "${abuse_conf:-0}" -ge 75 ]; then # High confidence malicious - add 30 points local curr_data=$(cat "$ip_file" 2>/dev/null || echo "0|0|human||0|0") IFS='|' read -r old_score old_hits old_bot old_attacks old_ban old_rep <<< "$curr_data" local new_score=$((old_score + 30)) [ "$new_score" -gt 100 ] && new_score=100 echo "$new_score|$old_hits|$old_bot|$old_attacks|$old_ban|$old_rep" > "$ip_file" elif [ "${abuse_conf:-0}" -ge 50 ]; then # Medium confidence - add 15 points local curr_data=$(cat "$ip_file" 2>/dev/null || echo "0|0|human||0|0") IFS='|' read -r old_score old_hits old_bot old_attacks old_ban old_rep <<< "$curr_data" local new_score=$((old_score + 15)) [ "$new_score" -gt 100 ] && new_score=100 echo "$new_score|$old_hits|$old_bot|$old_attacks|$old_ban|$old_rep" > "$ip_file" fi # High-risk country adds 5 points if is_high_risk_country "${geo:-XX}" 2>/dev/null; then local curr_data=$(cat "$ip_file" 2>/dev/null || echo "0|0|human||0|0") IFS='|' read -r old_score old_hits old_bot old_attacks old_ban old_rep <<< "$curr_data" local new_score=$((old_score + 5)) [ "$new_score" -gt 100 ] && new_score=100 echo "$new_score|$old_hits|$old_bot|$old_attacks|$old_ban|$old_rep" > "$ip_file" fi ) & fi # Reputation pre-boost: IPs with existing HTTP attacks get higher SYN scoring local http_attack_bonus=0 if [[ "$attacks" =~ (SQLI|XSS|RCE|LFI|RFI|WEBSHELL|XXE|SSRF) ]]; then http_attack_bonus=25 # Already known attacker, very suspicious fi # Record attack intelligence record_attack_timestamp "$ip" record_attack_vector "$ip" "NETWORK" track_subnet_attack "$ip" # Add SYN_FLOOD to attacks if not already present if [[ ! "$attacks" =~ SYN_FLOOD ]]; then [ -z "$attacks" ] && attacks="SYN_FLOOD" || attacks="${attacks},SYN_FLOOD" fi # Progressive scoring based on connection count # 20-50 conns: +15 pts, 50-100: +25 pts, 100+: +40 pts local conn_bonus=0 if [ "$count" -ge 100 ]; then conn_bonus=40 elif [ "$count" -ge 50 ]; then conn_bonus=25 else conn_bonus=15 fi # Distributed attack severity bonus # Higher severity = more dangerous, boost scores case "$attack_severity" in 4) conn_bonus=$((conn_bonus + 25)) ;; # Critical DDoS 3) conn_bonus=$((conn_bonus + 15)) ;; # Severe DDoS 2) conn_bonus=$((conn_bonus + 10)) ;; # Major DDoS 1) conn_bonus=$((conn_bonus + 5)) ;; # Moderate DDoS esac # Attack momentum bonus (growing attack = more dangerous) if [ "$attack_momentum" -eq 2 ]; then conn_bonus=$((conn_bonus + 15)) # Rapidly accelerating elif [ "$attack_momentum" -eq 1 ]; then conn_bonus=$((conn_bonus + 8)) # Accelerating fi # Multi-vector attack detection: Check if IP also has HTTP attacks # This indicates sophisticated attacker (SYN flood + application layer) local multi_vector=0 if [ -f "$TEMP_DIR/ip_${ip//\./_}" ]; then local existing_attacks=$(grep -oP 'attacks=\K[^|]+' "$TEMP_DIR/ip_${ip//\./_}" 2>/dev/null || echo "") if [[ "$existing_attacks" =~ (SQLI|XSS|RCE|LFI|RFI|WEBSHELL) ]]; then multi_vector=1 conn_bonus=$((conn_bonus + 30)) # Multi-vector = very dangerous fi fi # Connection persistence bonus (repeated detections of same IP) # This indicates sustained attack vs transient spike if [ "${hits:-0}" -ge 5 ]; then conn_bonus=$((conn_bonus + 20)) # Persistent attacker elif [ "${hits:-0}" -ge 3 ]; then conn_bonus=$((conn_bonus + 10)) # Repeated attack fi # Connection escalation detection # Check if connection count is increasing (more aggressive attack) local prev_count="${CONNECTION_COUNT[$ip]:-0}" if [ "$count" -gt "$prev_count" ] && [ "$prev_count" -gt 0 ]; then local increase=$((count - prev_count)) if [ "$increase" -ge 50 ]; then conn_bonus=$((conn_bonus + 25)) # Rapidly escalating elif [ "$increase" -ge 20 ]; then conn_bonus=$((conn_bonus + 15)) # Escalating fi fi # Add HTTP attack pre-boost conn_bonus=$((conn_bonus + http_attack_bonus)) # Geographic clustering bonus local geo_bonus=0 if [ -f "$TEMP_DIR/threat_enrich_${ip//\./_}" ]; then local threat_data=$(cat "$TEMP_DIR/threat_enrich_${ip//\./_}") local ip_geo=$(echo "$threat_data" | cut -d'|' -f5) local ip_isp=$(echo "$threat_data" | cut -d'|' -f4) # Check if from hostile country (5+ attackers) if [ -n "$ip_geo" ] && grep -q "^${ip_geo}$" "$TEMP_DIR/hostile_countries" 2>/dev/null; then geo_bonus=$((geo_bonus + 10)) # Part of coordinated country-level attack fi # Check if from hostile ASN (3+ attackers) if [ -n "$ip_isp" ]; then local ip_asn=$(echo "$ip_isp" | grep -oP 'AS\K\d+' | head -1) if [ -n "$ip_asn" ] && grep -q "^${ip_asn}$" "$TEMP_DIR/hostile_asns" 2>/dev/null; then geo_bonus=$((geo_bonus + 15)) # Same botnet infrastructure fi fi fi conn_bonus=$((conn_bonus + geo_bonus)) # First hit or add to existing score if [ "${hits:-0}" -eq 1 ]; then score=$conn_bonus else score=$((score + conn_bonus)) 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 subnet_data=$(calculate_subnet_bonus "$ip") IFS='|' read -r subnet_count subnet_bonus subnet_reason <<< "$subnet_data" if [ "${subnet_bonus:-0}" -gt 0 ]; then score=$((score + subnet_bonus)) [ -n "$block_reasons" ] && block_reasons="${block_reasons}+" || block_reasons="" block_reasons="${block_reasons}${subnet_reason}" fi # Detect timing patterns local timing_result=$(detect_timing_pattern "$ip") IFS='|' read -r timing_type timing_bonus timing_reason <<< "$timing_result" if [ "$timing_bonus" -gt 0 ]; then score=$((score + timing_bonus)) [ -n "$block_reasons" ] && block_reasons="${block_reasons}+" || block_reasons="" block_reasons="${block_reasons}${timing_reason}" fi # Cap at 100 [ "$score" -gt 100 ] && score=100 # Write to file for main process echo "$score|$hits|$bot_type|$attacks|$ban_count|$rep_score" > "$ip_file" # Store block reasons for auto-mitigation if [ -n "$block_reasons" ]; then echo "$block_reasons" > "$TEMP_DIR/block_reason_${ip//\./_}" fi # Log to reputation DB flag_ip_attack "$ip" "SYN_FLOOD" 0 "SYN flood: $count connections" >/dev/null 2>&1 & # Log event with reputation score and attack intelligence local time_str=$(date +"%H:%M:%S") local level=$(get_threat_level "$score") local color=$(get_threat_color "$level") # Build intelligence summary local intel_tags="" [ "$attack_severity" -ge 1 ] && intel_tags="${intel_tags}DDoS:T${attack_severity} " [ "$attack_momentum" -ge 1 ] && intel_tags="${intel_tags}ACCEL " [ "$coordinated_attack" -eq 1 ] && intel_tags="${intel_tags}BOTNET " [ "$multi_vector" -eq 1 ] && intel_tags="${intel_tags}MULTI-VECTOR " [ "$http_attack_bonus" -gt 0 ] && intel_tags="${intel_tags}HTTP-ATTACKER " [ "$geo_bonus" -ge 15 ] && intel_tags="${intel_tags}HOSTILE-ASN " [ "$geo_bonus" -ge 10 ] && [ "$geo_bonus" -lt 15 ] && intel_tags="${intel_tags}HOSTILE-GEO " echo -e "${color}[${time_str}] $ip | Score:$score [$level] | 💥SYN_FLOOD | Conns:$count | ${intel_tags}${NC}" >> "$TEMP_DIR/recent_events" fi else # Reset alert if connections drop below threshold 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" # Validate score is numeric [ -z "$score" ] && score=0 [[ ! "$score" =~ ^[0-9]+$ ]] && score=0 # Skip if already blocked in this session [ -n "${BLOCKED_THIS_SESSION[$ip]}" ] && continue # INSTANT block at score 100 (MAXIMUM threat via IPset) if [ "${score:-0}" -ge 100 ]; then # Mark as blocked BLOCKED_THIS_SESSION[$ip]=1 # Instant IPset block local time_str=$(date +"%H:%M:%S") echo -e "${CRITICAL_COLOR}[${time_str}] INSTANT_BLOCK | $ip | Score:100 | ${attacks}${NC}" >> "$TEMP_DIR/recent_events" # Get detailed block reason local block_reason="INSTANT AUTO-BLOCK: Score=100 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 # Instant block via quick_block_ip (uses IPset for speed) quick_block_ip "$ip" "$block_reason" & continue fi # Auto-block at score >= 80 (CRITICAL) if [ "${score:-0}" -ge 80 ]; then # 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