From 5e517b9858eae0325fcbc28283e47979138903cd Mon Sep 17 00:00:00 2001 From: cschantz Date: Mon, 3 Nov 2025 19:08:53 -0500 Subject: [PATCH] Add user/domain filtering and ModSecurity support to error analyzer - Added per-user analysis (select cPanel user from list) - Added per-domain analysis (enter specific domain) - ModSecurity audit log integration - Detects SQL injection, XSS, command injection blocks by ModSec - Filters logs by user or domain when specified - Shows ModSecurity blocks in error report - Provides specific recommendations for ModSec false positives - Matches bot analyzer's filtering capabilities --- modules/website/website-error-analyzer.sh | 169 ++++++++++++++++++++-- 1 file changed, 155 insertions(+), 14 deletions(-) diff --git a/modules/website/website-error-analyzer.sh b/modules/website/website-error-analyzer.sh index eb7aac5..e41a818 100755 --- a/modules/website/website-error-analyzer.sh +++ b/modules/website/website-error-analyzer.sh @@ -11,11 +11,15 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" source "$SCRIPT_DIR/lib/common-functions.sh" source "$SCRIPT_DIR/lib/system-detect.sh" +source "$SCRIPT_DIR/lib/user-manager.sh" # Configuration APACHE_ERROR_LOG="/var/log/apache2/error_log" DOMLOGS_DIR="/var/log/apache2/domlogs" +MODSEC_AUDIT_LOG="/usr/local/apache/logs/modsec_audit.log" HOURS_TO_ANALYZE=24 +FILTER_USER="" +FILTER_DOMAIN="" print_banner "Intelligent Website Error Analyzer" @@ -37,6 +41,39 @@ echo " ✓ Permission issues blocking access" echo " ✓ Memory exhaustion affecting performance" echo "" +# Ask for filtering scope +echo -e "${CYAN}Analysis Scope:${NC}" +echo " 1) All users/domains (default)" +echo " 2) Specific cPanel user" +echo " 3) Specific domain" +echo "" +read -p "Select option [1]: " scope_choice +scope_choice=${scope_choice:-1} + +case $scope_choice in + 2) + # Select specific user + select_user_interactive "Select cPanel user to analyze" + if [ -n "$SELECTED_USER" ]; then + FILTER_USER="$SELECTED_USER" + echo "→ Filtering for user: $FILTER_USER" + fi + ;; + 3) + # Enter specific domain + echo "" + read -p "Enter domain name (e.g., example.com): " FILTER_DOMAIN + if [ -n "$FILTER_DOMAIN" ]; then + echo "→ Filtering for domain: $FILTER_DOMAIN" + fi + ;; + *) + echo "→ Analyzing all users/domains" + ;; +esac + +echo "" + # Ask for time range echo -e "${CYAN}How far back should we analyze?${NC}" echo " 1) Last 1 hour" @@ -74,26 +111,77 @@ LOG_FILES_LIST="$TEMP_DIR/log_files.txt" echo "→ Discovering log file locations..." > "$LOG_FILES_LIST" -# Apache main error log -for log in /var/log/apache2/error_log /usr/local/apache/logs/error_log /var/log/httpd/error_log; do - [ -f "$log" ] && echo "$log|apache_main" >> "$LOG_FILES_LIST" -done +# Apache main error log (only if not filtering by user) +if [ -z "$FILTER_USER" ] && [ -z "$FILTER_DOMAIN" ]; then + for log in /var/log/apache2/error_log /usr/local/apache/logs/error_log /var/log/httpd/error_log; do + [ -f "$log" ] && echo "$log|apache_main" >> "$LOG_FILES_LIST" + done +fi # Per-domain PHP error logs in public_html -find /home/*/public_html -name "error_log" -type f 2>/dev/null | while read -r log; do - domain=$(echo "$log" | grep -oE '/home/[^/]+' | sed 's|/home/||') - echo "$log|php_$domain" >> "$LOG_FILES_LIST" -done +if [ -n "$FILTER_USER" ]; then + # Specific user + find "/home/$FILTER_USER" -name "error_log" -type f 2>/dev/null | while read -r log; do + echo "$log|php_$FILTER_USER" >> "$LOG_FILES_LIST" + done +elif [ -n "$FILTER_DOMAIN" ]; then + # Try to find domain's user and error logs + user=$(grep -l "DNS.*$FILTER_DOMAIN" /var/cpanel/users/* 2>/dev/null | head -1 | xargs basename 2>/dev/null) + if [ -n "$user" ]; then + find "/home/$user" -name "error_log" -type f 2>/dev/null | while read -r log; do + echo "$log|php_$FILTER_DOMAIN" >> "$LOG_FILES_LIST" + done + fi +else + # All users + find /home/*/public_html -name "error_log" -type f 2>/dev/null | while read -r log; do + username=$(echo "$log" | grep -oE '/home/[^/]+' | sed 's|/home/||') + echo "$log|php_$username" >> "$LOG_FILES_LIST" + done +fi # Per-domain Apache logs in domlogs if [ -d "$DOMLOGS_DIR" ]; then - for log in "$DOMLOGS_DIR"/*; do - [ -f "$log" ] && echo "$log|domlog_$(basename "$log")" >> "$LOG_FILES_LIST" - done + if [ -n "$FILTER_DOMAIN" ]; then + # Specific domain + for log in "$DOMLOGS_DIR/$FILTER_DOMAIN" "$DOMLOGS_DIR/$FILTER_DOMAIN-"*; do + [ -f "$log" ] && echo "$log|domlog_$FILTER_DOMAIN" >> "$LOG_FILES_LIST" + done + elif [ -n "$FILTER_USER" ]; then + # Specific user - find their domains + if [ -f "/var/cpanel/users/$FILTER_USER" ]; then + grep "^DNS" "/var/cpanel/users/$FILTER_USER" | awk '{print $2}' | while read -r domain; do + for log in "$DOMLOGS_DIR/$domain" "$DOMLOGS_DIR/$domain-"*; do + [ -f "$log" ] && echo "$log|domlog_$domain" >> "$LOG_FILES_LIST" + done + done + fi + else + # All domains + for log in "$DOMLOGS_DIR"/*; do + [ -f "$log" ] && ! [[ "$log" =~ (bytes_log|offset|error_log)$ ]] && \ + echo "$log|domlog_$(basename "$log")" >> "$LOG_FILES_LIST" + done + fi +fi + +# ModSecurity audit log (only if not filtering or matches filter) +if [ -f "$MODSEC_AUDIT_LOG" ]; then + if [ -z "$FILTER_USER" ] && [ -z "$FILTER_DOMAIN" ]; then + echo "$MODSEC_AUDIT_LOG|modsec_audit" >> "$LOG_FILES_LIST" + elif [ -n "$FILTER_DOMAIN" ]; then + # Will filter ModSec entries during processing + echo "$MODSEC_AUDIT_LOG|modsec_audit" >> "$LOG_FILES_LIST" + fi fi log_count=$(wc -l < "$LOG_FILES_LIST" 2>/dev/null || echo "0") echo " Found $log_count log files to analyze" +if [ -n "$FILTER_USER" ]; then + echo " Scope: User '$FILTER_USER'" +elif [ -n "$FILTER_DOMAIN" ]; then + echo " Scope: Domain '$FILTER_DOMAIN'" +fi echo "" ################################################################################ @@ -230,11 +318,38 @@ critical_found=0 while IFS='|' read -r log_path log_type; do echo " Analyzing: $(basename "$log_path")..." - # Determine if it's an access log (contains status codes) or error log + # Determine log type is_access_log=false + is_modsec_log=false [[ "$log_type" == domlog_* ]] && ! [[ "$log_path" =~ error ]] && is_access_log=true + [[ "$log_type" == modsec_* ]] && is_modsec_log=true - if $is_access_log; then + if $is_modsec_log; then + # ModSecurity audit log - parse for blocks/denials + while IFS= read -r line; do + ((total_lines++)) + + # Skip if not a security event + echo "$line" | grep -qiE "ModSecurity.*Action.*Intercepted|status: (403|406|500)" || continue + + # Extract domain/host if filtering + if [ -n "$FILTER_DOMAIN" ]; then + echo "$line" | grep -q "$FILTER_DOMAIN" || continue + fi + + # Extract useful info + if echo "$line" | grep -qiE "SQL Injection|XSS|Command Injection|Path Traversal"; then + ((critical_found++)) + attack_type=$(echo "$line" | grep -oiE "SQL Injection|XSS|Command Injection|Path Traversal" | head -1) + uri=$(echo "$line" | grep -oE "uri: [^ ]+" | sed 's/uri: //' || echo "") + domain=$(echo "$line" | grep -oE "hostname: [^ ]+" | sed 's/hostname: //' || echo "unknown") + + echo "$domain||ModSecurity blocked: $attack_type $uri" >> "$CRITICAL_ERRORS" + fi + + done < <(tail -n 5000 "$log_path" 2>/dev/null) + + elif $is_access_log; then # Access log - look for 5xx status codes while IFS= read -r line; do ((total_lines++)) @@ -247,12 +362,17 @@ while IFS='|' read -r log_path log_type; do # Extract status code and URL if echo "$line" | grep -qE '" 5[0-9]{2} '; then - ((critical_found++)) status=$(echo "$line" | grep -oE '" 5[0-9]{2} ' | tr -d '" ') url=$(echo "$line" | awk '{print $7}' | cut -c1-80) ip=$(echo "$line" | awk '{print $1}') domain=$(basename "$log_path" | sed 's/-.*//') + # Apply domain filter if set + if [ -n "$FILTER_DOMAIN" ] && [ "$domain" != "$FILTER_DOMAIN" ]; then + continue + fi + + ((critical_found++)) echo "$domain||HTTP $status - $url (from $ip)" >> "$CRITICAL_ERRORS" fi @@ -268,6 +388,14 @@ while IFS='|' read -r log_path log_type; do continue fi + # Apply user/domain filter if set + if [ -n "$FILTER_USER" ]; then + echo "$line" | grep -q "/home/$FILTER_USER" || continue + fi + if [ -n "$FILTER_DOMAIN" ]; then + echo "$line" | grep -q "$FILTER_DOMAIN" || continue + fi + # Check if it's critical and user-facing if is_critical_user_facing "$line"; then ((critical_found++)) @@ -412,6 +540,19 @@ if grep -qiE "memory.*exhausted|Out of memory" "$CRITICAL_ERRORS"; then echo "" fi +if grep -qiE "ModSecurity" "$CRITICAL_ERRORS"; then + echo -e "${YELLOW}● ModSecurity Blocks detected${NC}" + echo " Priority: MEDIUM - Security rules blocking requests" + echo "" + echo " Actions:" + echo " 1. Review ModSecurity audit log for false positives" + echo " 2. Check if legitimate functionality is being blocked" + echo " 3. Consider whitelisting specific rules if needed" + echo " 4. Review blocked attack types to understand threats" + echo " 5. ModSecurity log: $MODSEC_AUDIT_LOG" + echo "" +fi + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "Full analysis saved to: $CRITICAL_ERRORS"