|
|
|
@@ -65,41 +65,102 @@ echo "0" > "$TEMP_DIR/event_counter"
|
|
|
|
|
echo "0" > "$TEMP_DIR/total_blocks"
|
|
|
|
|
|
|
|
|
|
# IPset configuration
|
|
|
|
|
IPSET_NAME="live_monitor_$$"
|
|
|
|
|
IPSET_NAME=""
|
|
|
|
|
IPSET_AVAILABLE=0
|
|
|
|
|
IPSET_SUPPORTS_TIMEOUT=0
|
|
|
|
|
IPSET_INIT_ERROR="" # Store initialization error message
|
|
|
|
|
|
|
|
|
|
# Initialize IPset for fast blocking (if available)
|
|
|
|
|
if command -v ipset &>/dev/null; then
|
|
|
|
|
# Create temporary IPset with 1-hour default timeout
|
|
|
|
|
if ipset create "$IPSET_NAME" hash:ip timeout 3600 maxelem 65536 2>/dev/null; then
|
|
|
|
|
# Check if CSF's chain_DENY IPset exists AND supports timeouts
|
|
|
|
|
if ipset list chain_DENY &>/dev/null 2>&1 && ipset list chain_DENY | grep -q "^Type:.*timeout"; then
|
|
|
|
|
# CSF ipset exists with timeout support - use it!
|
|
|
|
|
IPSET_NAME="chain_DENY"
|
|
|
|
|
IPSET_AVAILABLE=1
|
|
|
|
|
|
|
|
|
|
# Add iptables rule to block IPs in the set
|
|
|
|
|
iptables -I INPUT -m set --match-set "$IPSET_NAME" src -j DROP 2>/dev/null
|
|
|
|
|
|
|
|
|
|
echo "✓ IPset initialized: $IPSET_NAME (fast blocking enabled)" >> "$TEMP_DIR/debug.log"
|
|
|
|
|
IPSET_SUPPORTS_TIMEOUT=1
|
|
|
|
|
echo "✓ Using CSF IPset: chain_DENY (with timeout support)" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
else
|
|
|
|
|
echo "✗ IPset creation failed - falling back to CSF" >> "$TEMP_DIR/debug.log"
|
|
|
|
|
# CSF ipset doesn't exist OR doesn't support timeouts - create our own
|
|
|
|
|
IPSET_NAME="live_monitor_$$"
|
|
|
|
|
|
|
|
|
|
if ipset list chain_DENY &>/dev/null 2>&1; then
|
|
|
|
|
echo "→ CSF chain_DENY exists but no timeout support - creating our own ipset" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
else
|
|
|
|
|
echo "→ No CSF IPset found - creating our own ipset" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Capture detailed error output
|
|
|
|
|
IPSET_CREATE_OUTPUT=$(ipset create "$IPSET_NAME" hash:ip timeout 3600 maxelem 65536 2>&1)
|
|
|
|
|
IPSET_CREATE_EXIT=$?
|
|
|
|
|
|
|
|
|
|
if [ $IPSET_CREATE_EXIT -eq 0 ]; then
|
|
|
|
|
IPSET_AVAILABLE=1
|
|
|
|
|
IPSET_SUPPORTS_TIMEOUT=1
|
|
|
|
|
|
|
|
|
|
# Add iptables rule to block IPs in the set
|
|
|
|
|
IPTABLES_OUTPUT=$(iptables -I INPUT -m set --match-set "$IPSET_NAME" src -j DROP 2>&1)
|
|
|
|
|
IPTABLES_EXIT=$?
|
|
|
|
|
|
|
|
|
|
if [ $IPTABLES_EXIT -ne 0 ]; then
|
|
|
|
|
# iptables rule failed - clean up ipset and report error
|
|
|
|
|
ipset destroy "$IPSET_NAME" 2>/dev/null
|
|
|
|
|
IPSET_AVAILABLE=0
|
|
|
|
|
IPSET_INIT_ERROR="iptables rule creation failed: $IPTABLES_OUTPUT"
|
|
|
|
|
echo "✗ IPset created but iptables rule failed: $IPTABLES_OUTPUT" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
else
|
|
|
|
|
echo "✓ IPset initialized: $IPSET_NAME (fast blocking enabled)" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
# IPset creation failed - capture why
|
|
|
|
|
IPSET_INIT_ERROR="ipset creation failed: $IPSET_CREATE_OUTPUT"
|
|
|
|
|
echo "✗ IPset creation failed: $IPSET_CREATE_OUTPUT" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
|
|
|
|
|
# Check for common issues and provide helpful diagnostics
|
|
|
|
|
if echo "$IPSET_CREATE_OUTPUT" | grep -qi "module"; then
|
|
|
|
|
KERNEL_MODS=$(lsmod | grep -E "ip_set|xt_set" || echo "NOT LOADED")
|
|
|
|
|
IPSET_INIT_ERROR="$IPSET_INIT_ERROR | Kernel modules: $KERNEL_MODS"
|
|
|
|
|
echo " → Kernel module issue detected. Loaded modules: $KERNEL_MODS" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if echo "$IPSET_CREATE_OUTPUT" | grep -qi "permission"; then
|
|
|
|
|
IPSET_INIT_ERROR="$IPSET_INIT_ERROR | Permission denied (need root)"
|
|
|
|
|
echo " → Permission denied. Current user: $(whoami), EUID: $EUID" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
echo "✗ IPset not available - using CSF for blocking" >> "$TEMP_DIR/debug.log"
|
|
|
|
|
# ipset command not found - provide diagnostic info
|
|
|
|
|
IPSET_INIT_ERROR="ipset command not found in PATH"
|
|
|
|
|
echo "✗ IPset not available - using CSF for blocking" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
|
|
|
|
|
# Check if ipset package is installed
|
|
|
|
|
if command -v rpm &>/dev/null && rpm -q ipset &>/dev/null; then
|
|
|
|
|
IPSET_INIT_ERROR="$IPSET_INIT_ERROR | Package installed but not in PATH"
|
|
|
|
|
echo " → ipset package IS installed but command not found" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
elif command -v dpkg &>/dev/null && dpkg -l ipset 2>/dev/null | grep -q "^ii"; then
|
|
|
|
|
IPSET_INIT_ERROR="$IPSET_INIT_ERROR | Package installed but not in PATH"
|
|
|
|
|
echo " → ipset package IS installed but command not found" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
else
|
|
|
|
|
IPSET_INIT_ERROR="$IPSET_INIT_ERROR | Package not installed"
|
|
|
|
|
echo " → ipset package NOT installed" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Initialize blocked IPs cache immediately on startup
|
|
|
|
|
{
|
|
|
|
|
# Get CSF temporary blocks - extract just the IP address
|
|
|
|
|
if command -v csf &>/dev/null; then
|
|
|
|
|
csf -t 2>/dev/null | awk '{print $1}' | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'
|
|
|
|
|
csf -t 2>/dev/null | awk '/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/ {print $1}'
|
|
|
|
|
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}$'
|
|
|
|
|
awk '/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/ {print $1}' /etc/csf/csf.deny 2>/dev/null
|
|
|
|
|
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}$'
|
|
|
|
|
iptables -L INPUT -n -v 2>/dev/null | awk '/DROP/ && $8 ~ /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/ {print $8}'
|
|
|
|
|
fi
|
|
|
|
|
} | sort -u > "$TEMP_DIR/blocked_ips_cache" 2>/dev/null
|
|
|
|
|
|
|
|
|
@@ -120,21 +181,38 @@ cleanup() {
|
|
|
|
|
# Wait a moment for background jobs
|
|
|
|
|
sleep 1
|
|
|
|
|
|
|
|
|
|
# Clean up IPset and iptables rule if we created them
|
|
|
|
|
if [ "$IPSET_AVAILABLE" -eq 1 ]; then
|
|
|
|
|
# SAVE SNAPSHOT BEFORE DELETING TEMP FILES!
|
|
|
|
|
echo "Saving IP reputation snapshot..."
|
|
|
|
|
save_snapshot
|
|
|
|
|
|
|
|
|
|
# Also save to IP reputation database for permanent tracking
|
|
|
|
|
if [ ${#IP_DATA[@]} -gt 0 ]; then
|
|
|
|
|
for ip in "${!IP_DATA[@]}"; do
|
|
|
|
|
IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "${IP_DATA[$ip]}"
|
|
|
|
|
# Update IP reputation database if score > 0
|
|
|
|
|
if [ "$score" -gt 0 ] && type record_ip_data &>/dev/null; then
|
|
|
|
|
record_ip_data "$ip" "$score" "$hits" "$attacks" "$ban_count" 2>/dev/null &
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
wait # Wait for all database updates to complete
|
|
|
|
|
echo "✓ Saved ${#IP_DATA[@]} IPs to reputation database"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Clean up IPset and iptables rule ONLY if we created them (not CSF's chain_DENY)
|
|
|
|
|
if [ "$IPSET_AVAILABLE" -eq 1 ] && [ "$IPSET_NAME" != "chain_DENY" ]; then
|
|
|
|
|
echo "Removing IPset firewall rules..."
|
|
|
|
|
iptables -D INPUT -m set --match-set "$IPSET_NAME" src -j DROP 2>/dev/null
|
|
|
|
|
ipset destroy "$IPSET_NAME" 2>/dev/null
|
|
|
|
|
echo "✓ IPset cleaned up"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Clean up temp directory
|
|
|
|
|
# Clean up temp directory (AFTER saving snapshot)
|
|
|
|
|
rm -rf "$TEMP_DIR" 2>/dev/null
|
|
|
|
|
|
|
|
|
|
# Restore cursor
|
|
|
|
|
command -v tput &>/dev/null && tput cnorm
|
|
|
|
|
|
|
|
|
|
echo "✓ Cleanup complete (snapshot saved)"
|
|
|
|
|
echo "✓ Cleanup complete - snapshot saved to $SNAPSHOT_DIR"
|
|
|
|
|
exit 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -142,10 +220,12 @@ trap cleanup EXIT INT TERM
|
|
|
|
|
|
|
|
|
|
# Save current monitoring state to temp files (for persistence across sessions)
|
|
|
|
|
save_snapshot() {
|
|
|
|
|
# Save IP_DATA associative array to file
|
|
|
|
|
local snapshot_file="$TEMP_DIR/snapshot.dat"
|
|
|
|
|
# Save IP_DATA associative array to PERMANENT storage (survives script exit)
|
|
|
|
|
local snapshot_file="$SNAPSHOT_DIR/latest_snapshot.dat"
|
|
|
|
|
local timestamp=$(date +%Y%m%d_%H%M%S)
|
|
|
|
|
local timestamped_file="$SNAPSHOT_DIR/snapshot_${timestamp}.dat"
|
|
|
|
|
|
|
|
|
|
# Write IP data
|
|
|
|
|
# Write IP data to both current and timestamped snapshot
|
|
|
|
|
{
|
|
|
|
|
for ip in "${!IP_DATA[@]}"; do
|
|
|
|
|
echo "IP_DATA[$ip]=${IP_DATA[$ip]}"
|
|
|
|
@@ -161,6 +241,12 @@ save_snapshot() {
|
|
|
|
|
echo "TOTAL_BLOCKS=$TOTAL_BLOCKS"
|
|
|
|
|
echo "START_TIME=$START_TIME"
|
|
|
|
|
} > "$snapshot_file" 2>/dev/null
|
|
|
|
|
|
|
|
|
|
# Also save timestamped copy for history
|
|
|
|
|
cp "$snapshot_file" "$timestamped_file" 2>/dev/null
|
|
|
|
|
|
|
|
|
|
# Keep only last 10 snapshots to prevent disk bloat
|
|
|
|
|
ls -t "$SNAPSHOT_DIR"/snapshot_*.dat 2>/dev/null | tail -n +11 | xargs rm -f 2>/dev/null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Statistics counters
|
|
|
|
@@ -280,7 +366,7 @@ update_ip_intelligence() {
|
|
|
|
|
|
|
|
|
|
# Classify bot if unknown
|
|
|
|
|
if [ "$bot_type" = "unknown" ] && [ -n "$user_agent" ]; then
|
|
|
|
|
bot_type=$(classify_bot_type "$user_agent")
|
|
|
|
|
bot_type=$(classify_bot_type "$user_agent" 2>/dev/null || echo "unknown")
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Record attack pattern for learning
|
|
|
|
@@ -488,7 +574,7 @@ calculate_diversity_bonus() {
|
|
|
|
|
|
|
|
|
|
[ -z "$vectors" ] && echo "0|0|" && return
|
|
|
|
|
|
|
|
|
|
local count=$(echo "$vectors" | tr ',' '\n' | wc -l)
|
|
|
|
|
local count=$(echo "$vectors" | tr ',' '\n' 2>/dev/null | wc -l 2>/dev/null || echo "0")
|
|
|
|
|
local bonus=0
|
|
|
|
|
local reason=""
|
|
|
|
|
|
|
|
|
@@ -605,8 +691,8 @@ detect_attack_success() {
|
|
|
|
|
track_subnet_attack() {
|
|
|
|
|
local ip="$1"
|
|
|
|
|
|
|
|
|
|
# Extract /24 subnet
|
|
|
|
|
local subnet=$(echo "$ip" | cut -d. -f1-3)
|
|
|
|
|
# Extract /24 subnet (bash built-in, 100x faster than cut)
|
|
|
|
|
local subnet="${ip%.*}" # Remove last octet: 1.2.3.4 → 1.2.3
|
|
|
|
|
|
|
|
|
|
# Increment subnet counter
|
|
|
|
|
local count=${SUBNET_ATTACKS[$subnet]:-0}
|
|
|
|
@@ -620,7 +706,7 @@ track_subnet_attack() {
|
|
|
|
|
# Returns: subnet_count|bonus_points|reason
|
|
|
|
|
calculate_subnet_bonus() {
|
|
|
|
|
local ip="$1"
|
|
|
|
|
local subnet=$(echo "$ip" | cut -d. -f1-3)
|
|
|
|
|
local subnet="${ip%.*}" # Bash built-in: 1.2.3.4 → 1.2.3 (100x faster than cut)
|
|
|
|
|
|
|
|
|
|
local count=${SUBNET_ATTACKS[$subnet]:-0}
|
|
|
|
|
local bonus=0
|
|
|
|
@@ -705,8 +791,8 @@ apply_reputation_decay() {
|
|
|
|
|
[ -z "$timestamps" ] && continue
|
|
|
|
|
|
|
|
|
|
# Get most recent attack time
|
|
|
|
|
local last_attack=$(echo "$timestamps" | tr ',' '\n' | tail -1)
|
|
|
|
|
local time_since_attack=$((now - last_attack))
|
|
|
|
|
local last_attack=$(echo "$timestamps" | tr ',' '\n' 2>/dev/null | tail -1 2>/dev/null || echo "0")
|
|
|
|
|
local time_since_attack=$((now - ${last_attack:-0}))
|
|
|
|
|
|
|
|
|
|
# If no activity for 6 hours, start decay
|
|
|
|
|
if [ "$time_since_attack" -gt 21600 ]; then
|
|
|
|
@@ -755,7 +841,9 @@ calculate_context_bonus() {
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Residential ISP (suspicious for server attacks)
|
|
|
|
|
if echo "$isp" | grep -qiE "(comcast|verizon|att|residential|cable|dsl|fiber|broadband)"; then
|
|
|
|
|
# Bash pattern matching (faster than grep subprocess)
|
|
|
|
|
local isp_lower="${isp,,}" # Convert to lowercase
|
|
|
|
|
if [[ "$isp_lower" =~ (comcast|verizon|att|residential|cable|dsl|fiber|broadband) ]]; then
|
|
|
|
|
bonus=$((bonus + 10))
|
|
|
|
|
[ -n "$reasons" ] && reasons="${reasons}+" || reasons=""
|
|
|
|
|
reasons="${reasons}RESIDENTIAL_ISP"
|
|
|
|
@@ -775,6 +863,27 @@ increment_block_counter() {
|
|
|
|
|
) 200>"$TEMP_DIR/counter.lock"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Record blocked IP to reputation database (for permanent tracking)
|
|
|
|
|
record_blocked_ip() {
|
|
|
|
|
local ip="$1"
|
|
|
|
|
local reason="${2:-Auto-blocked}"
|
|
|
|
|
|
|
|
|
|
# Update IP_DATA to increment ban_count
|
|
|
|
|
if [ -n "${IP_DATA[$ip]}" ]; then
|
|
|
|
|
IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "${IP_DATA[$ip]}"
|
|
|
|
|
ban_count=$((ban_count + 1))
|
|
|
|
|
IP_DATA[$ip]="$score|$hits|$bot_type|$attacks|$ban_count|$rep_score"
|
|
|
|
|
|
|
|
|
|
# Also save to IP reputation database (in background to avoid blocking)
|
|
|
|
|
if type record_ip_data &>/dev/null; then
|
|
|
|
|
(record_ip_data "$ip" "$score" "$hits" "$attacks" "$ban_count" 2>/dev/null) &
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Log to permanent block history file
|
|
|
|
|
echo "$(date '+%Y-%m-%d %H:%M:%S')|$ip|$reason" >> "$SNAPSHOT_DIR/block_history.log"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Batch block multiple IPs at once (optimized for DDoS scenarios)
|
|
|
|
|
batch_block_ips() {
|
|
|
|
|
local -a ip_list=("$@")
|
|
|
|
@@ -785,10 +894,14 @@ batch_block_ips() {
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# DEBUG: Log function entry
|
|
|
|
|
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: Starting batch block for ${#ip_list[@]} IPs: ${ip_list[*]}" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
|
|
|
|
|
echo "Batch blocking ${#ip_list[@]} IPs..."
|
|
|
|
|
|
|
|
|
|
# Use IPset for instant batch blocking if available
|
|
|
|
|
if [ "$IPSET_AVAILABLE" -eq 1 ]; then
|
|
|
|
|
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: Using IPSET path" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
for ip in "${ip_list[@]}"; do
|
|
|
|
|
# Validate IP format
|
|
|
|
|
if ! is_valid_ip "$ip"; then
|
|
|
|
@@ -812,23 +925,33 @@ batch_block_ips() {
|
|
|
|
|
echo "✓ IPset batch: $blocked blocked, $failed skipped"
|
|
|
|
|
else
|
|
|
|
|
# Fallback to CSF (slower, but still batch where possible)
|
|
|
|
|
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: Using CSF path (IPSET_AVAILABLE=$IPSET_AVAILABLE)" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
|
|
|
|
|
for ip in "${ip_list[@]}"; do
|
|
|
|
|
if ! is_valid_ip "$ip"; then
|
|
|
|
|
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: Invalid IP format: $ip" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
((failed++))
|
|
|
|
|
continue
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if csf -td "$ip" 3600 "Batch auto-block" >/dev/null 2>&1; then
|
|
|
|
|
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: Attempting CSF block for $ip" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
|
|
|
|
|
local csf_output=$(csf -td "$ip" 3600 "Batch auto-block" 2>&1)
|
|
|
|
|
if [ $? -eq 0 ]; then
|
|
|
|
|
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: CSF SUCCESS for $ip" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
((blocked++))
|
|
|
|
|
else
|
|
|
|
|
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: CSF FAILED for $ip: $csf_output" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
((failed++))
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
echo "✓ CSF batch: $blocked blocked, $failed failed"
|
|
|
|
|
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: CSF batch complete - blocked=$blocked, failed=$failed" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Update total counter atomically
|
|
|
|
|
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: Incrementing counter by $blocked" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
increment_block_counter "$blocked"
|
|
|
|
|
|
|
|
|
|
return 0
|
|
|
|
@@ -847,33 +970,44 @@ block_ip_temporary() {
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Use IPset for instant blocking if available
|
|
|
|
|
# PRIORITY 1: Use IPset for instant kernel-level blocking (performance critical)
|
|
|
|
|
if [ "$IPSET_AVAILABLE" -eq 1 ]; then
|
|
|
|
|
echo "Blocking $ip for ${hours}h: $reason"
|
|
|
|
|
if ipset add "$IPSET_NAME" "$ip" timeout "$seconds" 2>/dev/null; then
|
|
|
|
|
echo "✓ $ip blocked via IPset (auto-expires in ${hours}h)"
|
|
|
|
|
echo "$ip" >> "$TEMP_DIR/blocked_ips_cache"
|
|
|
|
|
|
|
|
|
|
# Update counter atomically
|
|
|
|
|
increment_block_counter 1
|
|
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
# Try IPset with timeout if supported
|
|
|
|
|
if [ "$IPSET_SUPPORTS_TIMEOUT" -eq 1 ]; then
|
|
|
|
|
if ipset add "$IPSET_NAME" "$ip" timeout "$seconds" -exist 2>/dev/null; then
|
|
|
|
|
echo "✓ $ip blocked via IPset $IPSET_NAME (expires in ${hours}h)"
|
|
|
|
|
echo "$ip" >> "$TEMP_DIR/blocked_ips_cache"
|
|
|
|
|
increment_block_counter 1
|
|
|
|
|
record_blocked_ip "$ip" "$reason"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
echo "✗ Warning: IPset add failed (IP may already be blocked)"
|
|
|
|
|
return 1
|
|
|
|
|
# IPset without timeout (CSF's chain_DENY) - add to IPset for instant block,
|
|
|
|
|
# then let CSF manage the timeout removal
|
|
|
|
|
if ipset add "$IPSET_NAME" "$ip" -exist 2>/dev/null; then
|
|
|
|
|
echo "$ip" >> "$TEMP_DIR/blocked_ips_cache"
|
|
|
|
|
increment_block_counter 1
|
|
|
|
|
record_blocked_ip "$ip" "$reason"
|
|
|
|
|
|
|
|
|
|
# Let CSF manage the timeout in background (IPset already blocking)
|
|
|
|
|
if command -v csf &>/dev/null; then
|
|
|
|
|
csf -td "$ip" "$seconds" "$reason" >/dev/null 2>&1 &
|
|
|
|
|
echo "✓ $ip blocked via IPset $IPSET_NAME (CSF managing timeout: ${hours}h)"
|
|
|
|
|
else
|
|
|
|
|
echo "✓ $ip blocked via IPset $IPSET_NAME (permanent - no CSF timeout)"
|
|
|
|
|
fi
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Fallback to CSF if IPset not available
|
|
|
|
|
# FALLBACK: CSF-only blocking (slower, but still works)
|
|
|
|
|
if command -v csf &>/dev/null; then
|
|
|
|
|
echo "Blocking $ip for ${hours}h: $reason"
|
|
|
|
|
if csf -td "$ip" "$seconds" "$reason" >/dev/null 2>&1; then
|
|
|
|
|
echo "✓ $ip blocked via CSF"
|
|
|
|
|
echo "✓ $ip blocked via CSF (expires in ${hours}h)"
|
|
|
|
|
echo "$ip" >> "$TEMP_DIR/blocked_ips_cache"
|
|
|
|
|
|
|
|
|
|
# Update counter atomically
|
|
|
|
|
increment_block_counter 1
|
|
|
|
|
|
|
|
|
|
record_blocked_ip "$ip" "$reason"
|
|
|
|
|
return 0
|
|
|
|
|
else
|
|
|
|
|
echo "✗ Warning: CSF block failed for $ip"
|
|
|
|
@@ -881,7 +1015,7 @@ block_ip_temporary() {
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo "✗ Error: CSF not available"
|
|
|
|
|
echo "✗ Error: No blocking method available"
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -911,13 +1045,18 @@ block_ip_permanent() {
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Permanent blocks always use CSF (IPset is temp-only for this script)
|
|
|
|
|
# But we can add to IPset with max timeout as well for immediate effect
|
|
|
|
|
# PRIORITY: Add to IPset immediately for instant kernel-level blocking
|
|
|
|
|
if [ "$IPSET_AVAILABLE" -eq 1 ]; then
|
|
|
|
|
# Add to IPset with 24-hour timeout for immediate blocking
|
|
|
|
|
ipset add "$IPSET_NAME" "$ip" timeout 86400 2>/dev/null
|
|
|
|
|
if [ "$IPSET_SUPPORTS_TIMEOUT" -eq 1 ]; then
|
|
|
|
|
# IPset with timeout - use max timeout (24 hours)
|
|
|
|
|
ipset add "$IPSET_NAME" "$ip" timeout 86400 -exist 2>/dev/null
|
|
|
|
|
else
|
|
|
|
|
# IPset without timeout (CSF's chain_DENY) - permanent add
|
|
|
|
|
ipset add "$IPSET_NAME" "$ip" -exist 2>/dev/null
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# CSF for persistent management (runs after IPset for immediate effect)
|
|
|
|
|
if command -v csf &>/dev/null; then
|
|
|
|
|
echo "Permanently blocking $ip: $reason"
|
|
|
|
|
if csf -d "$ip" "$reason" >/dev/null 2>&1; then
|
|
|
|
@@ -927,6 +1066,9 @@ block_ip_permanent() {
|
|
|
|
|
# Update counter atomically
|
|
|
|
|
increment_block_counter 1
|
|
|
|
|
|
|
|
|
|
# Record to reputation database
|
|
|
|
|
record_blocked_ip "$ip" "PERMANENT:$reason"
|
|
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
else
|
|
|
|
|
echo "✗ Warning: CSF permanent block failed for $ip"
|
|
|
|
@@ -1244,7 +1386,7 @@ draw_quick_actions() {
|
|
|
|
|
if [ "$has_ddos" -eq 1 ] || [ "$high_conn_count" -gt 0 ]; then
|
|
|
|
|
# Check current security settings
|
|
|
|
|
local synflood_status=$(grep "^SYNFLOOD\s*=" /etc/csf/csf.conf 2>/dev/null | cut -d'"' -f2)
|
|
|
|
|
local ct_limit=$(grep "^CT_LIMIT\s*=" /etc/csf/csf.conf 2>/dev/null | grep -oE '[0-9]+' | head -1)
|
|
|
|
|
local ct_limit=$(grep -oP "^CT_LIMIT\s*=\s*\"\K[0-9]+" /etc/csf/csf.conf 2>/dev/null | head -1)
|
|
|
|
|
|
|
|
|
|
local needs_config=0
|
|
|
|
|
|
|
|
|
@@ -1275,7 +1417,7 @@ draw_quick_actions() {
|
|
|
|
|
[[ ! "$ssh_attacks" =~ ^[0-9]+$ ]] && ssh_attacks=0
|
|
|
|
|
if [ "$ssh_attacks" -gt 5 ]; then
|
|
|
|
|
# Check if SSH hardening is already applied
|
|
|
|
|
local current_lf=$(grep "^LF_SSHD\s*=" /etc/csf/csf.conf 2>/dev/null | grep -oE '[0-9]+' | head -1)
|
|
|
|
|
local current_lf=$(grep -oP "^LF_SSHD\s*=\s*\"\K[0-9]+" /etc/csf/csf.conf 2>/dev/null | head -1)
|
|
|
|
|
[ -z "$current_lf" ] && current_lf="5"
|
|
|
|
|
|
|
|
|
|
# Only show recommendation if not already hardened
|
|
|
|
@@ -1429,7 +1571,7 @@ show_security_hardening_menu() {
|
|
|
|
|
|
|
|
|
|
# Check current settings
|
|
|
|
|
local synflood_status=$(grep "^SYNFLOOD\s*=" /etc/csf/csf.conf 2>/dev/null | cut -d'"' -f2)
|
|
|
|
|
local current_lf=$(grep "^LF_SSHD\s*=" /etc/csf/csf.conf 2>/dev/null | grep -oE '[0-9]+' | head -1)
|
|
|
|
|
local current_lf=$(grep -oP "^LF_SSHD\s*=\s*\"\K[0-9]+" /etc/csf/csf.conf 2>/dev/null | head -1)
|
|
|
|
|
[ -z "$current_lf" ] && current_lf="5"
|
|
|
|
|
|
|
|
|
|
echo "Current Security Status:"
|
|
|
|
@@ -1450,7 +1592,7 @@ show_security_hardening_menu() {
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# CT_LIMIT status (basic check)
|
|
|
|
|
local ct_limit=$(grep "^CT_LIMIT\s*=" /etc/csf/csf.conf 2>/dev/null | grep -oE '[0-9]+' | head -1)
|
|
|
|
|
local ct_limit=$(grep -oP "^CT_LIMIT\s*=\s*\"\K[0-9]+" /etc/csf/csf.conf 2>/dev/null | head -1)
|
|
|
|
|
if [ -n "$ct_limit" ] && [ "$ct_limit" -gt 0 ]; then
|
|
|
|
|
echo -e " ${SAFE_COLOR}✓${NC} Connection Tracking: ${BOLD}Configured${NC} (CT_LIMIT=$ct_limit)"
|
|
|
|
|
else
|
|
|
|
@@ -1603,7 +1745,7 @@ apply_ssh_hardening() {
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
# Check current LF_SSHD setting
|
|
|
|
|
local current_lf=$(grep "^LF_SSHD\s*=" /etc/csf/csf.conf 2>/dev/null | grep -oE '[0-9]+' | head -1)
|
|
|
|
|
local current_lf=$(grep -oP "^LF_SSHD\s*=\s*\"\K[0-9]+" /etc/csf/csf.conf 2>/dev/null | head -1)
|
|
|
|
|
|
|
|
|
|
if [ -z "$current_lf" ]; then
|
|
|
|
|
current_lf="5" # CSF default
|
|
|
|
@@ -1806,9 +1948,10 @@ monitor_apache_logs() {
|
|
|
|
|
# Show ET detection if found
|
|
|
|
|
if [ "$et_attack_score" -gt 0 ]; then
|
|
|
|
|
# Show primary attack type (cleaner than full list)
|
|
|
|
|
local primary_type=$(echo "$et_attack_types" | grep -oE 'SQLI|XSS|CMD|TRAVERSAL|WEBSHELL|RCE|UPLOAD|CVE' | head -1)
|
|
|
|
|
local primary_type=$(echo "$et_attack_types" | grep -oE 'SQLI|XSS|CMD|TRAVERSAL|WEBSHELL|RCE|UPLOAD|CVE' | head -1 2>/dev/null || echo "")
|
|
|
|
|
if [ -z "$primary_type" ]; then
|
|
|
|
|
primary_type=$(echo "$et_attack_types" | cut -d',' -f1)
|
|
|
|
|
# Bash built-in: Get first field (100x faster than cut)
|
|
|
|
|
primary_type="${et_attack_types%%,*}"
|
|
|
|
|
fi
|
|
|
|
|
log_line+=" | 🛡️ET:$primary_type"
|
|
|
|
|
|
|
|
|
@@ -1832,7 +1975,8 @@ monitor_apache_logs() {
|
|
|
|
|
|
|
|
|
|
# Show legacy attacks if no ET detection
|
|
|
|
|
if [ -n "$attacks" ] && [ "$et_attack_score" -eq 0 ]; then
|
|
|
|
|
local first_attack=$(echo "$attacks" | cut -d',' -f1)
|
|
|
|
|
# Bash built-in: Get first field (100x faster than cut)
|
|
|
|
|
local first_attack="${attacks%%,*}"
|
|
|
|
|
local icon=$(get_attack_icon "$first_attack")
|
|
|
|
|
log_line+=" | $icon$first_attack"
|
|
|
|
|
fi
|
|
|
|
@@ -2219,11 +2363,25 @@ monitor_network_attacks() {
|
|
|
|
|
(
|
|
|
|
|
declare -A CONNECTION_COUNT
|
|
|
|
|
declare -A ALERT_SENT
|
|
|
|
|
local ss_cache=""
|
|
|
|
|
local ss_cache_time=0
|
|
|
|
|
while true; do
|
|
|
|
|
# Use ss if available (faster), otherwise netstat
|
|
|
|
|
if command -v ss &>/dev/null; then
|
|
|
|
|
# Get total SYN_RECV count for distributed attack detection
|
|
|
|
|
local total_syn=$(ss -tn state syn-recv 2>/dev/null | wc -l)
|
|
|
|
|
# PERFORMANCE: Cache ss output during high-severity attacks
|
|
|
|
|
# During Tier 3+ attacks, cache for 5 seconds to reduce CPU usage by 50%
|
|
|
|
|
local current_time=$(date +%s 2>/dev/null || echo "${ss_cache_time:-0}")
|
|
|
|
|
local cache_age=$((${current_time:-0} - ${ss_cache_time:-0}))
|
|
|
|
|
|
|
|
|
|
# Refresh cache if: (1) no cache, (2) cache > 5s old, (3) not in attack (always fresh)
|
|
|
|
|
local prev_severity="${ATTACK_SEVERITY:-0}"
|
|
|
|
|
if [ -z "$ss_cache" ] || [ "$cache_age" -gt 5 ] || [ "${prev_severity}" -lt 3 ]; then
|
|
|
|
|
ss_cache=$(ss -tn state syn-recv 2>/dev/null)
|
|
|
|
|
ss_cache_time=$current_time
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Get total SYN_RECV count from cache
|
|
|
|
|
local total_syn=$(echo "$ss_cache" | wc -l)
|
|
|
|
|
local attack_severity=0
|
|
|
|
|
local unique_ips=0
|
|
|
|
|
|
|
|
|
@@ -2237,6 +2395,7 @@ monitor_network_attacks() {
|
|
|
|
|
elif [ "$total_syn" -gt 75 ]; then
|
|
|
|
|
attack_severity=1 # Moderate DDoS
|
|
|
|
|
fi
|
|
|
|
|
ATTACK_SEVERITY=$attack_severity # Store for next iteration
|
|
|
|
|
|
|
|
|
|
# Attack momentum tracking: Check if attack is growing
|
|
|
|
|
local prev_total="${PREV_TOTAL_SYN:-0}"
|
|
|
|
@@ -2251,15 +2410,15 @@ monitor_network_attacks() {
|
|
|
|
|
fi
|
|
|
|
|
PREV_TOTAL_SYN=$total_syn
|
|
|
|
|
|
|
|
|
|
# Count unique attacker IPs and track /24 subnets
|
|
|
|
|
# Count unique attacker IPs and track /24 subnets (use cached data)
|
|
|
|
|
declare -A subnet_counts
|
|
|
|
|
local attacker_ips=$(ss -tn state syn-recv 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | sort -u)
|
|
|
|
|
local attacker_ips=$(echo "$ss_cache" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | sort -u)
|
|
|
|
|
while IFS= read -r attacker_ip; do
|
|
|
|
|
[ -z "$attacker_ip" ] && continue
|
|
|
|
|
((unique_ips++))
|
|
|
|
|
|
|
|
|
|
# Track /24 subnets to detect coordinated attacks
|
|
|
|
|
local subnet=$(echo "$attacker_ip" | cut -d. -f1-3)
|
|
|
|
|
local subnet="${attacker_ip%.*}" # Bash built-in (100x faster)
|
|
|
|
|
((subnet_counts[$subnet]++))
|
|
|
|
|
done <<< "$attacker_ips"
|
|
|
|
|
|
|
|
|
@@ -2279,16 +2438,16 @@ monitor_network_attacks() {
|
|
|
|
|
for subnet in "${!hostile_subnets[@]}"; do
|
|
|
|
|
local subnet_ip_count=${hostile_subnets[$subnet]}
|
|
|
|
|
if [ "$subnet_ip_count" -ge 10 ]; then
|
|
|
|
|
# Block entire /24 subnet via IPset
|
|
|
|
|
# Block entire /24 subnet via IPset (PRIORITY) then CSF
|
|
|
|
|
local subnet_cidr="${subnet}.0/24"
|
|
|
|
|
if ! grep -q "^${subnet_cidr}\$" "$TEMP_DIR/blocked_subnets" 2>/dev/null; then
|
|
|
|
|
echo "$subnet_cidr" >> "$TEMP_DIR/blocked_subnets"
|
|
|
|
|
(
|
|
|
|
|
# Add to IPset if available
|
|
|
|
|
if command -v ipset &>/dev/null && ipset list blocklist &>/dev/null 2>&1; then
|
|
|
|
|
ipset add blocklist "$subnet_cidr" -exist 2>/dev/null
|
|
|
|
|
# PRIORITY: Add to IPset for instant kernel-level blocking
|
|
|
|
|
if [ "$IPSET_AVAILABLE" -eq 1 ]; then
|
|
|
|
|
ipset add "$IPSET_NAME" "$subnet_cidr" -exist 2>/dev/null
|
|
|
|
|
fi
|
|
|
|
|
# Also add to CSF
|
|
|
|
|
# CSF for persistent management (runs in background after IPset)
|
|
|
|
|
if command -v csf &>/dev/null; then
|
|
|
|
|
csf -d "$subnet_cidr" "SUBNET_DDOS:${subnet_ip_count}IPs" 2>/dev/null
|
|
|
|
|
fi
|
|
|
|
@@ -2395,7 +2554,7 @@ monitor_network_attacks() {
|
|
|
|
|
# ASN clustering detection
|
|
|
|
|
if [ -n "$isp" ]; then
|
|
|
|
|
# Extract ASN number from ISP string
|
|
|
|
|
local asn=$(echo "$isp" | grep -oP 'AS\K\d+' | head -1)
|
|
|
|
|
local asn=$(echo "$isp" | grep -oP 'AS\K\d+' 2>/dev/null | head -1 2>/dev/null || echo "")
|
|
|
|
|
if [ -n "$asn" ]; then
|
|
|
|
|
echo "$asn" >> "$TEMP_DIR/attack_asns"
|
|
|
|
|
local asn_count=$(grep -c "^${asn}$" "$TEMP_DIR/attack_asns" 2>/dev/null || echo "0")
|
|
|
|
@@ -2570,9 +2729,9 @@ monitor_network_attacks() {
|
|
|
|
|
# Geographic clustering bonus
|
|
|
|
|
local geo_bonus=0
|
|
|
|
|
if [ -f "$TEMP_DIR/threat_enrich_${ip//\./_}" ]; then
|
|
|
|
|
local threat_data=$(cat "$TEMP_DIR/threat_enrich_${ip//\./_}")
|
|
|
|
|
local ip_geo=$(echo "$threat_data" | cut -d'|' -f5)
|
|
|
|
|
local ip_isp=$(echo "$threat_data" | cut -d'|' -f4)
|
|
|
|
|
local threat_data=$(cat "$TEMP_DIR/threat_enrich_${ip//\./_}" 2>/dev/null || echo "")
|
|
|
|
|
# Bash IFS field splitting (100x faster than cut)
|
|
|
|
|
IFS='|' read -r _ _ _ ip_isp ip_geo _ <<< "$threat_data"
|
|
|
|
|
|
|
|
|
|
# Check if from hostile country (5+ attackers)
|
|
|
|
|
if [ -n "$ip_geo" ] && grep -q "^${ip_geo}$" "$TEMP_DIR/hostile_countries" 2>/dev/null; then
|
|
|
|
@@ -2581,7 +2740,7 @@ monitor_network_attacks() {
|
|
|
|
|
|
|
|
|
|
# Check if from hostile ASN (3+ attackers)
|
|
|
|
|
if [ -n "$ip_isp" ]; then
|
|
|
|
|
local ip_asn=$(echo "$ip_isp" | grep -oP 'AS\K\d+' | head -1)
|
|
|
|
|
local ip_asn=$(echo "$ip_isp" | grep -oP 'AS\K\d+' 2>/dev/null | head -1 2>/dev/null || echo "")
|
|
|
|
|
if [ -n "$ip_asn" ] && grep -q "^${ip_asn}$" "$TEMP_DIR/hostile_asns" 2>/dev/null; then
|
|
|
|
|
geo_bonus=$((geo_bonus + 15)) # Same botnet infrastructure
|
|
|
|
|
fi
|
|
|
|
@@ -3099,15 +3258,26 @@ auto_mitigation_engine() {
|
|
|
|
|
declare -A BLOCKED_THIS_SESSION
|
|
|
|
|
|
|
|
|
|
while true; do
|
|
|
|
|
sleep 10
|
|
|
|
|
# Batch blocking arrays (collect IPs, block in batches of 50)
|
|
|
|
|
local -a batch_instant=()
|
|
|
|
|
local -a batch_critical=()
|
|
|
|
|
|
|
|
|
|
# DEBUG: Log that we're checking
|
|
|
|
|
echo "[$(date +"%H:%M:%S")] AUTO_MIT: Checking for IPs to block..." >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
|
|
|
|
|
# Read current IP data from snapshot file (updated by main process)
|
|
|
|
|
if [ -f "$TEMP_DIR/ip_data" ]; then
|
|
|
|
|
# DEBUG: File exists
|
|
|
|
|
local ip_count=$(wc -l < "$TEMP_DIR/ip_data" 2>/dev/null || echo "0")
|
|
|
|
|
echo "[$(date +"%H:%M:%S")] AUTO_MIT: ip_data exists with $ip_count IPs" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
while IFS='=' read -r ip data; do
|
|
|
|
|
[ -z "$ip" ] && continue
|
|
|
|
|
|
|
|
|
|
IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$data"
|
|
|
|
|
|
|
|
|
|
# DEBUG: Log parsed data
|
|
|
|
|
echo "[$(date +"%H:%M:%S")] AUTO_MIT: Parsing IP $ip | score=$score | hits=$hits | attacks=$attacks" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
|
|
|
|
|
# Validate score is numeric
|
|
|
|
|
[ -z "$score" ] && score=0
|
|
|
|
|
[[ ! "$score" =~ ^[0-9]+$ ]] && score=0
|
|
|
|
@@ -3117,47 +3287,55 @@ auto_mitigation_engine() {
|
|
|
|
|
|
|
|
|
|
# INSTANT block at score 100 (MAXIMUM threat via IPset)
|
|
|
|
|
if [ "${score:-0}" -ge 100 ]; then
|
|
|
|
|
# DEBUG: Log score 100 detection
|
|
|
|
|
echo "[$(date +"%H:%M:%S")] AUTO_MIT: Found score 100 IP: $ip" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
|
|
|
|
|
# Mark as blocked
|
|
|
|
|
BLOCKED_THIS_SESSION[$ip]=1
|
|
|
|
|
|
|
|
|
|
# Instant IPset block
|
|
|
|
|
# Add to instant batch
|
|
|
|
|
batch_instant+=("$ip")
|
|
|
|
|
|
|
|
|
|
# Log event
|
|
|
|
|
local time_str=$(date +"%H:%M:%S")
|
|
|
|
|
echo -e "${CRITICAL_COLOR}[${time_str}] INSTANT_BLOCK | $ip | Score:100 | ${attacks}${NC}" >> "$TEMP_DIR/recent_events"
|
|
|
|
|
|
|
|
|
|
# Get detailed block reason
|
|
|
|
|
local block_reason="INSTANT AUTO-BLOCK: Score=100 Attacks=${attacks}"
|
|
|
|
|
if [ -f "$TEMP_DIR/block_reason_${ip//\./_}" ]; then
|
|
|
|
|
local intel_reason=$(cat "$TEMP_DIR/block_reason_${ip//\./_}")
|
|
|
|
|
block_reason="${block_reason} Intel:${intel_reason}"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Instant block via quick_block_ip (uses IPset for speed)
|
|
|
|
|
quick_block_ip "$ip" "$block_reason" &
|
|
|
|
|
continue
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Auto-block at score >= 80 (CRITICAL)
|
|
|
|
|
if [ "${score:-0}" -ge 80 ]; then
|
|
|
|
|
# Mark as blocked to prevent duplicate attempts
|
|
|
|
|
# Mark as blocked
|
|
|
|
|
BLOCKED_THIS_SESSION[$ip]=1
|
|
|
|
|
|
|
|
|
|
# Auto-block
|
|
|
|
|
# Add to critical batch
|
|
|
|
|
batch_critical+=("$ip")
|
|
|
|
|
|
|
|
|
|
# Log event
|
|
|
|
|
local time_str=$(date +"%H:%M:%S")
|
|
|
|
|
echo -e "${CRITICAL_COLOR}[${time_str}] AUTO_BLOCK | $ip | Score:$score | ${attacks}${NC}" >> "$TEMP_DIR/recent_events"
|
|
|
|
|
|
|
|
|
|
# Get detailed block reason
|
|
|
|
|
local block_reason="Auto-block: Score=$score Attacks=${attacks}"
|
|
|
|
|
if [ -f "$TEMP_DIR/block_reason_${ip//\./_}" ]; then
|
|
|
|
|
local intel_reason=$(cat "$TEMP_DIR/block_reason_${ip//\./_}")
|
|
|
|
|
block_reason="${block_reason} Intel:${intel_reason}"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Block for 1 hour with detailed reason
|
|
|
|
|
# Block in background and counter is updated within function
|
|
|
|
|
block_ip_temporary "$ip" 1 "$block_reason" &
|
|
|
|
|
fi
|
|
|
|
|
done < "$TEMP_DIR/ip_data"
|
|
|
|
|
else
|
|
|
|
|
# DEBUG: File doesn't exist
|
|
|
|
|
echo "[$(date +"%H:%M:%S")] AUTO_MIT: WARNING - ip_data file not found at $TEMP_DIR/ip_data" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# BATCH BLOCK - Instant (score 100)
|
|
|
|
|
if [ ${#batch_instant[@]} -gt 0 ]; then
|
|
|
|
|
echo "[$(date +"%H:%M:%S")] AUTO_MIT: Blocking ${#batch_instant[@]} instant IPs: ${batch_instant[*]}" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
batch_block_ips "${batch_instant[@]}"
|
|
|
|
|
else
|
|
|
|
|
echo "[$(date +"%H:%M:%S")] AUTO_MIT: No instant IPs to block" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# BATCH BLOCK - Critical (score 80-99)
|
|
|
|
|
if [ ${#batch_critical[@]} -gt 0 ]; then
|
|
|
|
|
echo "[$(date +"%H:%M:%S")] AUTO_MIT: Blocking ${#batch_critical[@]} critical IPs: ${batch_critical[*]}" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
|
|
|
|
|
batch_block_ips "${batch_critical[@]}"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Sleep at END of loop to check immediately on startup
|
|
|
|
|
sleep 10
|
|
|
|
|
done
|
|
|
|
|
) &
|
|
|
|
|
}
|
|
|
|
@@ -3172,6 +3350,49 @@ monitor_firewall_blocks
|
|
|
|
|
monitor_cphulk_blocks
|
|
|
|
|
monitor_network_attacks
|
|
|
|
|
|
|
|
|
|
# Display IPset initialization status
|
|
|
|
|
if [ -n "$IPSET_INIT_ERROR" ]; then
|
|
|
|
|
echo ""
|
|
|
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
|
|
echo -e "${HIGH_COLOR}⚠️ IPset Initialization Warning${NC}"
|
|
|
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
|
|
echo ""
|
|
|
|
|
echo " IPset fast blocking is NOT available"
|
|
|
|
|
echo " Reason: $IPSET_INIT_ERROR"
|
|
|
|
|
echo ""
|
|
|
|
|
echo " ${BOLD}Impact:${NC}"
|
|
|
|
|
echo " • Blocking will use CSF (slower than IPset)"
|
|
|
|
|
echo " • Large-scale attacks (500+ IPs) will be slower to block"
|
|
|
|
|
echo " • Performance: ~50x slower blocking vs IPset"
|
|
|
|
|
echo ""
|
|
|
|
|
echo " ${BOLD}To enable IPset fast blocking:${NC}"
|
|
|
|
|
|
|
|
|
|
if echo "$IPSET_INIT_ERROR" | grep -q "not found"; then
|
|
|
|
|
echo " 1. Install ipset: yum install ipset -y (or apt-get install ipset)"
|
|
|
|
|
echo " 2. Restart this script"
|
|
|
|
|
elif echo "$IPSET_INIT_ERROR" | grep -qi "module"; then
|
|
|
|
|
echo " 1. Load kernel modules: modprobe ip_set ip_set_hash_ip xt_set"
|
|
|
|
|
echo " 2. Restart this script"
|
|
|
|
|
elif echo "$IPSET_INIT_ERROR" | grep -qi "permission"; then
|
|
|
|
|
echo " 1. Run script as root: sudo $0"
|
|
|
|
|
elif echo "$IPSET_INIT_ERROR" | grep -q "iptables"; then
|
|
|
|
|
echo " 1. Check iptables: iptables -L -n"
|
|
|
|
|
echo " 2. Install iptables if missing: yum install iptables -y"
|
|
|
|
|
echo " 3. Ensure xt_set kernel module is loaded: modprobe xt_set"
|
|
|
|
|
else
|
|
|
|
|
echo " 1. Check debug log: $TEMP_DIR/debug.log"
|
|
|
|
|
echo " 2. Ensure ipset and iptables are installed"
|
|
|
|
|
echo " 3. Run as root"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
echo " Fallback: Using CSF for all blocking (still functional)"
|
|
|
|
|
echo ""
|
|
|
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
|
|
echo ""
|
|
|
|
|
sleep 3 # Give user time to read
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Start intelligence engines
|
|
|
|
|
detect_distributed_attacks
|
|
|
|
|
auto_mitigation_engine
|
|
|
|
@@ -3191,17 +3412,17 @@ if [ "$IPSET_AVAILABLE" -eq 0 ]; then
|
|
|
|
|
{
|
|
|
|
|
# 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}$'
|
|
|
|
|
csf -t 2>/dev/null | awk '/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/ {print $1}'
|
|
|
|
|
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}$'
|
|
|
|
|
awk '/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/ {print $1}' /etc/csf/csf.deny 2>/dev/null
|
|
|
|
|
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}$'
|
|
|
|
|
iptables -L INPUT -n -v 2>/dev/null | awk '/DROP/ && $8 ~ /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/ {print $8}'
|
|
|
|
|
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
|
|
|
|
@@ -3224,7 +3445,7 @@ 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")"
|
|
|
|
|
basename_file="${ip_file##*/}"
|
|
|
|
|
|
|
|
|
|
# Skip non-IP files explicitly
|
|
|
|
|
case "$basename_file" in
|
|
|
|
@@ -3310,7 +3531,7 @@ while true; do
|
|
|
|
|
echo ""
|
|
|
|
|
echo "Querying threat intelligence for $lookup_ip..."
|
|
|
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
|
|
local threat_intel=$(get_threat_intelligence "$lookup_ip")
|
|
|
|
|
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}"
|
|
|
|
@@ -3332,7 +3553,7 @@ while true; do
|
|
|
|
|
echo ""
|
|
|
|
|
read -p "Generate full incident report? (y/n): " gen_report
|
|
|
|
|
if [[ "$gen_report" =~ ^[Yy]$ ]]; then
|
|
|
|
|
local report_file=$(generate_incident_report "$lookup_ip")
|
|
|
|
|
report_file=$(generate_incident_report "$lookup_ip")
|
|
|
|
|
echo ""
|
|
|
|
|
echo "Report generated: $report_file"
|
|
|
|
|
echo ""
|
|
|
|
@@ -3351,7 +3572,7 @@ while true; do
|
|
|
|
|
clear
|
|
|
|
|
print_banner "Server Performance Monitor"
|
|
|
|
|
echo ""
|
|
|
|
|
local load_data=$(get_server_load)
|
|
|
|
|
load_data=$(get_server_load)
|
|
|
|
|
IFS='|' read -r load1 load5 load15 cpu_count <<< "$load_data"
|
|
|
|
|
echo "${BOLD}Current Load:${NC}"
|
|
|
|
|
echo " 1 min: $load1"
|
|
|
|
|