Implement HAProxy tarpit escalation and CLI monitoring
All checks were successful
HAProxy Manager Build and Push / Build-and-Push (push) Successful in 51s
All checks were successful
HAProxy Manager Build and Push / Build-and-Push (push) Successful in 51s
- Add full tarpit escalation logic with gpc1 tracking (levels 0-3) - Implement progressive delays: 2-5s → 8-15s → 20-45s → 60s - Increase initial threshold from 5 to 10 errors (more tolerant) - Reduce tracking duration from 2h to 1h (faster cleanup) - Add show-tarpit-ips.sh script for monitoring tarpitted IPs via CLI - Script shows IP, scan count, escalation level, and tarpit status 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
117
scripts/show-tarpit-ips.sh
Executable file
117
scripts/show-tarpit-ips.sh
Executable file
@@ -0,0 +1,117 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Script to display IPs that have been tarpitted by HAProxy 3.0
|
||||||
|
# Uses HAProxy stats socket to query stick-table data
|
||||||
|
#
|
||||||
|
# Usage in Docker container:
|
||||||
|
# docker exec -it haproxy-manager /haproxy/scripts/show-tarpit-ips.sh
|
||||||
|
|
||||||
|
SOCKET="/tmp/haproxy-cli"
|
||||||
|
|
||||||
|
# Check if socket exists
|
||||||
|
if [ ! -S "$SOCKET" ]; then
|
||||||
|
echo "Error: HAProxy socket not found at $SOCKET"
|
||||||
|
echo "Make sure HAProxy is running with stats socket enabled"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==================================================================="
|
||||||
|
echo " HAProxy Tarpitted IPs Report "
|
||||||
|
echo "==================================================================="
|
||||||
|
echo
|
||||||
|
echo "Showing IPs tracked in the stick-table with scan detection counters:"
|
||||||
|
echo "(gpc0 = total scan attempts, gpc1 = escalation level)"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# In HAProxy 3.0, we need to use the proper process prefix
|
||||||
|
# The web frontend table is in the worker process, not master
|
||||||
|
# First check which process has the table
|
||||||
|
# Note: grep for actual worker line, not the header
|
||||||
|
PROCESS_ID=$(echo "show proc" | socat stdio "$SOCKET" 2>/dev/null | grep -E '^[0-9]+.*worker' | awk '{print $1}' | head -1)
|
||||||
|
|
||||||
|
if [ -z "$PROCESS_ID" ]; then
|
||||||
|
echo "Error: Could not find HAProxy worker process"
|
||||||
|
echo "Try: echo 'show proc' | socat stdio $SOCKET"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show stick-table entries from the web frontend using the worker process
|
||||||
|
# Use printf to avoid bash history expansion issues with !
|
||||||
|
printf "@!%s show table web\n" "${PROCESS_ID}" | socat stdio "$SOCKET" 2>/dev/null | {
|
||||||
|
# Skip the header line
|
||||||
|
read header
|
||||||
|
|
||||||
|
# Check if we got an error or empty response
|
||||||
|
if echo "$header" | grep -q "No such table"; then
|
||||||
|
echo "Error: Table 'web' not found. HAProxy may need to be reloaded."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
has_data=false
|
||||||
|
echo "IP Address | Scan Count | Level | HTTP Err Rate | Status"
|
||||||
|
echo "---------------------|------------|-------|---------------|------------------"
|
||||||
|
|
||||||
|
# Process each line
|
||||||
|
while IFS= read -r line; do
|
||||||
|
# Skip empty lines and comments
|
||||||
|
if [ -z "$line" ] || echo "$line" | grep -q "^#"; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# HAProxy 3.0 format: 0x... key=<ip> use=... exp=... gpc0=... gpc1=... http_err_rate(10s)=...
|
||||||
|
if echo "$line" | grep -q "key="; then
|
||||||
|
has_data=true
|
||||||
|
|
||||||
|
# Extract IP and counters
|
||||||
|
ip=$(echo "$line" | grep -o 'key=[^ ]*' | cut -d'=' -f2)
|
||||||
|
gpc0=$(echo "$line" | grep -o 'gpc0=[0-9]*' | cut -d'=' -f2)
|
||||||
|
gpc1=$(echo "$line" | grep -o 'gpc1=[0-9]*' | cut -d'=' -f2)
|
||||||
|
err_rate=$(echo "$line" | grep -o 'http_err_rate([^)]*=[0-9]*' | grep -o '[0-9]*$')
|
||||||
|
|
||||||
|
# Set defaults if values are empty
|
||||||
|
gpc0=${gpc0:-0}
|
||||||
|
gpc1=${gpc1:-0}
|
||||||
|
err_rate=${err_rate:-0}
|
||||||
|
|
||||||
|
# Determine status based on scan count
|
||||||
|
status=""
|
||||||
|
if [ "$gpc0" -ge 50 ]; then
|
||||||
|
status="BLOCKED (critical)"
|
||||||
|
elif [ "$gpc0" -ge 35 ]; then
|
||||||
|
status="TARPITTED (high)"
|
||||||
|
elif [ "$gpc0" -ge 20 ]; then
|
||||||
|
status="TARPITTED (medium)"
|
||||||
|
elif [ "$gpc0" -ge 10 ]; then
|
||||||
|
status="MONITORED (low)"
|
||||||
|
else
|
||||||
|
status="Normal"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Format output
|
||||||
|
printf "%-20s | %10s | %5s | %13s | %s\n" "$ip" "$gpc0" "$gpc1" "$err_rate/10s" "$status"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$has_data" = false ]; then
|
||||||
|
echo "(No IPs currently tracked - table is empty)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "==================================================================="
|
||||||
|
echo "Legend:"
|
||||||
|
echo " - Scan Count 10-19: Potential scanner (monitored/low tarpit)"
|
||||||
|
echo " - Scan Count 20-34: Likely scanner (tarpitted with medium delay)"
|
||||||
|
echo " - Scan Count 35-49: Confirmed scanner (tarpitted with high delay)"
|
||||||
|
echo " - Scan Count 50+: Aggressive scanner (blocked with 429 status)"
|
||||||
|
echo "==================================================================="
|
||||||
|
echo "Note: IPs are tracked for 1 hour since last activity"
|
||||||
|
echo
|
||||||
|
echo "To clear a specific IP from the table:"
|
||||||
|
echo " printf '@!${PROCESS_ID} del table web key <IP>\\n' | socat stdio $SOCKET"
|
||||||
|
echo
|
||||||
|
echo "To clear all entries:"
|
||||||
|
echo " printf '@!${PROCESS_ID} clear table web\\n' | socat stdio $SOCKET"
|
||||||
|
echo
|
||||||
|
echo "Debug: Worker PID is ${PROCESS_ID}"
|
||||||
|
echo
|
@@ -7,7 +7,7 @@ frontend web
|
|||||||
# Stick table for tracking attacks with escalating timeouts
|
# Stick table for tracking attacks with escalating timeouts
|
||||||
# gpc0 = total scan attempts
|
# gpc0 = total scan attempts
|
||||||
# gpc1 = escalation level (0=none, 1=level1, 2=level2, 3=level3)
|
# gpc1 = escalation level (0=none, 1=level1, 2=level2, 3=level3)
|
||||||
stick-table type ip size 200k expire 2h store gpc0,gpc1,http_err_rate(10s)
|
stick-table type ip size 200k expire 1h store gpc0,gpc1,http_err_rate(10s)
|
||||||
|
|
||||||
# Whitelist trusted networks and monitoring systems
|
# Whitelist trusted networks and monitoring systems
|
||||||
acl trusted_networks src 127.0.0.1 192.168.0.0/16 10.0.0.0/8 172.16.0.0/12
|
acl trusted_networks src 127.0.0.1 192.168.0.0/16 10.0.0.0/8 172.16.0.0/12
|
||||||
@@ -40,21 +40,51 @@ frontend web
|
|||||||
|
|
||||||
# Define threat levels based on accumulated error responses from backends
|
# Define threat levels based on accumulated error responses from backends
|
||||||
# These will be checked on subsequent requests after errors are tracked
|
# These will be checked on subsequent requests after errors are tracked
|
||||||
acl scanner_low sc0_get_gpc0 ge 5 # 5+ errors = potential scanner
|
acl scanner_low sc0_get_gpc0 ge 10 # 10+ errors = potential scanner
|
||||||
acl scanner_medium sc0_get_gpc0 ge 15 # 15+ errors = likely scanner
|
acl scanner_medium sc0_get_gpc0 ge 20 # 20+ errors = likely scanner
|
||||||
acl scanner_high sc0_get_gpc0 ge 30 # 30+ errors = confirmed scanner
|
acl scanner_high sc0_get_gpc0 ge 35 # 35+ errors = confirmed scanner
|
||||||
acl scanner_critical sc0_get_gpc0 ge 50 # 50+ errors = aggressive scanner
|
acl scanner_critical sc0_get_gpc0 ge 50 # 50+ errors = aggressive scanner
|
||||||
|
|
||||||
# Rate-based detection (burst of errors)
|
# Rate-based detection (burst of errors)
|
||||||
acl burst_scanner sc0_http_err_rate gt 5 # >5 errors in 10 seconds
|
acl burst_scanner sc0_http_err_rate gt 5 # >5 errors in 10 seconds
|
||||||
|
|
||||||
|
# Escalation levels (tracks how many times we've escalated this IP)
|
||||||
|
acl escalation_level_0 sc0_get_gpc1 eq 0 # First offense
|
||||||
|
acl escalation_level_1 sc0_get_gpc1 eq 1 # Second offense
|
||||||
|
acl escalation_level_2 sc0_get_gpc1 eq 2 # Third offense
|
||||||
|
acl escalation_level_3 sc0_get_gpc1 ge 3 # Repeat offender
|
||||||
|
|
||||||
# BLOCKING RULES - Block aggressive scanners completely
|
# BLOCKING RULES - Block aggressive scanners completely
|
||||||
# Only block after significant error accumulation
|
# Only block after significant error accumulation
|
||||||
http-request deny deny_status 429 if scanner_critical
|
http-request deny deny_status 429 if scanner_critical
|
||||||
|
|
||||||
# TARPIT RULES - Slow down detected scanners
|
# ESCALATING TARPIT RULES - Progressive delays based on offense level
|
||||||
# Apply progressive delays based on error count
|
# Level 0 (first offense): Short delays
|
||||||
http-request tarpit if scanner_high
|
http-request tarpit deny_status 429 timeout 2s if scanner_low escalation_level_0
|
||||||
http-request tarpit if scanner_medium burst_scanner
|
http-request tarpit deny_status 429 timeout 3s if scanner_medium escalation_level_0
|
||||||
|
http-request tarpit deny_status 429 timeout 5s if scanner_high escalation_level_0
|
||||||
|
http-request tarpit deny_status 429 timeout 5s if burst_scanner escalation_level_0
|
||||||
|
|
||||||
|
# Level 1 (second offense): Medium delays
|
||||||
|
http-request tarpit deny_status 429 timeout 8s if scanner_low escalation_level_1
|
||||||
|
http-request tarpit deny_status 429 timeout 12s if scanner_medium escalation_level_1
|
||||||
|
http-request tarpit deny_status 429 timeout 15s if scanner_high escalation_level_1
|
||||||
|
http-request tarpit deny_status 429 timeout 10s if burst_scanner escalation_level_1
|
||||||
|
|
||||||
|
# Level 2 (third offense): Long delays
|
||||||
|
http-request tarpit deny_status 429 timeout 20s if scanner_low escalation_level_2
|
||||||
|
http-request tarpit deny_status 429 timeout 30s if scanner_medium escalation_level_2
|
||||||
|
http-request tarpit deny_status 429 timeout 45s if scanner_high escalation_level_2
|
||||||
|
http-request tarpit deny_status 429 timeout 25s if burst_scanner escalation_level_2
|
||||||
|
|
||||||
|
# Level 3+ (repeat offender): Maximum delays
|
||||||
|
http-request tarpit deny_status 429 timeout 60s if scanner_low escalation_level_3
|
||||||
|
http-request tarpit deny_status 429 timeout 60s if scanner_medium escalation_level_3
|
||||||
|
http-request tarpit deny_status 429 timeout 60s if scanner_high escalation_level_3
|
||||||
|
http-request tarpit deny_status 429 timeout 60s if burst_scanner escalation_level_3
|
||||||
|
|
||||||
|
# Increment escalation level when we apply tarpit
|
||||||
|
# This tracks how many times this IP has been tarpitted
|
||||||
|
http-request sc-inc-gpc1(0) if scanner_low or scanner_medium or scanner_high or burst_scanner
|
||||||
|
|
||||||
# Note: The backend will increment sc0_get_gpc0 when it sees 400/401/403/404 responses
|
# Note: The backend will increment sc0_get_gpc0 when it sees 400/401/403/404 responses
|
||||||
|
Reference in New Issue
Block a user