#!/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" }