Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d30cc0aea | |||
| f0d53322e6 | |||
| ea0a9721ba | |||
| 32b620756f | |||
| d038f51e60 |
@@ -28,6 +28,9 @@ source "$SCRIPT_DIR/lib/common-functions.sh"
|
|||||||
source "$SCRIPT_DIR/lib/system-detect.sh"
|
source "$SCRIPT_DIR/lib/system-detect.sh"
|
||||||
source "$SCRIPT_DIR/lib/user-manager.sh"
|
source "$SCRIPT_DIR/lib/user-manager.sh"
|
||||||
source "$SCRIPT_DIR/lib/ip-reputation.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"
|
||||||
|
|
||||||
# Default configuration (auto-detected from system)
|
# Default configuration (auto-detected from system)
|
||||||
LOG_DIR="${SYS_LOG_DIR:-/var/log/apache2/domlogs}"
|
LOG_DIR="${SYS_LOG_DIR:-/var/log/apache2/domlogs}"
|
||||||
@@ -230,80 +233,8 @@ trap "rm -rf $TEMP_DIR" EXIT
|
|||||||
#############################################################################
|
#############################################################################
|
||||||
# Bot Signature Database
|
# Bot Signature Database
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
# NOTE: Bot signatures now loaded from lib/bot-signatures.sh
|
||||||
# Legitimate bots (search engines)
|
# Arrays available: LEGIT_BOTS, AI_BOTS, MONITOR_BOTS, SUSPICIOUS_BOTS
|
||||||
declare -A LEGIT_BOTS=(
|
|
||||||
["Googlebot"]="Google Search"
|
|
||||||
["Googlebot-Image"]="Google Images"
|
|
||||||
["Googlebot-Video"]="Google Video"
|
|
||||||
["Googlebot-News"]="Google News"
|
|
||||||
["Google-InspectionTool"]="Google Search Console"
|
|
||||||
["Storebot-Google"]="Google Merchant"
|
|
||||||
["APIs-Google"]="Google APIs"
|
|
||||||
["AdsBot-Google"]="Google Ads"
|
|
||||||
["Mediapartners-Google"]="Google AdSense"
|
|
||||||
["bingbot"]="Bing Search"
|
|
||||||
["msnbot"]="MSN Search"
|
|
||||||
["Slurp"]="Yahoo Search"
|
|
||||||
["DuckDuckBot"]="DuckDuckGo"
|
|
||||||
["Baiduspider"]="Baidu Search"
|
|
||||||
["YandexBot"]="Yandex Search"
|
|
||||||
)
|
|
||||||
|
|
||||||
# AI Bots
|
|
||||||
declare -A AI_BOTS=(
|
|
||||||
["GPTBot"]="OpenAI"
|
|
||||||
["ChatGPT-User"]="OpenAI ChatGPT"
|
|
||||||
["ClaudeBot"]="Anthropic Claude"
|
|
||||||
["Claude-Web"]="Anthropic Web"
|
|
||||||
["Bytespider"]="ByteDance (TikTok)"
|
|
||||||
["PetalBot"]="Huawei"
|
|
||||||
["CCBot"]="Common Crawl"
|
|
||||||
["anthropic-ai"]="Anthropic"
|
|
||||||
["Applebot"]="Apple Intelligence"
|
|
||||||
["facebookexternalhit"]="Facebook/Meta"
|
|
||||||
["Meta-ExternalAgent"]="Meta AI"
|
|
||||||
["cohere-ai"]="Cohere AI"
|
|
||||||
["PerplexityBot"]="Perplexity AI"
|
|
||||||
["YouBot"]="You.com AI"
|
|
||||||
["Diffbot"]="Diffbot AI"
|
|
||||||
["ImagesiftBot"]="ImageSift AI"
|
|
||||||
["Omgilibot"]="Omgili AI"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Monitoring/SEO bots
|
|
||||||
declare -A MONITOR_BOTS=(
|
|
||||||
["AhrefsBot"]="Ahrefs SEO"
|
|
||||||
["SemrushBot"]="SEMrush SEO"
|
|
||||||
["MJ12bot"]="Majestic SEO"
|
|
||||||
["DotBot"]="Moz/OpenSite"
|
|
||||||
["BLEXBot"]="BLEXBot SEO"
|
|
||||||
["PingdomBot"]="Pingdom Monitoring"
|
|
||||||
["UptimeRobot"]="Uptime Monitoring"
|
|
||||||
["StatusCake"]="StatusCake Monitoring"
|
|
||||||
["SiteImprove"]="SiteImprove Analytics"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Suspicious/Aggressive bots (malicious or security scanners)
|
|
||||||
declare -A SUSPICIOUS_BOTS=(
|
|
||||||
["MauiBot"]="Malicious crawler"
|
|
||||||
["DataForSeoBot"]="Data scraper"
|
|
||||||
["ZoominfoBot"]="Data harvester"
|
|
||||||
["MegaIndex"]="Aggressive crawler"
|
|
||||||
["SeznamBot"]="Aggressive crawler"
|
|
||||||
["Yeti"]="Naver crawler"
|
|
||||||
["serpstatbot"]="SEO crawler"
|
|
||||||
["LinkpadBot"]="Link checker"
|
|
||||||
["Nessus"]="Vulnerability scanner"
|
|
||||||
["Nikto"]="Security scanner"
|
|
||||||
["sqlmap"]="SQL injection tool"
|
|
||||||
["ZmEu"]="Scanner/exploit"
|
|
||||||
["masscan"]="Port scanner"
|
|
||||||
["nmap"]="Port scanner"
|
|
||||||
["wget"]="Command-line tool"
|
|
||||||
["curl"]="Command-line tool"
|
|
||||||
["python-requests"]="Script/automation"
|
|
||||||
)
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
# Helper Functions
|
# Helper Functions
|
||||||
@@ -417,20 +348,22 @@ parse_logs() {
|
|||||||
if (ip != "" && ip !~ /^[[:space:]]*$/) {
|
if (ip != "" && ip !~ /^[[:space:]]*$/) {
|
||||||
print ip "|" domain "|" request_url "|" status "|" size "|" user_agent "|" http_method "|" timestamp
|
print ip "|" domain "|" request_url "|" status "|" size "|" user_agent "|" http_method "|" timestamp
|
||||||
}
|
}
|
||||||
}' "$logfile" >> "$TEMP_DIR/parsed_logs.txt" 2>/dev/null
|
}' "$logfile" 2>/dev/null
|
||||||
done
|
done | gzip > "$TEMP_DIR/parsed_logs.txt.gz"
|
||||||
|
|
||||||
# Clear the progress line
|
# Clear the progress line
|
||||||
echo -ne "\r\033[K"
|
echo -ne "\r\033[K"
|
||||||
|
|
||||||
if [ ! -s "$TEMP_DIR/parsed_logs.txt" ]; then
|
if [ ! -s "$TEMP_DIR/parsed_logs.txt.gz" ]; then
|
||||||
print_alert "No log entries were parsed. Check log format or permissions."
|
print_alert "No log entries were parsed. Check log format or permissions."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local line_count
|
local line_count
|
||||||
line_count=$(wc -l < "$TEMP_DIR/parsed_logs.txt")
|
line_count=$(zcat "$TEMP_DIR/parsed_logs.txt.gz" | wc -l)
|
||||||
print_success "Logs parsed successfully ($line_count entries)"
|
local file_size_kb
|
||||||
|
file_size_kb=$(du -k "$TEMP_DIR/parsed_logs.txt.gz" | cut -f1)
|
||||||
|
print_success "Logs parsed successfully ($line_count entries, ${file_size_kb}KB compressed)"
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,16 +462,18 @@ classify_bots() {
|
|||||||
if (bot_type != "unknown") {
|
if (bot_type != "unknown") {
|
||||||
print ip "|" domain "|" url "|" status "|" size "|" ua "|" method "|" timestamp "|" bot_type "|" bot_name
|
print ip "|" domain "|" url "|" status "|" size "|" ua "|" method "|" timestamp "|" bot_type "|" bot_name
|
||||||
}
|
}
|
||||||
}' "$TEMP_DIR/parsed_logs.txt" > "$TEMP_DIR/classified_bots.txt"
|
}' < <(zcat "$TEMP_DIR/parsed_logs.txt.gz") | gzip > "$TEMP_DIR/classified_bots.txt.gz"
|
||||||
|
|
||||||
if [ ! -s "$TEMP_DIR/classified_bots.txt" ]; then
|
if [ ! -s "$TEMP_DIR/classified_bots.txt.gz" ]; then
|
||||||
print_alert "Bot classification failed"
|
print_alert "Bot classification failed"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local classified_count
|
local classified_count
|
||||||
classified_count=$(wc -l < "$TEMP_DIR/classified_bots.txt")
|
classified_count=$(zcat "$TEMP_DIR/classified_bots.txt.gz" | wc -l)
|
||||||
print_success "Bot classification complete ($classified_count entries)"
|
local file_size_kb
|
||||||
|
file_size_kb=$(du -k "$TEMP_DIR/classified_bots.txt.gz" | cut -f1)
|
||||||
|
print_success "Bot classification complete ($classified_count entries, ${file_size_kb}KB compressed)"
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,7 +560,7 @@ detect_threats() {
|
|||||||
# Track response codes for intelligence
|
# Track response codes for intelligence
|
||||||
print status > "'"$TEMP_DIR"'/response_codes_raw.txt"
|
print status > "'"$TEMP_DIR"'/response_codes_raw.txt"
|
||||||
}
|
}
|
||||||
' "$TEMP_DIR/parsed_logs.txt"
|
' < <(zcat "$TEMP_DIR/parsed_logs.txt.gz")
|
||||||
|
|
||||||
# Process attack vectors by type
|
# Process attack vectors by type
|
||||||
if [ -f "$TEMP_DIR/attack_vectors_raw.txt" ]; then
|
if [ -f "$TEMP_DIR/attack_vectors_raw.txt" ]; then
|
||||||
@@ -691,23 +626,23 @@ detect_botnets() {
|
|||||||
|
|
||||||
# Group IPs by similar behavior patterns
|
# Group IPs by similar behavior patterns
|
||||||
# Pattern 1: Multiple IPs hitting same URLs in coordinated manner
|
# Pattern 1: Multiple IPs hitting same URLs in coordinated manner
|
||||||
awk -F'|' '{print $1"|"$3}' "$TEMP_DIR/parsed_logs.txt" | \
|
zcat "$TEMP_DIR/parsed_logs.txt.gz" | awk -F'|' '{print $1"|"$3}' | \
|
||||||
sort | uniq -c | awk '$1 > 10 {print $2}' | \
|
sort | uniq -c | awk '$1 > 10 {print $2}' | \
|
||||||
cut -d'|' -f2 | sort | uniq -c | sort -rn | \
|
cut -d'|' -f2 | sort | uniq -c | sort -rn | \
|
||||||
awk '$1 > 5 {print $2}' > "$TEMP_DIR/coordinated_urls.txt"
|
awk '$1 > 5 {print $2}' > "$TEMP_DIR/coordinated_urls.txt"
|
||||||
|
|
||||||
# Pattern 2: IPs with similar User-Agents hitting multiple domains
|
# Pattern 2: IPs with similar User-Agents hitting multiple domains
|
||||||
awk -F'|' '{print $1"|"$6}' "$TEMP_DIR/parsed_logs.txt" | \
|
zcat "$TEMP_DIR/parsed_logs.txt.gz" | awk -F'|' '{print $1"|"$6}' | \
|
||||||
sort | uniq > "$TEMP_DIR/ip_ua_pairs.txt"
|
sort | uniq > "$TEMP_DIR/ip_ua_pairs.txt"
|
||||||
|
|
||||||
# Pattern 3: Detect IP ranges (Class C networks) with suspicious activity
|
# Pattern 3: Detect IP ranges (Class C networks) with suspicious activity
|
||||||
awk -F'|' '{print $1}' "$TEMP_DIR/parsed_logs.txt" | \
|
zcat "$TEMP_DIR/parsed_logs.txt.gz" | awk -F'|' '{print $1}' | \
|
||||||
awk -F'.' '{print $1"."$2"."$3".0/24"}' | \
|
awk -F'.' '{print $1"."$2"."$3".0/24"}' | \
|
||||||
sort | uniq -c | sort -rn | awk '$1 > 20' > "$TEMP_DIR/suspicious_networks.txt"
|
sort | uniq -c | sort -rn | awk '$1 > 20' > "$TEMP_DIR/suspicious_networks.txt"
|
||||||
|
|
||||||
# Pattern 4: Rapid fire requests (DDoS indicators)
|
# Pattern 4: Rapid fire requests (DDoS indicators)
|
||||||
# Extract timestamp and count requests per IP per minute
|
# Extract timestamp and count requests per IP per minute
|
||||||
awk -F'|' '{
|
zcat "$TEMP_DIR/parsed_logs.txt.gz" | awk -F'|' '{
|
||||||
ip = $1
|
ip = $1
|
||||||
timestamp = $8
|
timestamp = $8
|
||||||
# Extract date/time components (handles format: DD/MMM/YYYY:HH:MM:SS)
|
# Extract date/time components (handles format: DD/MMM/YYYY:HH:MM:SS)
|
||||||
@@ -716,7 +651,7 @@ detect_botnets() {
|
|||||||
time_key = ts[3] ts[2] ts[1] "_" ts[4] ts[5]
|
time_key = ts[3] ts[2] ts[1] "_" ts[4] ts[5]
|
||||||
print ip "|" time_key
|
print ip "|" time_key
|
||||||
}
|
}
|
||||||
}' "$TEMP_DIR/parsed_logs.txt" | \
|
}' | \
|
||||||
sort | uniq -c | \
|
sort | uniq -c | \
|
||||||
awk '$1 > 50 {print $1 " " $2}' | \
|
awk '$1 > 50 {print $1 " " $2}' | \
|
||||||
awk -F'|' '{print $1}' | \
|
awk -F'|' '{print $1}' | \
|
||||||
@@ -811,13 +746,13 @@ analyze_time_series() {
|
|||||||
print_info "Analyzing time-series patterns..."
|
print_info "Analyzing time-series patterns..."
|
||||||
|
|
||||||
# Extract hourly bot traffic
|
# Extract hourly bot traffic
|
||||||
awk -F'|' '$9 != "unknown" {
|
zcat "$TEMP_DIR/classified_bots.txt.gz" | awk -F'|' '$9 != "unknown" {
|
||||||
timestamp = $8
|
timestamp = $8
|
||||||
if (match(timestamp, /([0-9]{2})\/([A-Za-z]{3})\/([0-9]{4}):([0-9]{2}):([0-9]{2}):([0-9]{2})/, ts)) {
|
if (match(timestamp, /([0-9]{2})\/([A-Za-z]{3})\/([0-9]{4}):([0-9]{2}):([0-9]{2}):([0-9]{2})/, ts)) {
|
||||||
hour = ts[4]
|
hour = ts[4]
|
||||||
print hour
|
print hour
|
||||||
}
|
}
|
||||||
}' "$TEMP_DIR/classified_bots.txt" | sort | uniq -c > "$TEMP_DIR/hourly_bot_traffic.txt"
|
}' | sort | uniq -c > "$TEMP_DIR/hourly_bot_traffic.txt"
|
||||||
|
|
||||||
# Extract hourly attack traffic
|
# Extract hourly attack traffic
|
||||||
if [ -f "$TEMP_DIR/attack_vectors_raw.txt" ]; then
|
if [ -f "$TEMP_DIR/attack_vectors_raw.txt" ]; then
|
||||||
@@ -828,7 +763,7 @@ analyze_time_series() {
|
|||||||
hour = ts[4]
|
hour = ts[4]
|
||||||
print hour
|
print hour
|
||||||
}
|
}
|
||||||
}' "$TEMP_DIR/attack_vectors_raw.txt" "$TEMP_DIR/parsed_logs.txt" | sort | uniq -c > "$TEMP_DIR/hourly_attack_traffic.txt"
|
}' "$TEMP_DIR/attack_vectors_raw.txt" <(zcat "$TEMP_DIR/parsed_logs.txt.gz") | sort | uniq -c > "$TEMP_DIR/hourly_attack_traffic.txt"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
print_success "Time-series analysis complete"
|
print_success "Time-series analysis complete"
|
||||||
@@ -845,7 +780,7 @@ calculate_threat_scores() {
|
|||||||
declare -A ip_request_counts
|
declare -A ip_request_counts
|
||||||
while IFS='|' read -r ip rest; do
|
while IFS='|' read -r ip rest; do
|
||||||
((ip_request_counts["$ip"]++))
|
((ip_request_counts["$ip"]++))
|
||||||
done < "$TEMP_DIR/parsed_logs.txt"
|
done < <(zcat "$TEMP_DIR/parsed_logs.txt.gz")
|
||||||
|
|
||||||
# Build hash tables from threat files for O(1) lookups
|
# Build hash tables from threat files for O(1) lookups
|
||||||
declare -A threat_ips_sqli threat_ips_xss threat_ips_path threat_ips_rce threat_ips_login
|
declare -A threat_ips_sqli threat_ips_xss threat_ips_path threat_ips_rce threat_ips_login
|
||||||
@@ -933,6 +868,26 @@ calculate_threat_scores() {
|
|||||||
scan_404=${threat_404_count[$ip]:-0}
|
scan_404=${threat_404_count[$ip]:-0}
|
||||||
[ "$scan_404" -gt 50 ] 2>/dev/null && score=$((score + 3))
|
[ "$scan_404" -gt 50 ] 2>/dev/null && score=$((score + 3))
|
||||||
|
|
||||||
|
# Threat Intelligence Enrichment (from external sources)
|
||||||
|
# Check AbuseIPDB reputation
|
||||||
|
local abuse_data=$(check_abuseipdb "$ip" 2>/dev/null || echo "0|0|Unknown|Unknown")
|
||||||
|
IFS='|' read -r abuse_confidence abuse_reports abuse_country abuse_isp <<< "$abuse_data"
|
||||||
|
|
||||||
|
# Add bonus for known malicious IPs
|
||||||
|
if [ "$abuse_confidence" -ge 75 ]; then
|
||||||
|
score=$((score + 15)) # High confidence malicious
|
||||||
|
elif [ "$abuse_confidence" -ge 50 ]; then
|
||||||
|
score=$((score + 8)) # Moderate confidence
|
||||||
|
elif [ "$abuse_confidence" -ge 25 ]; then
|
||||||
|
score=$((score + 3)) # Low confidence
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Geographic risk assessment
|
||||||
|
local geo_country=$(get_country_code "$ip" 2>/dev/null || echo "XX")
|
||||||
|
if is_high_risk_country "$geo_country" 2>/dev/null; then
|
||||||
|
score=$((score + 5)) # High-risk country bonus
|
||||||
|
fi
|
||||||
|
|
||||||
# Cap at 100
|
# Cap at 100
|
||||||
[ $score -gt 100 ] && score=100
|
[ $score -gt 100 ] && score=100
|
||||||
|
|
||||||
@@ -971,7 +926,7 @@ detect_false_positives() {
|
|||||||
print_info "Detecting legitimate services (false positives)..."
|
print_info "Detecting legitimate services (false positives)..."
|
||||||
|
|
||||||
# Known monitoring service patterns
|
# Known monitoring service patterns
|
||||||
awk -F'|' '{
|
zcat "$TEMP_DIR/parsed_logs.txt.gz" | awk -F'|' '{
|
||||||
ip = $1
|
ip = $1
|
||||||
domain = $2
|
domain = $2
|
||||||
url = $3
|
url = $3
|
||||||
@@ -997,7 +952,7 @@ detect_false_positives() {
|
|||||||
else if (match(ua, /jetpack|vaultpress|updraftplus/)) {
|
else if (match(ua, /jetpack|vaultpress|updraftplus/)) {
|
||||||
print ip "|Backup Service|" ua "|" domain
|
print ip "|Backup Service|" ua "|" domain
|
||||||
}
|
}
|
||||||
}' "$TEMP_DIR/parsed_logs.txt" | sort -u > "$TEMP_DIR/false_positives.txt"
|
}' | sort -u > "$TEMP_DIR/false_positives.txt"
|
||||||
|
|
||||||
print_success "False positive detection complete"
|
print_success "False positive detection complete"
|
||||||
}
|
}
|
||||||
@@ -1010,31 +965,31 @@ generate_statistics() {
|
|||||||
print_info "Generating statistics..."
|
print_info "Generating statistics..."
|
||||||
|
|
||||||
# Top 5 bots by request count
|
# Top 5 bots by request count
|
||||||
awk -F'|' '$9 != "unknown" {print $10}' "$TEMP_DIR/classified_bots.txt" | \
|
zcat "$TEMP_DIR/classified_bots.txt.gz" | awk -F'|' '$9 != "unknown" {print $10}' | \
|
||||||
sort | uniq -c | sort -rn | head -5 > "$TEMP_DIR/top_bots.txt"
|
sort | uniq -c | sort -rn | head -5 > "$TEMP_DIR/top_bots.txt"
|
||||||
|
|
||||||
# Top 5 most-hit sites
|
# Top 5 most-hit sites
|
||||||
awk -F'|' '{print $2}' "$TEMP_DIR/parsed_logs.txt" | \
|
zcat "$TEMP_DIR/parsed_logs.txt.gz" | awk -F'|' '{print $2}' | \
|
||||||
sort | uniq -c | sort -rn | head -5 > "$TEMP_DIR/top_sites.txt"
|
sort | uniq -c | sort -rn | head -5 > "$TEMP_DIR/top_sites.txt"
|
||||||
|
|
||||||
# Top 5 most-hit URLs
|
# Top 5 most-hit URLs
|
||||||
awk -F'|' '{print $2"|"$3}' "$TEMP_DIR/parsed_logs.txt" | \
|
zcat "$TEMP_DIR/parsed_logs.txt.gz" | awk -F'|' '{print $2"|"$3}' | \
|
||||||
sort | uniq -c | sort -rn | head -5 > "$TEMP_DIR/top_urls.txt"
|
sort | uniq -c | sort -rn | head -5 > "$TEMP_DIR/top_urls.txt"
|
||||||
|
|
||||||
# Top 5 IP addresses by request count
|
# Top 5 IP addresses by request count
|
||||||
awk -F'|' '{print $1}' "$TEMP_DIR/parsed_logs.txt" | \
|
zcat "$TEMP_DIR/parsed_logs.txt.gz" | awk -F'|' '{print $1}' | \
|
||||||
sort | uniq -c | sort -rn | head -5 > "$TEMP_DIR/top_ips.txt"
|
sort | uniq -c | sort -rn | head -5 > "$TEMP_DIR/top_ips.txt"
|
||||||
|
|
||||||
# Traffic breakdown by bot type
|
# Traffic breakdown by bot type
|
||||||
awk -F'|' '{print $9}' "$TEMP_DIR/classified_bots.txt" | \
|
zcat "$TEMP_DIR/classified_bots.txt.gz" | awk -F'|' '{print $9}' | \
|
||||||
sort | uniq -c | sort -rn > "$TEMP_DIR/traffic_breakdown.txt"
|
sort | uniq -c | sort -rn > "$TEMP_DIR/traffic_breakdown.txt"
|
||||||
|
|
||||||
# Per-domain traffic sources
|
# Per-domain traffic sources
|
||||||
while read -r domain; do
|
while read -r domain; do
|
||||||
echo "$domain" > "$TEMP_DIR/domain_${domain}_stats.txt"
|
echo "$domain" > "$TEMP_DIR/domain_${domain}_stats.txt"
|
||||||
grep "|$domain|" "$TEMP_DIR/classified_bots.txt" | \
|
zcat "$TEMP_DIR/classified_bots.txt.gz" | grep "|$domain|" | \
|
||||||
awk -F'|' '{print $9}' | sort | uniq -c | sort -rn >> "$TEMP_DIR/domain_${domain}_stats.txt"
|
awk -F'|' '{print $9}' | sort | uniq -c | sort -rn >> "$TEMP_DIR/domain_${domain}_stats.txt"
|
||||||
done < <(awk -F'|' '{print $2}' "$TEMP_DIR/parsed_logs.txt" | sort -u)
|
done < <(zcat "$TEMP_DIR/parsed_logs.txt.gz" | awk -F'|' '{print $2}' | sort -u)
|
||||||
|
|
||||||
print_success "Statistics generated"
|
print_success "Statistics generated"
|
||||||
}
|
}
|
||||||
@@ -1118,19 +1073,19 @@ generate_report() {
|
|||||||
# QUICK STATS DASHBOARD
|
# QUICK STATS DASHBOARD
|
||||||
print_header "QUICK STATS DASHBOARD"
|
print_header "QUICK STATS DASHBOARD"
|
||||||
|
|
||||||
total_requests=$(wc -l < "$TEMP_DIR/parsed_logs.txt")
|
total_requests=$(zcat "$TEMP_DIR/parsed_logs.txt.gz" | wc -l)
|
||||||
unique_ips=$(awk -F'|' '{print $1}' "$TEMP_DIR/parsed_logs.txt" | sort -u | wc -l)
|
unique_ips=$(zcat "$TEMP_DIR/parsed_logs.txt.gz" | awk -F'|' '{print $1}' | sort -u | wc -l)
|
||||||
unique_domains=$(awk -F'|' '{print $2}' "$TEMP_DIR/parsed_logs.txt" | sort -u | wc -l)
|
unique_domains=$(zcat "$TEMP_DIR/parsed_logs.txt.gz" | awk -F'|' '{print $2}' | sort -u | wc -l)
|
||||||
bot_requests=$(awk -F'|' '$9 != "unknown"' "$TEMP_DIR/classified_bots.txt" | wc -l)
|
bot_requests=$(zcat "$TEMP_DIR/classified_bots.txt.gz" | awk -F'|' '$9 != "unknown"' | wc -l)
|
||||||
|
|
||||||
# Count private/internal IPs (excluded from threat analysis)
|
# Count private/internal IPs (excluded from threat analysis)
|
||||||
private_ips=$(awk -F'|' '{print $1}' "$TEMP_DIR/parsed_logs.txt" | sort -u | grep -E '^(127\.|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.|169\.254\.)' | wc -l)
|
private_ips=$(zcat "$TEMP_DIR/parsed_logs.txt.gz" | awk -F'|' '{print $1}' | sort -u | grep -E '^(127\.|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.|169\.254\.)' | wc -l)
|
||||||
|
|
||||||
# Count server's own IPs in the logs
|
# Count server's own IPs in the logs
|
||||||
server_ip_hits=0
|
server_ip_hits=0
|
||||||
if [ -f "$TEMP_DIR/server_ips.txt" ] && [ -s "$TEMP_DIR/server_ips.txt" ]; then
|
if [ -f "$TEMP_DIR/server_ips.txt" ] && [ -s "$TEMP_DIR/server_ips.txt" ]; then
|
||||||
while read -r server_ip; do
|
while read -r server_ip; do
|
||||||
if grep -q "^$server_ip|" "$TEMP_DIR/parsed_logs.txt" 2>/dev/null; then
|
if zcat "$TEMP_DIR/parsed_logs.txt.gz" | grep -q "^$server_ip|" 2>/dev/null; then
|
||||||
server_ip_hits=$((server_ip_hits + 1))
|
server_ip_hits=$((server_ip_hits + 1))
|
||||||
fi
|
fi
|
||||||
done < "$TEMP_DIR/server_ips.txt"
|
done < "$TEMP_DIR/server_ips.txt"
|
||||||
|
|||||||
@@ -55,23 +55,35 @@ touch "$TEMP_DIR/ip_data"
|
|||||||
echo "0" > "$TEMP_DIR/event_counter"
|
echo "0" > "$TEMP_DIR/event_counter"
|
||||||
echo "0" > "$TEMP_DIR/total_blocks"
|
echo "0" > "$TEMP_DIR/total_blocks"
|
||||||
|
|
||||||
# Save snapshot of IP data (for persistence across restarts)
|
# Initialize blocked IPs cache immediately on startup
|
||||||
save_snapshot() {
|
{
|
||||||
{
|
# Get CSF temporary blocks - extract just the IP address
|
||||||
for ip in "${!IP_DATA[@]}"; do
|
if command -v csf &>/dev/null; then
|
||||||
echo "$ip=${IP_DATA[$ip]}"
|
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}$'
|
||||||
done
|
fi
|
||||||
} > "$SNAPSHOT_DIR/ip_data_snapshot" 2>/dev/null
|
|
||||||
}
|
# 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 function
|
||||||
cleanup() {
|
cleanup() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "Stopping monitoring processes..."
|
echo "Stopping monitoring processes..."
|
||||||
|
|
||||||
# Save snapshot before exit
|
|
||||||
save_snapshot
|
|
||||||
|
|
||||||
# Kill all child processes
|
# Kill all child processes
|
||||||
pkill -P $$ 2>/dev/null
|
pkill -P $$ 2>/dev/null
|
||||||
|
|
||||||
@@ -104,13 +116,6 @@ VELOCITY_WINDOW=3600 # 1 hour in seconds
|
|||||||
DECAY_CHECK_INTERVAL=1800 # Check for decay every 30 minutes
|
DECAY_CHECK_INTERVAL=1800 # Check for decay every 30 minutes
|
||||||
LAST_DECAY_CHECK=$START_TIME
|
LAST_DECAY_CHECK=$START_TIME
|
||||||
|
|
||||||
# Load persistent data from previous sessions if exists
|
|
||||||
if [ -f "$SNAPSHOT_DIR/ip_data_snapshot" ]; then
|
|
||||||
while IFS='=' read -r ip data; do
|
|
||||||
[ -n "$ip" ] && IP_DATA[$ip]="$data"
|
|
||||||
done < "$SNAPSHOT_DIR/ip_data_snapshot"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Hide cursor for cleaner display
|
# Hide cursor for cleaner display
|
||||||
command -v tput &>/dev/null && tput civis
|
command -v tput &>/dev/null && tput civis
|
||||||
|
|
||||||
@@ -691,20 +696,171 @@ calculate_context_bonus() {
|
|||||||
echo "${bonus}|${reasons}"
|
echo "${bonus}|${reasons}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if IP is currently blocked in CSF/iptables
|
# 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() {
|
is_ip_blocked() {
|
||||||
local ip="$1"
|
local ip="$1"
|
||||||
|
|
||||||
# Check CSF deny list
|
# Use cached blocked IPs list (refreshed every 10 seconds by background process)
|
||||||
if command -v csf &>/dev/null; then
|
if [ -f "$TEMP_DIR/blocked_ips_cache" ]; then
|
||||||
if csf -g "$ip" 2>/dev/null | grep -q "DENY"; then
|
if grep -q "^$ip$" "$TEMP_DIR/blocked_ips_cache" 2>/dev/null; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
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
|
# Check iptables directly
|
||||||
if command -v iptables &>/dev/null; then
|
if command -v iptables &>/dev/null; then
|
||||||
if iptables -L -n 2>/dev/null | grep -q "$ip"; then
|
if iptables -L INPUT -n 2>/dev/null | grep -q "$ip"; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -714,7 +870,7 @@ is_ip_blocked() {
|
|||||||
|
|
||||||
# Get threat level from score
|
# Get threat level from score
|
||||||
get_threat_level() {
|
get_threat_level() {
|
||||||
local score="$1"
|
local score="${1:-0}"
|
||||||
|
|
||||||
if [ "$score" -ge "$THREAT_THRESHOLD_CRITICAL" ]; then
|
if [ "$score" -ge "$THREAT_THRESHOLD_CRITICAL" ]; then
|
||||||
echo "CRITICAL"
|
echo "CRITICAL"
|
||||||
@@ -776,12 +932,40 @@ draw_header() {
|
|||||||
draw_intelligence_panel() {
|
draw_intelligence_panel() {
|
||||||
echo -e "${HIGH_COLOR}┌─ THREAT INTELLIGENCE ──────────────────────────────────────────────────────┐${NC}"
|
echo -e "${HIGH_COLOR}┌─ THREAT INTELLIGENCE ──────────────────────────────────────────────────────┐${NC}"
|
||||||
|
|
||||||
# Get top IPs by threat score
|
# Debug: Show cache status
|
||||||
local count=0
|
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
|
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]}"
|
IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "${IP_DATA[$ip]}"
|
||||||
echo "$score|$ip|$hits|$bot_type|$attacks|$ban_count|$rep_score"
|
ip_list+="$score|$ip|$hits|$bot_type|$attacks|$ban_count|$rep_score"$'\n'
|
||||||
done | sort -t'|' -k1 -rn | head -10 | while IFS='|' read -r score ip hits bot_type attacks ban_count rep_score; do
|
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 level=$(get_threat_level "$score")
|
||||||
local color=$(get_threat_color "$level")
|
local color=$(get_threat_color "$level")
|
||||||
@@ -824,6 +1008,14 @@ draw_intelligence_panel() {
|
|||||||
|
|
||||||
echo -e "${color}${status_line}${NC}"
|
echo -e "${color}${status_line}${NC}"
|
||||||
done
|
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 -e "${HIGH_COLOR}└────────────────────────────────────────────────────────────────────────────┘${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -959,6 +1151,11 @@ show_blocking_menu() {
|
|||||||
for ip in "${!IP_DATA[@]}"; do
|
for ip in "${!IP_DATA[@]}"; do
|
||||||
IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "${IP_DATA[$ip]}"
|
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
|
# Skip if score too low or already blocked
|
||||||
[ "$score" -lt 60 ] && continue
|
[ "$score" -lt 60 ] && continue
|
||||||
is_ip_blocked "$ip" 2>/dev/null && continue
|
is_ip_blocked "$ip" 2>/dev/null && continue
|
||||||
@@ -1012,17 +1209,24 @@ show_blocking_menu() {
|
|||||||
elif [ "$choice" = "a" ]; then
|
elif [ "$choice" = "a" ]; then
|
||||||
# Block all IPs with score >= 80
|
# Block all IPs with score >= 80
|
||||||
local blocked=0
|
local blocked=0
|
||||||
|
local failed=0
|
||||||
for entry in "${blockable_list[@]}"; do
|
for entry in "${blockable_list[@]}"; do
|
||||||
IFS='|' read -r ip score hits attacks <<< "$entry"
|
IFS='|' read -r ip score hits attacks <<< "$entry"
|
||||||
[ "$score" -lt 80 ] && continue
|
[ "$score" -lt 80 ] && continue
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
block_ip_temporary "$ip" 1 "Auto-block: High threat (score $score)"
|
if block_ip_temporary "$ip" 1 "Auto-block: High threat (score $score)"; then
|
||||||
((blocked++))
|
((blocked++))
|
||||||
|
else
|
||||||
|
((failed++))
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Blocked $blocked IPs"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "✓ Successfully blocked: $blocked IPs"
|
||||||
|
[ $failed -gt 0 ] && echo "✗ Failed to block: $failed IPs"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
read -p "Press Enter to continue..."
|
read -p "Press Enter to continue..."
|
||||||
elif [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le ${#blockable_list[@]} ]; then
|
elif [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le ${#blockable_list[@]} ]; then
|
||||||
# Block specific IP
|
# Block specific IP
|
||||||
@@ -1076,7 +1280,7 @@ monitor_apache_logs() {
|
|||||||
|
|
||||||
# Monitor all log files
|
# Monitor all log files
|
||||||
local event_count=0
|
local event_count=0
|
||||||
tail -f "${log_files[@]}" 2>/dev/null | while read -r line; do
|
tail -n 0 -F "${log_files[@]}" 2>/dev/null | while read -r line; do
|
||||||
# Increment event counter (update file every 10 events for performance)
|
# Increment event counter (update file every 10 events for performance)
|
||||||
((event_count++))
|
((event_count++))
|
||||||
if [ $((event_count % 10)) -eq 0 ]; then
|
if [ $((event_count % 10)) -eq 0 ]; then
|
||||||
@@ -1178,7 +1382,12 @@ monitor_ssh_attacks() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Process as BRUTEFORCE attack
|
# Process as BRUTEFORCE attack
|
||||||
local current_data="${IP_DATA[$ip]:-0|0|human||0|0}"
|
# 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"
|
IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$current_data"
|
||||||
|
|
||||||
# Increment hits
|
# Increment hits
|
||||||
@@ -1256,8 +1465,9 @@ monitor_ssh_attacks() {
|
|||||||
# Cap at 100
|
# Cap at 100
|
||||||
[ $score -gt 100 ] && score=100
|
[ $score -gt 100 ] && score=100
|
||||||
|
|
||||||
# Update IP_DATA
|
# Update ip_data file directly (subshells can't access IP_DATA array)
|
||||||
IP_DATA[$ip]="$score|$hits|$bot_type|$attacks|$ban_count|$rep_score"
|
local ip_file="$TEMP_DIR/ip_${ip//\./_}"
|
||||||
|
echo "$score|$hits|$bot_type|$attacks|$ban_count|$rep_score" > "$ip_file"
|
||||||
|
|
||||||
# Store block reasons for CSF
|
# Store block reasons for CSF
|
||||||
if [ -n "$block_reasons" ]; then
|
if [ -n "$block_reasons" ]; then
|
||||||
@@ -1544,7 +1754,12 @@ monitor_email_attacks() {
|
|||||||
[[ "$ip" =~ ^127\. ]] || [[ "$ip" =~ ^10\. ]] || [[ "$ip" =~ ^192\.168\. ]] || [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[01])\. ]] && continue
|
[[ "$ip" =~ ^127\. ]] || [[ "$ip" =~ ^10\. ]] || [[ "$ip" =~ ^192\.168\. ]] || [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[01])\. ]] && continue
|
||||||
|
|
||||||
# Process as BRUTEFORCE attack
|
# Process as BRUTEFORCE attack
|
||||||
local current_data="${IP_DATA[$ip]:-0|0|human||0|0}"
|
# 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"
|
IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$current_data"
|
||||||
|
|
||||||
hits=$((hits + 1))
|
hits=$((hits + 1))
|
||||||
@@ -1606,7 +1821,9 @@ monitor_email_attacks() {
|
|||||||
|
|
||||||
[ $score -gt 100 ] && score=100
|
[ $score -gt 100 ] && score=100
|
||||||
|
|
||||||
IP_DATA[$ip]="$score|$hits|$bot_type|$attacks|$ban_count|$rep_score"
|
# 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
|
# Store block reasons for CSF
|
||||||
if [ -n "$block_reasons" ]; then
|
if [ -n "$block_reasons" ]; then
|
||||||
@@ -1651,7 +1868,12 @@ monitor_ftp_attacks() {
|
|||||||
[[ "$ip" =~ ^127\. ]] || [[ "$ip" =~ ^10\. ]] || [[ "$ip" =~ ^192\.168\. ]] || [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[01])\. ]] && continue
|
[[ "$ip" =~ ^127\. ]] || [[ "$ip" =~ ^10\. ]] || [[ "$ip" =~ ^192\.168\. ]] || [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[01])\. ]] && continue
|
||||||
|
|
||||||
# Process as BRUTEFORCE attack
|
# Process as BRUTEFORCE attack
|
||||||
local current_data="${IP_DATA[$ip]:-0|0|human||0|0}"
|
# 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"
|
IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$current_data"
|
||||||
|
|
||||||
hits=$((hits + 1))
|
hits=$((hits + 1))
|
||||||
@@ -1713,7 +1935,9 @@ monitor_ftp_attacks() {
|
|||||||
|
|
||||||
[ $score -gt 100 ] && score=100
|
[ $score -gt 100 ] && score=100
|
||||||
|
|
||||||
IP_DATA[$ip]="$score|$hits|$bot_type|$attacks|$ban_count|$rep_score"
|
# 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
|
# Store block reasons for CSF
|
||||||
if [ -n "$block_reasons" ]; then
|
if [ -n "$block_reasons" ]; then
|
||||||
@@ -1758,7 +1982,12 @@ monitor_database_attacks() {
|
|||||||
[[ "$ip" =~ ^127\. ]] || [[ "$ip" =~ ^10\. ]] || [[ "$ip" =~ ^192\.168\. ]] || [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[01])\. ]] && continue
|
[[ "$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)
|
# Process as SQL_INJECTION attack (database level)
|
||||||
local current_data="${IP_DATA[$ip]:-0|0|human||0|0}"
|
# 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"
|
IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$current_data"
|
||||||
|
|
||||||
hits=$((hits + 1))
|
hits=$((hits + 1))
|
||||||
@@ -1822,7 +2051,9 @@ monitor_database_attacks() {
|
|||||||
|
|
||||||
[ $score -gt 100 ] && score=100
|
[ $score -gt 100 ] && score=100
|
||||||
|
|
||||||
IP_DATA[$ip]="$score|$hits|$bot_type|$attacks|$ban_count|$rep_score"
|
# 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
|
# Store block reasons for CSF
|
||||||
if [ -n "$block_reasons" ]; then
|
if [ -n "$block_reasons" ]; then
|
||||||
@@ -1955,6 +2186,30 @@ auto_mitigation_engine
|
|||||||
done
|
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
|
# Periodic snapshot saving in background
|
||||||
(
|
(
|
||||||
while true; do
|
while true; do
|
||||||
@@ -1966,13 +2221,41 @@ auto_mitigation_engine
|
|||||||
# Main dashboard loop
|
# Main dashboard loop
|
||||||
LOOP_COUNT=0
|
LOOP_COUNT=0
|
||||||
while true; do
|
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_header
|
||||||
draw_intelligence_panel
|
draw_intelligence_panel
|
||||||
draw_attack_breakdown
|
draw_attack_breakdown
|
||||||
draw_live_feed
|
draw_live_feed
|
||||||
draw_quick_actions
|
draw_quick_actions
|
||||||
|
|
||||||
# Write IP data to temp file for auto-mitigation engine (every loop)
|
# Write IP_DATA to ip_data file for auto-mitigation engine
|
||||||
{
|
{
|
||||||
for ip in "${!IP_DATA[@]}"; do
|
for ip in "${!IP_DATA[@]}"; do
|
||||||
echo "$ip=${IP_DATA[$ip]}"
|
echo "$ip=${IP_DATA[$ip]}"
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ source "$SCRIPT_DIR/lib/common-functions.sh" 2>/dev/null || true
|
|||||||
source "$SCRIPT_DIR/lib/system-detect.sh" 2>/dev/null || true
|
source "$SCRIPT_DIR/lib/system-detect.sh" 2>/dev/null || true
|
||||||
source "$SCRIPT_DIR/lib/user-manager.sh" 2>/dev/null || true
|
source "$SCRIPT_DIR/lib/user-manager.sh" 2>/dev/null || true
|
||||||
source "$SCRIPT_DIR/lib/reference-db.sh" 2>/dev/null || true
|
source "$SCRIPT_DIR/lib/reference-db.sh" 2>/dev/null || true
|
||||||
|
source "$SCRIPT_DIR/lib/ip-reputation.sh" 2>/dev/null || true
|
||||||
|
|
||||||
# Arrays for docroots and scanners
|
# Arrays for docroots and scanners
|
||||||
declare -a docroot_array
|
declare -a docroot_array
|
||||||
@@ -858,6 +859,49 @@ done
|
|||||||
sort -u "$INFECTED_LIST"
|
sort -u "$INFECTED_LIST"
|
||||||
echo ""
|
echo ""
|
||||||
echo "ACTION REQUIRED: Review and quarantine/remove infected files"
|
echo "ACTION REQUIRED: Review and quarantine/remove infected files"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# IP Reputation Integration: Flag IPs that uploaded malware
|
||||||
|
echo "----------------------------------------"
|
||||||
|
echo "Analyzing upload sources..."
|
||||||
|
echo "----------------------------------------"
|
||||||
|
|
||||||
|
# Correlate infected files with Apache logs to find uploading IPs
|
||||||
|
local flagged_ips=0
|
||||||
|
while read -r infected_file; do
|
||||||
|
# Extract file path components
|
||||||
|
local filename=$(basename "$infected_file")
|
||||||
|
local filepath=$(dirname "$infected_file")
|
||||||
|
|
||||||
|
# Try to find corresponding Apache access logs
|
||||||
|
# Look for POST requests to the directory containing the infected file
|
||||||
|
if [ -d "/var/log/apache2/domlogs" ]; then
|
||||||
|
# Search last 7 days of logs for POST requests to this path
|
||||||
|
find /var/log/apache2/domlogs -type f -name "*.com" -o -name "*.net" -o -name "*.org" 2>/dev/null | while read -r logfile; do
|
||||||
|
# Check if this log corresponds to the domain/user
|
||||||
|
grep -h "POST.*${filepath}" "$logfile" 2>/dev/null | tail -20 | while read -r logline; do
|
||||||
|
# Extract IP from Apache log line
|
||||||
|
local ip=$(echo "$logline" | awk '{print $1}')
|
||||||
|
if [ -n "$ip" ] && [[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
# Flag this IP in reputation database
|
||||||
|
if type flag_ip_attack &>/dev/null; then
|
||||||
|
flag_ip_attack "$ip" "RCE" 25 "Malware scanner: Uploaded $filename" >/dev/null 2>&1
|
||||||
|
echo " → Flagged IP: $ip (uploaded to $filepath)" >> "$LOG_DIR/flagged_ips.log"
|
||||||
|
((flagged_ips++))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
done < <(sort -u "$INFECTED_LIST" | head -20) # Limit to first 20 files to avoid long processing
|
||||||
|
|
||||||
|
if [ $flagged_ips -gt 0 ]; then
|
||||||
|
echo "✓ Flagged $flagged_ips IPs in reputation database"
|
||||||
|
echo " (See $LOG_DIR/flagged_ips.log for details)"
|
||||||
|
else
|
||||||
|
echo " No upload IPs identified (files may be older than log retention)"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
else
|
else
|
||||||
echo "✓ No infected files detected by automated scan."
|
echo "✓ No infected files detected by automated scan."
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -8,6 +8,15 @@
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
# Fix HISTFILE if set to non-existent path (prevents crashes on sourcing)
|
||||||
|
if [ -n "$HISTFILE" ]; then
|
||||||
|
HISTFILE_DIR="$(dirname "$HISTFILE" 2>/dev/null)"
|
||||||
|
if [ ! -d "$HISTFILE_DIR" ] 2>/dev/null; then
|
||||||
|
# Fallback to default history location
|
||||||
|
export HISTFILE="$HOME/.bash_history"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Check if being sourced or executed
|
# Check if being sourced or executed
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
echo "ERROR: This script must be sourced, not executed."
|
echo "ERROR: This script must be sourced, not executed."
|
||||||
|
|||||||
Reference in New Issue
Block a user