b7417a6bfa
- 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
2411 lines
93 KiB
Bash
Executable File
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
|