Files
Linux-Server-Management-Too…/modules/security/live-attack-monitor.sh
T
cschantz b7417a6bfa Fix live-attack-monitor auto-blocking and bot-analyzer compression
- live-attack-monitor.sh:
  * Remove snapshot loading (start fresh each session)
  * Fix Apache log monitoring to use tail -n 0 -F (only new entries)
  * Add IP file sync to main loop for auto-blocking to work
  * Fix IP_DATA consolidation for cross-process communication

- bot-analyzer.sh:
  * Implement gzip compression for large temp files (10-20x space savings)
  * Update all read/write operations to use compressed files
  * Fix for servers with 200+ domains and millions of log entries

- run.sh:
  * Add HISTFILE fallback to prevent crashes when sourced
2025-11-17 22:28:38 -05:00

2411 lines
93 KiB
Bash
Executable File

#!/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"
# 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
# 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"
# 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 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
# 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 -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 -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 -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 -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
local new_attacks=$(detect_all_attacks "$url" "$method")
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
attacks=$(echo "$attacks" | tr ',' '\n' | sort -u | tr '\n' ',' | sed 's/,$//')
# 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 -gt 100 ]; then
score=$((score + 5))
elif [ $hits -gt 50 ]; then
score=$((score + 3))
elif [ $hits -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 -lt 0 ] && score=0
;;
suspicious)
# Suspicious bots - increase score
score=$((score + 10))
;;
esac
# Cap at 100
[ $score -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
local check_score=$(echo "${IP_DATA[$check_ip]}" | cut -d'|' -f1)
[ "$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 -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 -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)
local count=$(echo "$timestamps" | tr ',' '\n' | wc -l)
if [ "$count" -gt 100 ]; then
timestamps=$(echo "$timestamps" | tr ',' '\n' | tail -100 | tr '\n' ',' | sed 's/,$//')
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}"
}
# 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))
if command -v csf &>/dev/null; then
echo "Blocking $ip for ${hours}h: $reason"
csf -td "$ip" "$seconds" "$reason" >/dev/null 2>&1
local result=$?
# Verify the block was successful (check twice to be sure)
sleep 0.5 # Give CSF a moment to apply the rule
if verify_ip_blocked "$ip"; then
# Double-check to ensure it's really blocked
sleep 0.3
if verify_ip_blocked "$ip"; then
echo "✓ Verified: $ip is now blocked"
# Increment blocks counter
local current_total=$(cat "$TEMP_DIR/total_blocks" 2>/dev/null || echo "0")
echo $((current_total + 1)) > "$TEMP_DIR/total_blocks"
# Trigger immediate cache refresh (don't wait for 10 second interval)
echo "Refreshing cache after blocking $ip..." >> "$TEMP_DIR/debug.log"
{
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
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
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
CACHE_COUNT=$(wc -l < "$TEMP_DIR/blocked_ips_cache" 2>/dev/null || echo 0)
echo "Cache refreshed: $CACHE_COUNT IPs total" >> "$TEMP_DIR/debug.log"
if grep -q "^$ip$" "$TEMP_DIR/blocked_ips_cache" 2>/dev/null; then
echo "$ip confirmed in cache" >> "$TEMP_DIR/debug.log"
else
echo "✗ WARNING: $ip NOT in cache after refresh!" >> "$TEMP_DIR/debug.log"
# Add it manually as fallback with file locking to prevent race conditions
(
flock -x 200
echo "$ip" >> "$TEMP_DIR/blocked_ips_cache"
sort -u "$TEMP_DIR/blocked_ips_cache" -o "$TEMP_DIR/blocked_ips_cache"
) 200>"$TEMP_DIR/cache.lock"
echo "$ip added manually to cache" >> "$TEMP_DIR/debug.log"
fi
return 0
fi
fi
echo "✗ Warning: Failed to verify block for $ip"
return 1
fi
echo "✗ Error: CSF not available"
return 1
}
# Block IP permanently with CSF
block_ip_permanent() {
local ip="$1"
local reason="${2:-Permanent block by live monitor}"
if command -v csf &>/dev/null; then
echo "Permanently blocking $ip: $reason"
csf -d "$ip" "$reason" >/dev/null 2>&1
local result=$?
# Verify the block was successful (check twice to be sure)
sleep 0.5 # Give CSF a moment to apply the rule
if verify_ip_blocked "$ip"; then
# Double-check to ensure it's really blocked
sleep 0.3
if verify_ip_blocked "$ip"; then
echo "✓ Verified: $ip is now permanently blocked"
# Increment blocks counter
local current_total=$(cat "$TEMP_DIR/total_blocks" 2>/dev/null || echo "0")
echo $((current_total + 1)) > "$TEMP_DIR/total_blocks"
# Trigger immediate cache refresh (don't wait for 10 second interval)
echo "Refreshing cache after permanently blocking $ip..." >> "$TEMP_DIR/debug.log"
{
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
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
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
CACHE_COUNT=$(wc -l < "$TEMP_DIR/blocked_ips_cache" 2>/dev/null || echo 0)
echo "Cache refreshed: $CACHE_COUNT IPs total" >> "$TEMP_DIR/debug.log"
if grep -q "^$ip$" "$TEMP_DIR/blocked_ips_cache" 2>/dev/null; then
echo "$ip confirmed in cache" >> "$TEMP_DIR/debug.log"
else
echo "✗ WARNING: $ip NOT in cache after refresh!" >> "$TEMP_DIR/debug.log"
# Add it manually as fallback with file locking to prevent race conditions
(
flock -x 200
echo "$ip" >> "$TEMP_DIR/blocked_ips_cache"
sort -u "$TEMP_DIR/blocked_ips_cache" -o "$TEMP_DIR/blocked_ips_cache"
) 200>"$TEMP_DIR/cache.lock"
echo "$ip added manually to cache" >> "$TEMP_DIR/debug.log"
fi
return 0
fi
fi
echo "✗ Warning: Failed to verify permanent block for $ip"
return 1
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)
local ip_list=""
local blocked_count=0
local displayed_count=0
for ip in "${!IP_DATA[@]}"; do
# Skip IPs that are already blocked
if is_ip_blocked "$ip" 2>/dev/null; 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
echo "$ip_list" | sort -t'|' -k1 -rn | head -10 | 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
if [ -n "$attacks" ]; then
# Show first attack type
local first_attack=$(echo "$attacks" | cut -d',' -f1)
local icon=$(get_attack_icon "$first_attack")
status_line+=" $icon$(echo "$attacks" | cut -d',' -f1)"
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() {
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}"
if [ -f "$TEMP_DIR/recent_events" ] && [ -s "$TEMP_DIR/recent_events" ]; then
tail -n "$MAX_DISPLAY_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
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
# Quick check - only verify if CSF/iptables commands available
# Don't check on every refresh (too slow)
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
echo -e "${HIGH_COLOR} ⚠️ DDoS/SYN Flood Detected - Firewall Protection Recommended${NC}"
echo -e "${MEDIUM_COLOR} → Enable SYNFLOOD protection: ${BOLD}csf -e SYNFLOOD${NC}"
echo -e "${MEDIUM_COLOR} → Optimize CT_LIMIT: ${BOLD}Press 'c' to run CT_LIMIT optimizer${NC}"
echo -e "${MEDIUM_COLOR} → Or manual: ${BOLD}modules/security/optimize-ct-limit.sh${NC}"
recommendations=1
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
echo -e "${HIGH_COLOR} ⚠️ SSH Bruteforce ($ssh_attacks attempts) - Strengthen SSH Security${NC}"
echo -e "${MEDIUM_COLOR} → Lower LF_SSHD trigger: ${BOLD}Edit /etc/csf/csf.conf → LF_SSHD=\"3\"${NC}"
echo -e "${MEDIUM_COLOR} → Enable PortKnocking or change SSH port${NC}"
recommendations=1
fi
fi
if [ $recommendations -eq 0 ]; then
echo ""
fi
echo -e "${INFO_COLOR} Keys: 'b' Block | 'c' CT_LIMIT | 's' Stats | 'r' Refresh | 'h' Help | 'q' Quit${NC}"
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 " 0) Cancel"
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 -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
}
################################################################################
# Log Monitoring
################################################################################
monitor_apache_logs() {
# Try multiple log locations
local log_files=()
# Set default if not defined by system-detect.sh
local LOG_DIR="${SYS_LOG_DIR:-/var/log/apache2/domlogs}"
# 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 (cPanel domlogs)
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" \) 2>/dev/null | head -5)
fi
if [ ${#log_files[@]} -eq 0 ]; then
echo "ERROR: No accessible Apache log files found" >> "$TEMP_DIR/recent_events"
echo "Checked: ${LOG_DIR}, /var/log/httpd, /var/log/apache2" >> "$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"
# 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
# This ensures we see everything interesting, not just high scores
if [ "$score" -gt 0 ] || [ -n "$attacks" ] || [ "$bot_type" = "suspicious" ]; then
local color=$(get_threat_color "$level")
local time_str=$(date +"%H:%M:%S")
# Build log line
local log_line="${color}[${time_str}] $ip"
log_line+=" | Score:$score [$level]"
# Show bot type if interesting
if [ "$bot_type" = "suspicious" ] || [ "$bot_type" = "ai" ]; then
log_line+=" | Bot:$bot_type"
fi
if [ -n "$attacks" ]; 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
if echo "$line" | grep -qi "Failed password\|authentication failure\|Invalid user"; then
# Extract IP address
local ip=$(echo "$line" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1)
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
fi
# Progressive scoring for bruteforce: Each attempt adds points
# First attempt: 10 pts, subsequent attempts: +8 pts each
if [ $hits -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 -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 (CSF, iptables, kernel blocks)
if echo "$line" | grep -qiE "Firewall|iptables.*DENY|iptables.*DROP|CSF.*block"; then
# Extract IP address
local ip=$(echo "$line" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1)
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)
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
if echo "$line" | grep -qiE "SYN flood|possible SYN flooding|TCP: Possible SYN flooding"; then
local ip=$(echo "$line" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1)
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
if echo "$line" | grep -qiE "port.*scan|stealth scan|SYN-FIN scan|NULL scan"; then
local ip=$(echo "$line" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1)
if [ -n "$ip" ]; then
# Skip local/private IPs
if [[ "$ip" =~ ^127\. ]] || \
[[ "$ip" =~ ^10\. ]] || \
[[ "$ip" =~ ^192\.168\. ]] || \
[[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[01])\. ]]; then
continue
fi
# Process as SCANNER attack
local current_data="${IP_DATA[$ip]:-0|0|human||0|0}"
IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$current_data"
# Add PORT_SCAN to attacks (using ADMIN_PROBE for now - 5 points)
if [[ ! "$attacks" =~ ADMIN_PROBE ]]; then
if [ -z "$attacks" ]; then
attacks="ADMIN_PROBE"
else
attacks="${attacks},ADMIN_PROBE"
fi
fi
# Calculate score
score=$(calculate_attack_score "$attacks")
hits=$((hits + 1))
# Update IP_DATA
IP_DATA[$ip]="$score|$hits|$bot_type|$attacks|$ban_count|$rep_score"
# Log event
local time_str=$(date +"%H:%M:%S")
local level=$(get_threat_level "$score")
local color=$(get_threat_color "$level")
echo -e "${color}[${time_str}] $ip | Score:$score [$level] | 🔎PORT_SCAN | Network reconnaissance${NC}" >> "$TEMP_DIR/recent_events"
fi
fi
done &
fi
# Monitor netstat for high connection counts (possible DDoS)
if command -v netstat &>/dev/null || command -v ss &>/dev/null; then
(
declare -A CONNECTION_COUNT
declare -A ALERT_SENT
while true; do
# Use ss if available (faster), otherwise netstat
if command -v ss &>/dev/null; then
# Count SYN_RECV connections per IP (sign of SYN flood)
while read -r ip count; do
if [ "$count" -gt 20 ]; then # More than 20 SYN_RECV connections
if [ -z "${ALERT_SENT[$ip]}" ]; then
ALERT_SENT[$ip]=1
# Skip local/private IPs
if [[ "$ip" =~ ^127\. ]] || \
[[ "$ip" =~ ^10\. ]] || \
[[ "$ip" =~ ^192\.168\. ]] || \
[[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[01])\. ]]; then
continue
fi
# Log high connection count
local time_str=$(date +"%H:%M:%S")
echo -e "${HIGH_COLOR}[${time_str}] $ip | 💥HIGH_CONN_COUNT | $count SYN_RECV connections (possible DDoS)${NC}" >> "$TEMP_DIR/recent_events"
fi
else
# Reset alert if connections drop
unset ALERT_SENT[$ip]
fi
done < <(ss -tn state syn-recv 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | sort | uniq -c | awk '$1 > 5 {print $2, $1}')
fi
sleep 15 # Check every 15 seconds
done
) &
fi
}
################################################################################
# Email/SMTP Attack Monitoring
################################################################################
monitor_email_attacks() {
# Monitor mail logs for SMTP/IMAP/POP3 bruteforce
local mail_log="/var/log/maillog"
if [ ! -f "$mail_log" ]; then
mail_log="/var/log/mail.log"
fi
if [ -f "$mail_log" ]; then
tail -n 0 -F "$mail_log" 2>/dev/null | while read -r line; do
# Dovecot authentication failures
if echo "$line" | grep -qiE "auth.*failed|authentication failed|password mismatch"; then
local ip=$(echo "$line" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1)
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 -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 -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
if echo "$line" | grep -qiE "FAIL LOGIN|authentication failed|530 Login incorrect"; then
local ip=$(echo "$line" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1)
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 -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 -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
if echo "$line" | grep -qiE "Access denied for user|Failed password for"; then
local ip=$(echo "$line" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1)
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 -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 -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
for attack_type in RCE SQL_INJECTION XSS PATH_TRAVERSAL BRUTEFORCE; do
local attack_count=$(echo "$recent" | grep -c "$attack_type")
if [ "$attack_count" -ge 5 ]; then
local unique_ips=$(echo "$recent" | grep "$attack_type" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | sort -u | wc -l)
if [ "$unique_ips" -ge 5 ]; then
# Distributed attack detected!
local time_str=$(date +"%H:%M:%S")
echo -e "${CRITICAL_COLOR}[${time_str}] DISTRIBUTED_ATTACK | ${attack_type} from ${unique_ips} IPs in last 2min | Possible botnet${NC}" >> "$TEMP_DIR/recent_events"
# Mark in a file for Quick Actions to see
echo "${attack_type}|${unique_ips}|$(date +%s)" >> "$TEMP_DIR/distributed_attacks"
fi
fi
done
fi
done
) &
}
################################################################################
# Automatic Mitigation Engine
################################################################################
auto_mitigation_engine() {
# Run in background, check every 10 seconds
(
# Track already blocked IPs in this session
declare -A BLOCKED_THIS_SESSION
while true; do
sleep 10
# Read current IP data from snapshot file (updated by main process)
if [ -f "$TEMP_DIR/ip_data" ]; then
while IFS='=' read -r ip data; do
[ -z "$ip" ] && continue
IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$data"
# Auto-block at score >= 80 (CRITICAL)
if [ "$score" -ge 80 ]; then
# Skip if already blocked in this session
[ -n "${BLOCKED_THIS_SESSION[$ip]}" ] && continue
# Mark as blocked to prevent duplicate attempts
BLOCKED_THIS_SESSION[$ip]=1
# Auto-block
local time_str=$(date +"%H:%M:%S")
echo -e "${CRITICAL_COLOR}[${time_str}] AUTO_BLOCK | $ip | Score:$score | ${attacks}${NC}" >> "$TEMP_DIR/recent_events"
# Get detailed block reason
local block_reason="Auto-block: Score=$score Attacks=${attacks}"
if [ -f "$TEMP_DIR/block_reason_${ip//\./_}" ]; then
local intel_reason=$(cat "$TEMP_DIR/block_reason_${ip//\./_}")
block_reason="${block_reason} Intel:${intel_reason}"
fi
# Block for 1 hour with detailed reason
block_ip_temporary "$ip" 1 "$block_reason" &
# Increment total blocks counter
local current_total=$(cat "$TEMP_DIR/total_blocks" 2>/dev/null || echo "0")
echo $((current_total + 1)) > "$TEMP_DIR/total_blocks"
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 (runs every 10 seconds for performance)
(
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
) &
# 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)
if ! echo "$basename_file" | grep -qE '^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)
ip=$(echo "$basename_file" | sed 's/^ip_//' | tr '_' '.')
data=$(cat "$ip_file" 2>/dev/null)
# Validate data format (should be score|hits|bot_type|attacks|ban_count|rep_score)
if [ -n "$data" ] && echo "$data" | grep -q '|'; 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)
# Run CT_LIMIT optimizer
clear
"$SCRIPT_DIR/modules/security/optimize-ct-limit.sh"
read -p "Press Enter to return to monitor..."
;;
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} - Run CT_LIMIT optimizer (analyze traffic & recommend limit)"
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