From aadc3be64a360fe9d8489ce30404ecf11e28235c Mon Sep 17 00:00:00 2001 From: cschantz Date: Wed, 24 Dec 2025 19:54:57 -0500 Subject: [PATCH] Sync v2 with main: Add all missing auto-blocking and SYN flood enhancements - Added missing quick_block_ip() function - Added INSTANT_BLOCK for score 100 - Added AUTO_BLOCK for score >=80 - Added full SYN flood reputation tracking - Added intelligent threat scoring (persistence, escalation, threat intel) - v2 was 7 days behind main, now synced --- modules/security/live-attack-monitor-v2.sh | 269 +++++++++++++++++++-- 1 file changed, 248 insertions(+), 21 deletions(-) diff --git a/modules/security/live-attack-monitor-v2.sh b/modules/security/live-attack-monitor-v2.sh index d508ac6..dbee0dc 100755 --- a/modules/security/live-attack-monitor-v2.sh +++ b/modules/security/live-attack-monitor-v2.sh @@ -20,9 +20,8 @@ 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" -# TEMP: Still needed for non-HTTP monitors (SSH, cPHulk, firewall) - will remove in Phase 4 -source "$SCRIPT_DIR/lib/attack-patterns.sh" 2>/dev/null || true # Enhanced attack detection (ET Open signatures) source "$SCRIPT_DIR/lib/attack-signatures.sh" 2>/dev/null || true @@ -289,8 +288,37 @@ update_ip_intelligence() { record_attack_pattern "$ip" "${attacks:-unknown}" "$url" "${user_agent:-unknown}" 2>/dev/null & fi - # NOTE: Legacy detect_all_attacks() removed - ET Open handles all attack detection - # This eliminates 50% performance waste from double detection + # Detect attacks in URL (pass user_agent and ip for enhanced detection) + local new_attacks=$(detect_all_attacks "$url" "$method" "$user_agent" "$ip") + + if [ -n "$new_attacks" ]; then + # Add to attack list (unique) + if [ -z "$attacks" ]; then + attacks="$new_attacks" + else + attacks="$attacks,$new_attacks" + fi + + # Remove duplicates using associative array (faster than sort -u pipeline) + local -A unique_attacks + IFS=',' read -ra ATTACK_LIST <<< "$attacks" + for atk in "${ATTACK_LIST[@]}"; do + [ -n "$atk" ] && unique_attacks[$atk]=1 + done + attacks=$(IFS=','; echo "${!unique_attacks[*]}") + + # Update attack type counter + IFS=',' read -ra ATTACK_ARRAY <<< "$new_attacks" + for attack in "${ATTACK_ARRAY[@]}"; do + ((ATTACK_TYPE_COUNTER["$attack"]++)) + done + + # Calculate attack score + local attack_score=$(calculate_attack_score "$new_attacks") + score=$((score + attack_score)) + + ((TOTAL_THREATS++)) + fi # Request volume scoring if [ "${hits:-0}" -gt 100 ]; then @@ -857,6 +885,21 @@ block_ip_temporary() { return 1 } +# Quick block IP (wrapper for background auto-blocking) +# Used by ET detection and auto-mitigation engine +quick_block_ip() { + local ip="$1" + local reason="${2:-Auto-block: Critical threat}" + + # Validate IP + if ! is_valid_ip "$ip"; then + return 1 + fi + + # Block for 1 hour using IPset or CSF + block_ip_temporary "$ip" 1 "$reason" >/dev/null 2>&1 +} + # Block IP permanently with CSF block_ip_permanent() { local ip="$1" @@ -1696,12 +1739,18 @@ monitor_apache_logs() { # Update IP intelligence with ET attack info update_ip_intelligence "$ip" "$url|ET:$et_attack_types|$et_signatures" "attack" "HTTP" - # Use ET detection score directly (legacy detection removed) + # Replace IP threat score with ET detection score + # Note: We use ET score instead of adding it to avoid double-counting + # (update_ip_intelligence already detected the same attack via legacy patterns) local current_intel=$(get_ip_intelligence "$ip") IFS='|' read -r curr_score curr_hits curr_bot curr_attacks curr_ban curr_rep <<< "$current_intel" - # Keep higher score (ET attack vs AbuseIPDB reputation) - local new_score=$((et_attack_score > curr_score ? et_attack_score : curr_score)) + # Use ET score if it's higher than current score + local new_score="$et_attack_score" + if [ "$curr_score" -gt "$et_attack_score" ]; then + # Keep higher score (e.g., from AbuseIPDB reputation boost) + new_score="$curr_score" + fi [ "$new_score" -gt 100 ] && new_score=100 # Update IP data with ET-based score @@ -2175,24 +2224,177 @@ monitor_network_attacks() { 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 + # Skip local/private IPs first + if [[ "$ip" =~ ^127\. ]] || \ + [[ "$ip" =~ ^10\. ]] || \ + [[ "$ip" =~ ^192\.168\. ]] || \ + [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[01])\. ]]; then + continue + fi + + # Track connection count for this IP + CONNECTION_COUNT[$ip]=$count + + if [ "$count" -gt 20 ]; then # More than 20 SYN_RECV connections = DDoS + # Only process once per detection window 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 + # Update IP reputation via file (subshell can't access IP_DATA array) + local ip_file="$TEMP_DIR/ip_${ip//\./_}" + local current_data="0|0|human||0|0" + if [ -f "$ip_file" ]; then + current_data=$(cat "$ip_file") + fi + IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$current_data" + + # Increment hits + hits=$((hits + 1)) + + # Enhanced threat intelligence on first detection + if [ "${hits:-0}" -eq 1 ]; then + # Check if whitelisted service first + if is_whitelisted_service "$ip" 2>/dev/null; then + continue # Skip whitelisted IPs + fi + + # Get threat intelligence in background to avoid slowdown + ( + local threat_intel=$(get_threat_intelligence "$ip" 2>/dev/null) + IFS='|' read -r abuse_conf abuse_rpts country isp geo timing whitelisted <<< "$threat_intel" + + # Store enrichment for later use + echo "$threat_intel" > "$TEMP_DIR/threat_enrich_${ip//\./_}" + + # Apply reputation boosts based on AbuseIPDB + if [ "${abuse_conf:-0}" -ge 75 ]; then + # High confidence malicious - add 30 points + local curr_data=$(cat "$ip_file" 2>/dev/null || echo "0|0|human||0|0") + IFS='|' read -r old_score old_hits old_bot old_attacks old_ban old_rep <<< "$curr_data" + local new_score=$((old_score + 30)) + [ "$new_score" -gt 100 ] && new_score=100 + echo "$new_score|$old_hits|$old_bot|$old_attacks|$old_ban|$old_rep" > "$ip_file" + elif [ "${abuse_conf:-0}" -ge 50 ]; then + # Medium confidence - add 15 points + local curr_data=$(cat "$ip_file" 2>/dev/null || echo "0|0|human||0|0") + IFS='|' read -r old_score old_hits old_bot old_attacks old_ban old_rep <<< "$curr_data" + local new_score=$((old_score + 15)) + [ "$new_score" -gt 100 ] && new_score=100 + echo "$new_score|$old_hits|$old_bot|$old_attacks|$old_ban|$old_rep" > "$ip_file" + fi + + # High-risk country adds 5 points + if is_high_risk_country "${geo:-XX}" 2>/dev/null; then + local curr_data=$(cat "$ip_file" 2>/dev/null || echo "0|0|human||0|0") + IFS='|' read -r old_score old_hits old_bot old_attacks old_ban old_rep <<< "$curr_data" + local new_score=$((old_score + 5)) + [ "$new_score" -gt 100 ] && new_score=100 + echo "$new_score|$old_hits|$old_bot|$old_attacks|$old_ban|$old_rep" > "$ip_file" + fi + ) & fi - # Log high connection count + # Record attack intelligence + record_attack_timestamp "$ip" + record_attack_vector "$ip" "NETWORK" + track_subnet_attack "$ip" + + # Add SYN_FLOOD to attacks if not already present + if [[ ! "$attacks" =~ SYN_FLOOD ]]; then + [ -z "$attacks" ] && attacks="SYN_FLOOD" || attacks="${attacks},SYN_FLOOD" + fi + + # Progressive scoring based on connection count + # 20-50 conns: +15 pts, 50-100: +25 pts, 100+: +40 pts + local conn_bonus=0 + if [ "$count" -ge 100 ]; then + conn_bonus=40 + elif [ "$count" -ge 50 ]; then + conn_bonus=25 + else + conn_bonus=15 + fi + + # Connection persistence bonus (repeated detections of same IP) + # This indicates sustained attack vs transient spike + if [ "${hits:-0}" -ge 5 ]; then + conn_bonus=$((conn_bonus + 20)) # Persistent attacker + elif [ "${hits:-0}" -ge 3 ]; then + conn_bonus=$((conn_bonus + 10)) # Repeated attack + fi + + # Connection escalation detection + # Check if connection count is increasing (more aggressive attack) + local prev_count="${CONNECTION_COUNT[$ip]:-0}" + if [ "$count" -gt "$prev_count" ] && [ "$prev_count" -gt 0 ]; then + local increase=$((count - prev_count)) + if [ "$increase" -ge 50 ]; then + conn_bonus=$((conn_bonus + 25)) # Rapidly escalating + elif [ "$increase" -ge 20 ]; then + conn_bonus=$((conn_bonus + 15)) # Escalating + fi + fi + + # First hit or add to existing score + if [ "${hits:-0}" -eq 1 ]; then + score=$conn_bonus + else + score=$((score + conn_bonus)) + fi + + # Apply advanced intelligence bonuses + local block_reasons="" + local velocity_data=$(calculate_attack_velocity "$ip") + IFS='|' read -r vel_count vel_bonus vel_reason <<< "$velocity_data" + [ "$vel_bonus" -gt 0 ] && score=$((score + vel_bonus)) && block_reasons="${vel_reason}" + + local div_data=$(calculate_diversity_bonus "$ip") + IFS='|' read -r div_count div_bonus div_reason <<< "$div_data" + if [ "$div_bonus" -gt 0 ]; then + score=$((score + div_bonus)) + [ -n "$block_reasons" ] && block_reasons="${block_reasons}+" || block_reasons="" + block_reasons="${block_reasons}${div_reason}" + fi + + local subnet_bonus=$(calculate_subnet_bonus "$ip") + if [ "$subnet_bonus" -gt 0 ]; then + score=$((score + subnet_bonus)) + local context_reason="SUBNET_ATTACK" + [ -n "$block_reasons" ] && block_reasons="${block_reasons}+" || block_reasons="" + block_reasons="${block_reasons}${context_reason}" + fi + + # Detect timing patterns + local timing_result=$(detect_timing_pattern "$ip") + IFS='|' read -r timing_type timing_bonus timing_reason <<< "$timing_result" + if [ "$timing_bonus" -gt 0 ]; then + score=$((score + timing_bonus)) + [ -n "$block_reasons" ] && block_reasons="${block_reasons}+" || block_reasons="" + block_reasons="${block_reasons}${timing_reason}" + fi + + # Cap at 100 + [ "$score" -gt 100 ] && score=100 + + # Write to file for main process + echo "$score|$hits|$bot_type|$attacks|$ban_count|$rep_score" > "$ip_file" + + # Store block reasons for auto-mitigation + if [ -n "$block_reasons" ]; then + echo "$block_reasons" > "$TEMP_DIR/block_reason_${ip//\./_}" + fi + + # Log to reputation DB + flag_ip_attack "$ip" "SYN_FLOOD" 0 "SYN flood: $count connections" >/dev/null 2>&1 & + + # Log event with reputation score 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" + local level=$(get_threat_level "$score") + local color=$(get_threat_color "$level") + echo -e "${color}[${time_str}] $ip | Score:$score [$level] | 💥SYN_FLOOD | $count SYN_RECV connections${NC}" >> "$TEMP_DIR/recent_events" fi else - # Reset alert if connections drop + # Reset alert if connections drop below threshold unset ALERT_SENT[$ip] fi done < <(ss -tn state syn-recv 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | sort | uniq -c | awk '$1 > 5 {print $2, $1}') @@ -2635,11 +2837,36 @@ auto_mitigation_engine() { 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 + # Validate score is numeric + [ -z "$score" ] && score=0 + [[ ! "$score" =~ ^[0-9]+$ ]] && score=0 + # Skip if already blocked in this session + [ -n "${BLOCKED_THIS_SESSION[$ip]}" ] && continue + + # INSTANT block at score 100 (MAXIMUM threat via IPset) + if [ "${score:-0}" -ge 100 ]; then + # Mark as blocked + BLOCKED_THIS_SESSION[$ip]=1 + + # Instant IPset block + local time_str=$(date +"%H:%M:%S") + echo -e "${CRITICAL_COLOR}[${time_str}] INSTANT_BLOCK | $ip | Score:100 | ${attacks}${NC}" >> "$TEMP_DIR/recent_events" + + # Get detailed block reason + local block_reason="INSTANT AUTO-BLOCK: Score=100 Attacks=${attacks}" + if [ -f "$TEMP_DIR/block_reason_${ip//\./_}" ]; then + local intel_reason=$(cat "$TEMP_DIR/block_reason_${ip//\./_}") + block_reason="${block_reason} Intel:${intel_reason}" + fi + + # Instant block via quick_block_ip (uses IPset for speed) + quick_block_ip "$ip" "$block_reason" & + continue + fi + + # Auto-block at score >= 80 (CRITICAL) + if [ "${score:-0}" -ge 80 ]; then # Mark as blocked to prevent duplicate attempts BLOCKED_THIS_SESSION[$ip]=1