682bd69cf8
QA scan found 4 library files with functions that weren't exported, making them unavailable in subshells and nested calls. Added export statements for: - lib/attack-signatures.sh: 3 functions - lib/http-attack-analyzer.sh: 5 functions - lib/email-functions.sh: 18 functions - lib/rate-anomaly-detector.sh: 9 functions Total: 35 functions now properly exported This ensures functions are available when libraries are sourced by scripts that spawn subshells or use process substitution.
303 lines
10 KiB
Bash
303 lines
10 KiB
Bash
#!/bin/bash
|
|
#
|
|
# HTTP Attack Analyzer
|
|
# Analyzes Apache/Nginx log entries for attack patterns using signature database
|
|
#
|
|
# Requires: attack-signatures.sh
|
|
|
|
# Source attack signatures
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "$SCRIPT_DIR/attack-signatures.sh" 2>/dev/null || {
|
|
echo "ERROR: attack-signatures.sh not found" >&2
|
|
exit 1
|
|
}
|
|
|
|
# Analyze a single HTTP request log line
|
|
# Input: Apache/Nginx combined log format
|
|
# Returns: threat_score||attack_types||matched_signatures||ip||uri
|
|
# Example: "85||SQLI,XSS||union_select,script_tag||192.168.1.100||/index.php?id=1"
|
|
analyze_http_log_line() {
|
|
local log_line="$1"
|
|
|
|
# Parse log line (Apache/Nginx combined format)
|
|
# 192.168.1.1 - - [12/Dec/2025:10:30:45 +0000] "GET /index.php?id=1 HTTP/1.1" 200 1234 "-" "Mozilla/5.0"
|
|
|
|
# Extract components using regex
|
|
if [[ "$log_line" =~ ^([0-9.]+)[[:space:]].*\"([A-Z]+)[[:space:]]([^[:space:]]+)[[:space:]]HTTP/[0-9.]+\"[[:space:]]([0-9]+)[[:space:]]([0-9-]+)[[:space:]]\"([^\"]*)\"[[:space:]]\"([^\"]*)\" ]]; then
|
|
local ip="${BASH_REMATCH[1]}"
|
|
local method="${BASH_REMATCH[2]}"
|
|
local uri="${BASH_REMATCH[3]}"
|
|
local status="${BASH_REMATCH[4]}"
|
|
local size="${BASH_REMATCH[5]}"
|
|
local referer="${BASH_REMATCH[6]}"
|
|
local user_agent="${BASH_REMATCH[7]}"
|
|
else
|
|
# Failed to parse
|
|
echo "0||PARSE_ERROR||||||"
|
|
return 1
|
|
fi
|
|
|
|
# Build complete request string for analysis
|
|
local full_request="$method $uri HTTP/1.1
|
|
Referer: $referer
|
|
User-Agent: $user_agent"
|
|
|
|
# Detect attacks using signature database
|
|
local attack_result=$(detect_all_attack_signatures "$full_request" 2>/dev/null)
|
|
|
|
if [ -n "$attack_result" ]; then
|
|
# Parse result: max_severity||match_count||matches...
|
|
local max_severity="${attack_result%%||*}"
|
|
local temp="${attack_result#*||}"
|
|
local match_count="${temp%%||*}"
|
|
local matches="${temp#*||}"
|
|
|
|
# Extract attack types and signatures
|
|
local attack_types=()
|
|
local signatures=()
|
|
|
|
# Parse each match (format: severity||category||pattern||description)
|
|
IFS=' ' read -ra match_array <<< "$matches"
|
|
for match in "${match_array[@]}"; do
|
|
# Extract category and pattern name
|
|
local match_sev="${match%%||*}"
|
|
local match_temp="${match#*||}"
|
|
local category="${match_temp%%||*}"
|
|
match_temp="${match_temp#*||}"
|
|
local pattern="${match_temp%%||*}"
|
|
|
|
attack_types+=("$category")
|
|
signatures+=("$pattern")
|
|
done
|
|
|
|
# Remove duplicates
|
|
local unique_types=$(printf '%s\n' "${attack_types[@]}" | sort -u | tr '\n' ',' | sed 's/,$//')
|
|
local unique_sigs=$(printf '%s\n' "${signatures[@]}" | sort -u | tr '\n' ',' | sed 's/,$//')
|
|
|
|
# Calculate final threat score
|
|
local threat_score=$max_severity
|
|
|
|
# Boost score for multiple attack types
|
|
if [ "$match_count" -gt 1 ]; then
|
|
threat_score=$((threat_score + (match_count - 1) * 5))
|
|
fi
|
|
|
|
# Boost for suspicious status codes
|
|
case "$status" in
|
|
200) threat_score=$((threat_score + 10)) ;; # Success = higher threat
|
|
500|502|503) threat_score=$((threat_score + 5)) ;; # Error might indicate exploit attempt
|
|
esac
|
|
|
|
# Cap at 100
|
|
[ "$threat_score" -gt 100 ] && threat_score=100
|
|
|
|
# Return: threat_score||attack_types||signatures||ip||uri
|
|
echo "$threat_score||${unique_types}||${unique_sigs}||$ip||$uri"
|
|
return 0
|
|
else
|
|
# No pattern matches - check for suspicious indicators
|
|
local suspicious_score=0
|
|
local indicators=()
|
|
|
|
# Unusual HTTP methods
|
|
case "$method" in
|
|
PUT|DELETE|TRACE|CONNECT|OPTIONS)
|
|
suspicious_score=$((suspicious_score + 30))
|
|
indicators+=("unusual_method:$method")
|
|
;;
|
|
esac
|
|
|
|
# Very long URIs (>500 chars)
|
|
if [ "${#uri}" -gt 500 ]; then
|
|
suspicious_score=$((suspicious_score + 20))
|
|
indicators+=("long_uri:${#uri}")
|
|
fi
|
|
|
|
# Multiple encoding layers
|
|
if echo "$uri" | grep -q '%25'; then
|
|
suspicious_score=$((suspicious_score + 25))
|
|
indicators+=("double_encoding")
|
|
fi
|
|
|
|
# Suspicious user agents
|
|
if echo "$user_agent" | grep -iEq "(nikto|sqlmap|nmap|masscan|burp|metasploit|acunetix|nessus|w3af)"; then
|
|
suspicious_score=$((suspicious_score + 40))
|
|
indicators+=("scanner_ua")
|
|
fi
|
|
|
|
# Empty or suspicious referer
|
|
if [ "$referer" = "-" ] && [ "$method" = "POST" ]; then
|
|
suspicious_score=$((suspicious_score + 15))
|
|
indicators+=("no_referer_post")
|
|
fi
|
|
|
|
# Excessive parameters (possible fuzzing)
|
|
local param_count=$(echo "$uri" | grep -o '&' | wc -l)
|
|
if [ "$param_count" -gt 20 ]; then
|
|
suspicious_score=$((suspicious_score + 20))
|
|
indicators+=("excessive_params:$param_count")
|
|
fi
|
|
|
|
if [ "$suspicious_score" -gt 0 ]; then
|
|
local indicator_str=$(IFS=,; echo "${indicators[*]}")
|
|
echo "$suspicious_score||SUSPICIOUS||${indicator_str}||$ip||$uri"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
# Clean request
|
|
echo "0||CLEAN||||$ip||$uri"
|
|
return 0
|
|
}
|
|
|
|
# Batch analyze multiple log lines
|
|
# Input: File path or stdin
|
|
# Output: Summary statistics + threat list
|
|
analyze_http_log_batch() {
|
|
local log_file="$1"
|
|
local time_window="${2:-300}" # Default 5 minutes (unused for now)
|
|
|
|
local total_requests=0
|
|
local clean_requests=0
|
|
local suspicious_requests=0
|
|
local attack_requests=0
|
|
local critical_attacks=0
|
|
|
|
declare -A ip_threats
|
|
declare -A attack_type_counts
|
|
|
|
# Process log lines
|
|
while IFS= read -r line; do
|
|
[ -z "$line" ] && continue
|
|
|
|
total_requests=$((total_requests + 1))
|
|
|
|
local result=$(analyze_http_log_line "$line")
|
|
local threat_score="${result%%||*}"
|
|
local temp="${result#*||}"
|
|
local attack_types="${temp%%||*}"
|
|
|
|
# Categorize
|
|
if [ "$threat_score" -eq 0 ]; then
|
|
clean_requests=$((clean_requests + 1))
|
|
elif [ "$threat_score" -lt 50 ]; then
|
|
suspicious_requests=$((suspicious_requests + 1))
|
|
else
|
|
attack_requests=$((attack_requests + 1))
|
|
|
|
# Count as critical if score >= 85
|
|
[ "$threat_score" -ge 85 ] && critical_attacks=$((critical_attacks + 1))
|
|
|
|
# Track by IP (extract IP from result)
|
|
local ip_temp="${result##*||}"
|
|
ip_temp="${ip_temp#*||}"
|
|
local ip="${ip_temp%%||*}"
|
|
|
|
ip_threats["$ip"]=$((${ip_threats[$ip]:-0} + threat_score))
|
|
|
|
# Track attack types
|
|
IFS=',' read -ra types <<< "$attack_types"
|
|
for type in "${types[@]}"; do
|
|
[ -n "$type" ] && attack_type_counts["$type"]=$((${attack_type_counts[$type]:-0} + 1))
|
|
done
|
|
fi
|
|
done < <(if [ -n "$log_file" ] && [ -f "$log_file" ]; then cat "$log_file"; else cat; fi)
|
|
|
|
# Generate summary
|
|
echo "SUMMARY||$total_requests||$clean_requests||$suspicious_requests||$attack_requests||$critical_attacks"
|
|
|
|
# Top threatening IPs
|
|
local top_ips=""
|
|
for ip in "${!ip_threats[@]}"; do
|
|
top_ips+="$ip:${ip_threats[$ip]} "
|
|
done
|
|
echo "TOP_IPS||$(echo "$top_ips" | tr ' ' '\n' | sort -t: -k2 -nr | head -10 | tr '\n' ' ' | sed 's/ $//')"
|
|
|
|
# Attack type distribution
|
|
local attack_dist=""
|
|
for type in "${!attack_type_counts[@]}"; do
|
|
attack_dist+="$type:${attack_type_counts[$type]} "
|
|
done
|
|
echo "ATTACK_TYPES||$(echo "$attack_dist" | tr ' ' '\n' | sort -t: -k2 -nr | tr '\n' ' ' | sed 's/ $//')"
|
|
}
|
|
|
|
# Real-time monitoring mode
|
|
# Watches log file and reports attacks as they happen
|
|
# Usage: monitor_http_log_realtime "/var/log/apache2/access_log" "callback_function_name"
|
|
monitor_http_log_realtime() {
|
|
local log_file="$1"
|
|
local callback_function="$2" # Function to call with results
|
|
|
|
if [ ! -f "$log_file" ]; then
|
|
echo "ERROR: Log file not found: $log_file" >&2
|
|
return 1
|
|
fi
|
|
|
|
tail -f "$log_file" 2>/dev/null | while IFS= read -r line; do
|
|
[ -z "$line" ] && continue
|
|
|
|
local result=$(analyze_http_log_line "$line")
|
|
local threat_score="${result%%||*}"
|
|
|
|
# Only report threats (score > 0)
|
|
if [ "$threat_score" -gt 0 ]; then
|
|
# Call callback function with result
|
|
if type "$callback_function" &>/dev/null; then
|
|
"$callback_function" "$result" "$line"
|
|
else
|
|
# Default: print to stdout
|
|
echo "[THREAT:$threat_score] $result"
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Parse analysis result into components
|
|
# Usage: parse_http_analysis_result "$result"
|
|
# Sets global variables: THREAT_SCORE, ATTACK_TYPES, SIGNATURES, IP_ADDR, URI
|
|
parse_http_analysis_result() {
|
|
local result="$1"
|
|
|
|
THREAT_SCORE="${result%%||*}"
|
|
local temp="${result#*||}"
|
|
ATTACK_TYPES="${temp%%||*}"
|
|
temp="${temp#*||}"
|
|
SIGNATURES="${temp%%||*}"
|
|
temp="${temp#*||}"
|
|
IP_ADDR="${temp%%||*}"
|
|
URI="${temp#*||}"
|
|
}
|
|
|
|
# Format threat for display
|
|
# Usage: format_threat_display "$result"
|
|
format_threat_display() {
|
|
local result="$1"
|
|
|
|
parse_http_analysis_result "$result"
|
|
|
|
local severity_label="LOW"
|
|
local color="\033[0;36m" # Cyan
|
|
|
|
if [ "$THREAT_SCORE" -ge 85 ]; then
|
|
severity_label="CRITICAL"
|
|
color="\033[0;31m" # Red
|
|
elif [ "$THREAT_SCORE" -ge 70 ]; then
|
|
severity_label="HIGH"
|
|
color="\033[1;31m" # Bright red
|
|
elif [ "$THREAT_SCORE" -ge 50 ]; then
|
|
severity_label="MEDIUM"
|
|
color="\033[1;33m" # Yellow
|
|
fi
|
|
|
|
echo -e "${color}[$severity_label:$THREAT_SCORE]${NC} $IP_ADDR → $ATTACK_TYPES"
|
|
echo " URI: ${URI:0:100}"
|
|
[ -n "$SIGNATURES" ] && echo " Signatures: $SIGNATURES"
|
|
}
|
|
|
|
# Export functions for use in subshells
|
|
export -f analyze_http_log_line
|
|
export -f analyze_http_log_batch
|
|
export -f monitor_http_log_realtime
|
|
export -f parse_http_analysis_result
|
|
export -f format_threat_display
|