diff --git a/modules/security/live-attack-monitor.sh b/modules/security/live-attack-monitor.sh index 0b4416d..0f80019 100755 --- a/modules/security/live-attack-monitor.sh +++ b/modules/security/live-attack-monitor.sh @@ -725,6 +725,16 @@ calculate_context_bonus() { echo "${bonus}|${reasons}" } +# Atomically increment block counter (prevents race conditions) +increment_block_counter() { + local increment="${1:-1}" + ( + flock -x 200 + local current=$(cat "$TEMP_DIR/total_blocks" 2>/dev/null || echo "0") + echo $((current + increment)) > "$TEMP_DIR/total_blocks" + ) 200>"$TEMP_DIR/counter.lock" +} + # Batch block multiple IPs at once (optimized for DDoS scenarios) batch_block_ips() { local -a ip_list=("$@") @@ -778,9 +788,8 @@ batch_block_ips() { echo "✓ CSF batch: $blocked blocked, $failed failed" fi - # Update total counter - local current_total=$(cat "$TEMP_DIR/total_blocks" 2>/dev/null || echo "0") - echo $((current_total + blocked)) > "$TEMP_DIR/total_blocks" + # Update total counter atomically + increment_block_counter "$blocked" return 0 } @@ -805,9 +814,8 @@ block_ip_temporary() { echo "✓ $ip blocked via IPset (auto-expires in ${hours}h)" echo "$ip" >> "$TEMP_DIR/blocked_ips_cache" - # Update counter - local current_total=$(cat "$TEMP_DIR/total_blocks" 2>/dev/null || echo "0") - echo $((current_total + 1)) > "$TEMP_DIR/total_blocks" + # Update counter atomically + increment_block_counter 1 return 0 else @@ -819,57 +827,18 @@ block_ip_temporary() { # Fallback to CSF if IPset not available 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=$? + if csf -td "$ip" "$seconds" "$reason" >/dev/null 2>&1; then + echo "✓ $ip blocked via CSF" + echo "$ip" >> "$TEMP_DIR/blocked_ips_cache" - # 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" + # Update counter atomically + increment_block_counter 1 - # 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 + return 0 + else + echo "✗ Warning: CSF block failed for $ip" + return 1 fi - - echo "✗ Warning: Failed to verify block for $ip" - return 1 fi echo "✗ Error: CSF not available" @@ -896,57 +865,18 @@ block_ip_permanent() { if command -v csf &>/dev/null; then echo "Permanently blocking $ip: $reason" - csf -d "$ip" "$reason" >/dev/null 2>&1 - local result=$? + if csf -d "$ip" "$reason" >/dev/null 2>&1; then + echo "✓ $ip permanently blocked via CSF" + echo "$ip" >> "$TEMP_DIR/blocked_ips_cache" - # 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" + # Update counter atomically + increment_block_counter 1 - # 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 + return 0 + else + echo "✗ Warning: CSF permanent block failed for $ip" + return 1 fi - - echo "✗ Warning: Failed to verify permanent block for $ip" - return 1 fi echo "✗ Error: CSF not available" @@ -2295,11 +2225,8 @@ auto_mitigation_engine() { fi # Block for 1 hour with detailed reason + # Block in background and counter is updated within function 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