548aabebe2
SECURITY ENHANCEMENT: Added IP format validation before calling CSF firewall commands to prevent potential command injection or invalid IP blocking attempts. CHANGES: - block_ip_temporary() - Added is_valid_ip() check before csf -td - block_ip_permanent() - Added is_valid_ip() check before csf -d - Both functions now return error if IP format is invalid IMPACT: Prevents invalid or malformed IPs from being passed to CSF commands, improving security and preventing potential firewall corruption.
2439 lines
94 KiB
Bash
Executable File
2439 lines
94 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))
|
|
|
|
# Validate IP format before blocking
|
|
if ! is_valid_ip "$ip"; then
|
|
echo "✗ Error: Invalid IP format: $ip"
|
|
return 1
|
|
fi
|
|
|
|
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}"
|
|
|
|
# Validate IP format before blocking
|
|
if ! is_valid_ip "$ip"; then
|
|
echo "✗ Error: Invalid IP format: $ip"
|
|
return 1
|
|
fi
|
|
|
|
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 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"
|
|
|
|
# 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 - 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
|
|
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
|