#!/bin/bash ################################################################################ # Malware Scanner ################################################################################ # Purpose: Comprehensive malware scanning with multiple engines # Supports: ImunifyAV, ClamAV, Maldet (LMD) # Scan scope: Single domain, user account, or entire server ################################################################################ # Color definitions (matching launcher.sh) RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" # Source required libraries (warn if missing, but allow graceful degradation) source "$SCRIPT_DIR/lib/common-functions.sh" 2>/dev/null || \ { echo "WARNING: common-functions.sh not found - some features may not work" >&2; } source "$SCRIPT_DIR/lib/system-detect.sh" 2>/dev/null || \ { echo "WARNING: system-detect.sh not found - control panel detection may fail" >&2; } source "$SCRIPT_DIR/lib/user-manager.sh" 2>/dev/null || \ { echo "WARNING: user-manager.sh not found - user selection may not work" >&2; } source "$SCRIPT_DIR/lib/reference-db.sh" 2>/dev/null || true # Optional source "$SCRIPT_DIR/lib/ip-reputation.sh" 2>/dev/null || true # Optional # Arrays for docroots and scanners declare -a docroot_array declare -a sanitized_docroot declare -a remove_docroot declare -a available_scanners # Validate that required functions were sourced from libraries # These functions must exist for the script to work properly validate_required_functions() { local required_functions=( "confirm" "print_header" "print_banner" "select_user_interactive" "get_user_domains" ) for func in "${required_functions[@]}"; do if ! declare -f "$func" &>/dev/null; then echo "ERROR: Required function '$func' not found." >&2 echo " Check that library files exist in: $SCRIPT_DIR/lib/" >&2 return 1 fi done return 0 } # Validate functions early if ! validate_required_functions; then exit 1 fi # Individual scanner detection functions is_imunify_installed() { command -v imunify-antivirus &>/dev/null || [ -f "/usr/bin/imunify-antivirus" ] } is_clamav_installed() { command -v clamscan &>/dev/null || \ [ -f "/usr/local/cpanel/3rdparty/bin/clamscan" ] || \ rpm -qa | grep -q "cpanel-clamav" } is_maldet_installed() { command -v maldet &>/dev/null || [ -f "/usr/local/sbin/maldet" ] } is_rkhunter_installed() { command -v rkhunter &>/dev/null || [ -f "/usr/bin/rkhunter" ] } # Scanner detection detect_scanners() { available_scanners=() if is_imunify_installed; then available_scanners+=("imunify") fi if is_clamav_installed; then available_scanners+=("clamav") fi if is_maldet_installed; then available_scanners+=("maldet") fi if is_rkhunter_installed; then available_scanners+=("rkhunter") fi # Note: If no scanners are found, available_scanners array will be empty # Menu option 9 allows installation, so we don't exit here # Just return success to allow menu to display return 0 } # Show installation instructions for missing scanners show_scanner_installation_guide() { echo -e "${YELLOW}Available Malware Scanners:${NC}" echo "" # Check ImunifyAV if ! is_imunify_installed; then echo -e "${CYAN}ImunifyAV${NC} - FREE real-time malware scanner" echo " Status: Not installed" echo " Installation (cPanel):" echo " yum install imunify-antivirus imunify-antivirus-cpanel" echo " /opt/alt/python35/share/imunify360/scripts/av-userside-plugin.sh" echo " Installation (script method):" echo " wget https://repo.imunify360.cloudlinux.com/defence360/imav-deploy.sh" echo " bash imav-deploy.sh" echo " Note: ImunifyAV is FREE. Imunify360 is the paid version." echo " Docs: https://docs.imunify360.com/imunifyav/" echo "" else echo -e "${GREEN}✓ ImunifyAV${NC} - Installed (FREE version)" echo "" fi # Check ClamAV if ! is_clamav_installed; then echo -e "${CYAN}ClamAV${NC} - Open source antivirus engine" echo " Status: Not installed" echo " Installation (cPanel):" echo " /scripts/update_local_rpm_versions --edit target_settings.clamav installed" echo " /scripts/check_cpanel_rpms --fix --targets=clamav" echo " Installation (manual):" echo " yum install clamav clamav-update # RHEL/CentOS" echo " apt-get install clamav clamav-daemon # Debian/Ubuntu" echo " freshclam # Update virus definitions" echo "" else echo -e "${GREEN}✓ ClamAV${NC} - Installed" echo "" fi # Check Maldet if ! is_maldet_installed; then echo -e "${CYAN}Maldet (LMD)${NC} - Linux Malware Detect" echo " Status: Not installed" echo " Installation:" echo " cd /tmp" echo " wget http://www.rfxn.com/downloads/maldetect-current.tar.gz" echo " tar -xzf maldetect-current.tar.gz" echo " cd maldetect-*" echo " ./install.sh" echo " Docs: https://www.rfxn.com/projects/linux-malware-detect/" echo "" else echo -e "${GREEN}✓ Maldet${NC} - Installed" echo "" fi # Check Rootkit Hunter if ! is_rkhunter_installed; then echo -e "${CYAN}Rootkit Hunter${NC} - Rootkit/backdoor/exploit scanner" echo " Status: Not installed" echo " Installation:" echo " yum install epel-release -y # Enable EPEL repo" echo " yum install rkhunter -y" echo " rkhunter --update # Update definitions" echo " rkhunter --propupd # Initialize baseline" echo " Docs: https://rkhunter.sourceforge.net/" echo "" else echo -e "${GREEN}✓ Rootkit Hunter${NC} - Installed" echo "" fi echo -e "${YELLOW}Recommendation:${NC} Install at least ClamAV + RKHunter (both free) for comprehensive protection" echo "" } # Install all scanners at once install_all_scanners() { echo "" print_header "Install All Malware Scanners" echo "This will install:" echo " • ClamAV (free, open source)" echo " • Maldet (free, Linux-specific)" echo " • ImunifyAV (FREE version)" echo " • Rootkit Hunter (free, rootkit detection)" echo "" echo -e "${YELLOW}Note: ImunifyAV is FREE. Imunify360 is the paid version.${NC}" echo "" read -p "Proceed with installation? (yes/no): " confirm if [ "$confirm" != "yes" ]; then echo "Cancelled." read -p "Press Enter to continue..." return 0 fi echo "" echo "==========================================" echo "Installing Scanners" echo "==========================================" echo "" # Install ClamAV if ! is_clamav_installed; then echo -e "${CYAN}[1/4] Installing ClamAV...${NC}" if [ -f "/usr/local/cpanel/cpanel" ]; then # cPanel method - check if already installed but not configured if rpm -qa | grep -q "cpanel-clamav"; then echo -e "${GREEN}✓ ClamAV already installed (cPanel)${NC}" else /scripts/update_local_rpm_versions --edit target_settings.clamav installed 2>/dev/null || true /scripts/check_cpanel_rpms --fix --targets=clamav 2>&1 | grep -E "Installing|Updating|up to date" || true fi elif command -v yum &>/dev/null; then yum install -y clamav clamav-update 2>&1 | grep -E "Installing|Updating|already installed" || true elif command -v apt-get &>/dev/null; then apt-get update && apt-get install -y clamav clamav-daemon || true fi if is_clamav_installed; then echo -e "${GREEN}✓ ClamAV installed${NC}" # Find freshclam binary local freshclam_bin=$(command -v freshclam || find /usr -name freshclam 2>/dev/null | head -1) # Update virus signatures immediately if [ -n "$freshclam_bin" ]; then echo " → Updating virus signatures (this may take a moment)..." if "$freshclam_bin" 2>&1 | grep -qE "updated|Downloaded|up-to-date"; then echo -e " ${GREEN}✓${NC} Signatures updated" else echo -e " ${YELLOW}⚠${NC} Signature update status unclear (may still be current)" fi fi else echo -e "${RED}✗ ClamAV installation failed${NC}" fi else echo -e "${GREEN}✓ ClamAV already installed${NC}" fi echo "" # Install Maldet if ! is_maldet_installed; then echo -e "${CYAN}[2/4] Installing Maldet...${NC}" cd /tmp || { echo -e "${RED}✗ Cannot access /tmp${NC}"; return 1; } # Download Maldet echo " → Downloading Maldet..." if ! wget -q http://www.rfxn.com/downloads/maldetect-current.tar.gz; then echo -e "${RED}✗ Download failed - check internet connectivity${NC}" return 1 fi if [ -f maldetect-current.tar.gz ]; then echo " → Extracting archive..." if ! tar -xzf maldetect-current.tar.gz 2>/dev/null; then echo -e "${RED}✗ Extraction failed - archive may be corrupted${NC}" rm -f maldetect-current.tar.gz return 1 fi # Find the extracted directory local maldet_dir=$(find /tmp -maxdepth 1 -type d -name "maldetect-*" 2>/dev/null | head -1) if [ -z "$maldet_dir" ]; then echo -e "${RED}✗ Cannot find extracted directory${NC}" echo " Available directories in /tmp:" ls -la /tmp | grep maldetect | sed 's/^/ /' cd /tmp rm -rf "maldetect-"* return 1 fi # Change to extracted directory if ! cd "$maldet_dir"; then echo -e "${RED}✗ Cannot access directory: $maldet_dir${NC}" cd /tmp rm -rf "maldetect-"* return 1 fi # Run installation with error capture echo " → Running installation script..." local install_log="/tmp/maldet-install-$$.log" if ./install.sh > "$install_log" 2>&1; then install_exit=0 else install_exit=$? fi # Cleanup cd /tmp rm -rf "maldetect-"* # Check if installation succeeded if is_maldet_installed; then echo -e "${GREEN}✓ Maldet installed${NC}" rm -f "$install_log" # Update malware signatures immediately echo " → Updating malware signatures..." if maldet -u 2>&1 | grep -qE "update completed|signatures"; then echo -e " ${GREEN}✓${NC} Signatures updated" else echo -e " ${YELLOW}⚠${NC} Signature update status unclear (continuing with current definitions)" fi else echo -e "${RED}✗ Maldet installation failed${NC}" # Show diagnostic information if [ -f "$install_log" ]; then echo -e "${YELLOW}Installation output (last 10 lines):${NC}" tail -10 "$install_log" | sed 's/^/ /' echo "" echo -e "${YELLOW}Full log saved to: $install_log${NC}" fi # Check for common Plesk issues if command -v plesk >/dev/null 2>&1; then echo -e "${YELLOW}Detected Plesk system - checking for conflicts...${NC}" # Check if cron is accessible if [ ! -w /var/spool/cron ] && [ ! -w /etc/cron.d ]; then echo " → Cron directory permissions may be restricted" fi # Check if required directories exist if [ ! -d /usr/local/sbin ]; then echo " → /usr/local/sbin does not exist (required for maldet)" fi fi return 1 fi else echo -e "${RED}✗ Download failed - maldetect-current.tar.gz not found${NC}" return 1 fi else echo -e "${GREEN}✓ Maldet already installed${NC}" fi echo "" # Install ImunifyAV (FREE version) if ! is_imunify_installed; then echo -e "${CYAN}[3/4] Installing ImunifyAV (FREE)...${NC}" echo " This may take several minutes - please wait..." # Use deployment script method (most reliable) cd /tmp if [ -f "imav-deploy.sh" ]; then rm -f imav-deploy.sh fi wget -q https://repo.imunify360.cloudlinux.com/defence360/imav-deploy.sh if [ -f imav-deploy.sh ]; then # Run deployment script with progress indicators bash imav-deploy.sh 2>&1 | grep -E "Installing|Installed|Complete|Error|Failed" || true rm -f imav-deploy.sh # Enable cPanel UI plugin if installed if [ -f "/opt/alt/python35/share/imunify360/scripts/av-userside-plugin.sh" ]; then echo " → Enabling cPanel UI plugin..." /opt/alt/python35/share/imunify360/scripts/av-userside-plugin.sh &>/dev/null fi else echo -e "${RED} Failed to download installation script${NC}" fi if is_imunify_installed; then echo -e "${GREEN}✓ ImunifyAV (FREE) installed${NC}" echo " No license key required - this is the FREE version" # Find imunify-antivirus binary local imunify_bin=$(command -v imunify-antivirus || find /usr -name imunify-antivirus 2>/dev/null | head -1) # Update malware signatures immediately if [ -n "$imunify_bin" ]; then echo " → Updating malware signatures..." if "$imunify_bin" update 2>&1 | grep -qE "updated|Success|completed"; then echo -e " ${GREEN}✓${NC} Signatures updated" else echo -e " ${YELLOW}⚠${NC} Signature update status unclear (continuing with current definitions)" fi fi else echo -e "${RED}✗ ImunifyAV installation failed${NC}" fi else echo -e "${GREEN}✓ ImunifyAV already installed${NC}" fi echo "" # Install Rootkit Hunter if ! is_rkhunter_installed; then echo -e "${CYAN}[4/4] Installing Rootkit Hunter...${NC}" # Ensure EPEL repo is enabled if command -v yum &>/dev/null; then if ! rpm -qa | grep -q epel-release; then echo " → Installing EPEL repository..." yum install -y epel-release 2>&1 | grep -E "Installing|Installed|already installed" fi # Install rkhunter yum install -y rkhunter 2>&1 | grep -E "Installing|Installed|already installed" elif command -v apt-get &>/dev/null; then apt-get update && apt-get install -y rkhunter fi if is_rkhunter_installed; then echo -e "${GREEN}✓ Rootkit Hunter installed${NC}" # Update definitions echo " → Updating rootkit definitions..." rkhunter --update 2>&1 | grep -E "updated|downloaded" || rkhunter --update &>/dev/null echo -e " ${GREEN}✓${NC} Definitions updated" # Initialize baseline (propupd creates file property database) echo " → Initializing baseline database..." rkhunter --propupd &>/dev/null echo -e " ${GREEN}✓${NC} Baseline initialized" else echo -e "${RED}✗ Rootkit Hunter installation failed${NC}" fi else echo -e "${GREEN}✓ Rootkit Hunter already installed${NC}" fi echo "" echo "==========================================" echo "Installation Complete" echo "==========================================" echo "" # Re-detect scanners detect_scanners echo "" read -p "Press Enter to continue..." } # Detect control panel and gather docroots detect_control_panel() { docroot_array=() # Use system-detect.sh if available, otherwise detect if [ -n "$SYS_CONTROL_PANEL" ]; then CONTROL_PANEL="$SYS_CONTROL_PANEL" elif [ -f "/etc/userdatadomains" ]; then CONTROL_PANEL="cpanel" elif [ -f "/usr/local/psa/version" ]; then CONTROL_PANEL="plesk" elif [ -d "/usr/local/interworx/" ]; then CONTROL_PANEL="interworx" else CONTROL_PANEL="none" fi # cPanel-specific setup if [ "$CONTROL_PANEL" = "cpanel" ]; then # Add cPanel 3rdparty bin to PATH only for cPanel export PATH=/usr/local/cpanel/3rdparty/bin/:$PATH while IFS= read -r line; do # Format: domain: user==owner==main==domain==docroot==... # Extract docroot (field 5, 0-indexed field 4) using awk directly docroot=$(awk -F'==' '{print $5}' <<< "$line") [ -n "$docroot" ] && [ -d "$docroot" ] && docroot_array+=("$docroot") done < <(cut -d: -f2- /etc/userdatadomains | sort -u) # Plesk-specific elif [ "$CONTROL_PANEL" = "plesk" ]; then while IFS= read -r domain; do docroot=$(plesk bin site -i "$domain" 2>/dev/null | grep "WWW-Root" | awk '{print $2}') [ -n "$docroot" ] && docroot_array+=("$docroot") done < <(plesk bin site --list 2>/dev/null) # InterWorx-specific (improved with proper path structure) elif [ "$CONTROL_PANEL" = "interworx" ]; then # InterWorx structure: /home/username/domain.com/html # Find all html directories in the InterWorx structure while IFS= read -r docroot; do [ -n "$docroot" ] && [ -d "$docroot" ] && docroot_array+=("$docroot") done < <(find /home/*/*/html -maxdepth 0 -type d 2>/dev/null | sort -u) else CONTROL_PANEL="none" echo -e "${YELLOW}No control panel detected${NC}" echo "Manual path selection required" return 1 fi # Remove subdirectory docroots (avoid scanning same files twice) sanitize_docroots return 0 } # Remove subdirectory docroots from array sanitize_docroots() { remove_docroot=() for search_value in "${docroot_array[@]}"; do # Count how many paths contain this value count=$(printf '%s\n' "${docroot_array[@]}" | grep -c "$search_value" || true) if [ "$count" -gt 1 ]; then # Find subdirectories and mark for removal while IFS= read -r subdir; do if [ "$subdir" != "$search_value" ]; then remove_docroot+=("$subdir") fi done < <(printf '%s\n' "${docroot_array[@]}" | grep "$search_value") fi done # Build sanitized array sanitized_docroot=() for docroot in "${docroot_array[@]}"; do # Check if this docroot is in remove list skip=0 for remove in "${remove_docroot[@]}"; do if [ "$docroot" = "$remove" ]; then skip=1 break fi done if [ "${skip:-0}" -eq 0 ]; then sanitized_docroot+=("$docroot") fi done } # Get docroots for specific user get_user_docroots() { local username="$1" local user_docroots=() if [ "$CONTROL_PANEL" = "cpanel" ]; then while IFS= read -r line; do docroot=$(awk -F'==' '{print $5}' <<< "$line") [ -n "$docroot" ] && [ -d "$docroot" ] && user_docroots+=("$docroot") done < <(grep ":.*${username}==" /etc/userdatadomains | cut -d: -f2- | sort -u) elif [ "$CONTROL_PANEL" = "interworx" ]; then # Use user-manager.sh to get all domains for this user local domains=$(get_user_domains "$username") if [ -n "$domains" ]; then while IFS= read -r domain; do # InterWorx: /home/username/domain.com/html local docroot="/home/${username}/${domain}/html" [ -d "$docroot" ] && user_docroots+=("$docroot") done <<< "$domains" fi else echo -e "${RED}User-specific scanning only supported on cPanel/InterWorx${NC}" return 1 fi echo "${user_docroots[@]}" } # Get docroot for specific domain get_domain_docroot() { local domain="$1" local domain_docroot="" if [ "$CONTROL_PANEL" = "cpanel" ]; then domain_docroot=$(grep "^${domain}:" /etc/userdatadomains | cut -d= -f5 | sed 's/==/=/g') elif [ "$CONTROL_PANEL" = "plesk" ]; then domain_docroot=$(plesk bin site -i "$domain" 2>/dev/null | grep "WWW-Root" | awk '{print $2}') elif [ "$CONTROL_PANEL" = "interworx" ]; then # Find which user owns this domain using vhost configs local username=$(grep -l "ServerName ${domain}" /etc/httpd/conf.d/vhost_*.conf 2>/dev/null | head -1 | \ xargs grep "SuexecUserGroup" 2>/dev/null | awk '{print $2}') if [ -n "$username" ]; then # InterWorx: /home/username/domain.com/html domain_docroot="/home/${username}/${domain}/html" fi else echo -e "${RED}Domain lookup only supported on cPanel/Plesk/InterWorx${NC}" return 1 fi echo "$domain_docroot" } # Memory check before scanning check_memory() { local total_mem=$(free -m | awk '/^Mem:/{print $2}') local avail_mem=$(free -m | awk '/^Mem:/{print $7}') local min_total=2048 # 2GB local min_avail=512 # 512MB if [ "$total_mem" -lt "$min_total" ] || [ "$avail_mem" -lt "$min_avail" ]; then echo -e "${YELLOW}WARNING: Low memory detected${NC}" echo "Total: ${total_mem}MB | Available: ${avail_mem}MB" echo "" echo "Running a full scan may cause high load or OOM conditions." echo "" read -p "Continue anyway? (yes/no): " confirm if [ "$confirm" != "yes" ]; then echo "Scan cancelled" return 1 fi fi return 0 } # ImunifyAV scanner # Generate standalone malware scan script generate_standalone_scanner() { local scan_paths=("$@") if [ ${#scan_paths[@]} -eq 0 ]; then echo -e "${RED}No paths to scan${NC}" return 1 fi # Create session ID and directory local session_id="malware-$(date +%Y%m%d-%H%M%S)" local session_dir="/opt/${session_id}" echo "" print_header "Generating Standalone Scanner" echo "Session ID: $session_id" echo "Location: $session_dir" echo "" # Create directory structure mkdir -p "$session_dir"/{logs,results} # Create standalone scan script cat > "$session_dir/scan.sh" << 'STANDALONE_EOF' #!/bin/bash ################################################################################ # Standalone Malware Scanner ################################################################################ # Auto-generated by Server Management Toolkit # This script is self-contained and can run independently ################################################################################ # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' # Get script directory SCAN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" LOG_DIR="$SCAN_DIR/logs" RESULTS_DIR="$SCAN_DIR/results" # Session info SESSION_LOG="$LOG_DIR/session.log" SUMMARY_FILE="$RESULTS_DIR/summary.txt" INFECTED_LIST="$RESULTS_DIR/infected_files.txt" # Logging function log_message() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$SESSION_LOG" } # Activity spinner for long-running scans show_spinner() { local pid=$1 local message=$2 local spin='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏' local i=0 while kill -0 $pid 2>/dev/null; do i=$(( (i+1) % 10 )) printf "\r ⏳ $message ${spin:$i:1} " sleep 0.2 done printf "\r ✓ $message - Complete\n" } # Format elapsed time format_time() { local seconds=$1 if [ "$seconds" -lt 60 ]; then echo "${seconds}s" elif [ "$seconds" -lt 3600 ]; then printf "%dm %ds" $((seconds / 60)) $((seconds % 60)) else printf "%dh %dm" $((seconds / 3600)) $(((seconds % 3600) / 60)) fi } # Cleanup function for trap handler cleanup_on_exit() { local exit_code=$? echo "" # Remove running marker file rm -f "$SCAN_DIR/.scan_running" # Only log if session log exists if [ -f "$SESSION_LOG" ]; then log_message "Cleanup triggered (exit code: $exit_code)" fi # Remove temporarily installed RKHunter if [ "${RKHUNTER_TEMP_INSTALLED:-false}" = "true" ]; then if [ -f "$SESSION_LOG" ]; then log_message "Removing temporarily installed RKHunter..." fi echo "→ Cleaning up: Removing Rootkit Hunter..." if command -v yum &>/dev/null; then if yum remove -y rkhunter &>/dev/null 2>&1; then if [ -f "$SESSION_LOG" ]; then log_message "RKHunter removed successfully" fi else if [ -f "$SESSION_LOG" ]; then log_message "WARNING: Failed to remove RKHunter (yum command failed)" fi fi elif command -v apt-get &>/dev/null; then if apt-get remove -y rkhunter &>/dev/null 2>&1; then if [ -f "$SESSION_LOG" ]; then log_message "RKHunter removed successfully" fi else if [ -f "$SESSION_LOG" ]; then log_message "WARNING: Failed to remove RKHunter (apt-get command failed)" fi fi fi fi # Save interrupted status (only if summary file directory exists) if [ $exit_code -ne 0 ] && [ -d "$RESULTS_DIR" ]; then { echo "" echo "SCAN INTERRUPTED" echo "Exit code: $exit_code" echo "Time: $(date)" } >> "$SUMMARY_FILE" if [ -f "$SESSION_LOG" ]; then log_message "Scan interrupted with exit code: $exit_code" fi fi } # Set trap for cleanup on exit, interrupt, or termination trap cleanup_on_exit EXIT INT TERM # Banner clear echo "========================================" echo "Standalone Malware Scanner" echo "========================================" echo "Session: $(basename "$SCAN_DIR")" echo "Started: $(date)" echo "========================================" echo "" log_message "Scan session started" # Create marker file to indicate scan is running touch "$SCAN_DIR/.scan_running" # Detect available scanners AVAILABLE_SCANNERS=() if command -v imunify-antivirus &>/dev/null; then AVAILABLE_SCANNERS+=("imunify") log_message "Detected: ImunifyAV" fi if command -v clamscan &>/dev/null; then AVAILABLE_SCANNERS+=("clamav") log_message "Detected: ClamAV" fi if command -v maldet &>/dev/null; then AVAILABLE_SCANNERS+=("maldet") log_message "Detected: Maldet" fi # Track if rkhunter was auto-installed (for cleanup) RKHUNTER_TEMP_INSTALLED=false if command -v rkhunter &>/dev/null; then AVAILABLE_SCANNERS+=("rkhunter") log_message "Detected: Rootkit Hunter" else # Auto-install rkhunter temporarily for this scan log_message "RKHunter not found - installing temporarily..." echo "→ Installing Rootkit Hunter (temporary, will be removed after scan)..." if command -v yum &>/dev/null; then # Ensure EPEL is available if ! rpm -qa | grep -q epel-release; then yum install -y epel-release &>/dev/null fi # Install rkhunter yum install -y rkhunter &>/dev/null if command -v rkhunter &>/dev/null; then # Update definitions and initialize baseline rkhunter --update &>/dev/null rkhunter --propupd &>/dev/null AVAILABLE_SCANNERS+=("rkhunter") RKHUNTER_TEMP_INSTALLED=true log_message "RKHunter installed temporarily" echo " ✓ RKHunter installed (will be removed after scan)" fi fi fi # If no scanners found, show installation guide and exit gracefully if [ ${#AVAILABLE_SCANNERS[@]} -eq 0 ]; then log_message "WARNING: No scanners found on this system" echo "" echo -e "${RED}No malware scanners detected!${NC}" echo "" echo -e "${YELLOW}Available Malware Scanners:${NC}" echo "" echo -e "${CYAN}ImunifyAV${NC} - FREE real-time malware scanner" echo " Status: Not installed" echo " Installation (cPanel):" echo " yum install imunify-antivirus imunify-antivirus-cpanel" echo " /opt/alt/python35/share/imunify360/scripts/av-userside-plugin.sh" echo " Installation (manual):" echo " wget https://repo.imunify360.cloudlinux.com/defence360/imav-deploy.sh" echo " bash imav-deploy.sh" echo " Docs: https://docs.imunify360.com/imunifyav/" echo "" echo -e "${CYAN}ClamAV${NC} - Open source antivirus engine" echo " Status: Not installed" echo " Installation:" echo " yum install clamav clamav-update # RHEL/CentOS" echo " apt-get install clamav clamav-daemon # Debian/Ubuntu" echo "" echo -e "${CYAN}Maldet (LMD)${NC} - Linux Malware Detect" echo " Status: Not installed" echo " Installation:" echo " cd /tmp && wget http://www.rfxn.com/downloads/maldetect-current.tar.gz" echo " tar -xzf maldetect-current.tar.gz && cd maldetect-*" echo " ./install.sh" echo "" echo -e "${CYAN}Rootkit Hunter${NC} - Rootkit/backdoor/exploit scanner" echo " Status: Not installed" echo " Installation:" echo " yum install epel-release rkhunter # RHEL/CentOS" echo " apt-get install rkhunter # Debian/Ubuntu" echo "" echo -e "${YELLOW}Recommendation:${NC} Install at least ClamAV + RKHunter (both free)" echo "" exit 0 fi log_message "Found ${#AVAILABLE_SCANNERS[@]} scanner(s): ${AVAILABLE_SCANNERS[*]}" # Scan paths (will be replaced) SCAN_PATHS=() PLACEHOLDER_SCAN_PATHS # Validate scan paths log_message "Validating scan paths..." VALID_PATHS=() for path in "${SCAN_PATHS[@]}"; do if [ -e "$path" ]; then if [ -r "$path" ]; then VALID_PATHS+=("$path") log_message "✓ Valid path: $path" else log_message "WARNING: Path not readable: $path (skipping)" echo "⚠️ WARNING: Cannot read $path (permission denied) - skipping" fi else log_message "WARNING: Path does not exist: $path (skipping)" echo "⚠️ WARNING: Path does not exist: $path - skipping" fi done if [ ${#VALID_PATHS[@]} -eq 0 ]; then log_message "ERROR: No valid paths to scan!" echo -e "${RED}ERROR: No valid paths to scan!${NC}" exit 1 fi # Use only valid paths SCAN_PATHS=("${VALID_PATHS[@]}") log_message "Scanning ${#SCAN_PATHS[@]} valid path(s)" # Check available disk space for logs log_message "Checking disk space..." SCAN_DIR_FS=$(df -P "$SCAN_DIR" | tail -1 | awk '{print $6}') AVAILABLE_KB=$(df -P "$SCAN_DIR" | tail -1 | awk '{print $4}') AVAILABLE_MB=$((AVAILABLE_KB / 1024)) if [ "$AVAILABLE_MB" -lt 100 ]; then log_message "WARNING: Low disk space ($AVAILABLE_MB MB available)" echo "⚠️ WARNING: Low disk space on $SCAN_DIR_FS ($AVAILABLE_MB MB available)" echo "Scan logs may be large. Recommend at least 100 MB free space." echo "" read -t 10 -p "Continue anyway? (y/N): " continue_scan if [[ ! "$continue_scan" =~ ^[Yy]$ ]]; then log_message "Scan cancelled due to low disk space" echo "Scan cancelled." exit 0 fi else log_message "Disk space OK: $AVAILABLE_MB MB available" fi # Initialize summary { echo "==========================================" echo "Malware Scan Summary Report" echo "==========================================" echo "Session: $(basename "$SCAN_DIR")" echo "Started: $(date)" echo "Scanners: ${AVAILABLE_SCANNERS[*]}" echo "Paths: ${#SCAN_PATHS[@]}" echo "" printf '%s\n' "${SCAN_PATHS[@]}" echo "" echo "==========================================" echo "" } > "$SUMMARY_FILE" # Track completion SCANNERS_COMPLETED=0 TOTAL_SCANNERS=${#AVAILABLE_SCANNERS[@]} # Run each scanner for scanner in "${AVAILABLE_SCANNERS[@]}"; do SCANNER_NUM=$((SCANNERS_COMPLETED + 1)) echo "" echo "" echo "==========================================" echo -e "${CYAN}${BOLD}Scanner $SCANNER_NUM of $TOTAL_SCANNERS: ${scanner^}${NC}" echo "==========================================" echo "" log_message "Starting ${scanner} scan ($SCANNER_NUM/$TOTAL_SCANNERS)" { echo "Scanner: ${scanner^}" echo "Started: $(date)" echo "---" } >> "$SUMMARY_FILE" case "$scanner" in imunify) # ImunifyAV has built-in exclusions that prevent comprehensive system scanning # Only use ImunifyAV for user-focused scans (not full server scans) if [ "${#SCAN_PATHS[@]}" -eq 1 ] && [ "${SCAN_PATHS[0]}" = "/" ]; then echo "" echo "ℹ️ Skipping ImunifyAV for full server scan" echo " Reason: ImunifyAV has built-in exclusions that skip system directories" echo " ClamAV and Maldet will provide comprehensive coverage instead" echo "" log_message "ImunifyAV: Skipped (not suitable for full server scans - use ClamAV/Maldet instead)" { echo "⊘ ImunifyAV scan skipped (not suitable for full system scans)" } >> "$SUMMARY_FILE" continue fi SCAN_START=$(date +%s) log_message "ImunifyAV: Updating signatures" if ! timeout 300 imunify-antivirus update &>> "$LOG_DIR/imunify.log"; then log_message "WARNING: ImunifyAV update failed (continuing with existing signatures)" echo "⚠️ WARNING: Signature update failed, using existing signatures" fi log_message "ImunifyAV: Starting on-demand scan" echo "" echo " 📁 Scanning paths: ${SCAN_PATHS[@]}" echo " ⏳ Scanner: ImunifyAV" echo "" IMUNIFY_INFECTED=0 FILES_SCANNED=0 # Run ImunifyAV scan with timeout (2 hours max) # Use --output-format to make parsing easier, with --timeout to prevent hanging for path in "${SCAN_PATHS[@]}"; do if [ ! -d "$path" ]; then log_message "ImunifyAV: Skipping non-existent path: $path" continue fi log_message "ImunifyAV: Scanning $path" echo " Scanning: $path" # Run scan with timeout to prevent hanging on status checks # ImunifyAV scan - output to log file timeout 7200 imunify-antivirus malware on-demand scan --path="$path" &>> "$LOG_DIR/imunify.log" & IMUNIFY_PID=$! # Monitor with simple timeout (don't try to parse imunify status which hangs) wait $IMUNIFY_PID IMUNIFY_EXIT=$? if [ "$IMUNIFY_EXIT" -eq 124 ]; then log_message "ERROR: ImunifyAV scan timed out after 2 hours for $path" echo " ⏱️ Scan timed out (2 hour limit)" elif [ "$IMUNIFY_EXIT" -ne 0 ]; then log_message "WARNING: ImunifyAV scan exited with code $IMUNIFY_EXIT for $path" echo " ⚠️ Scan completed with code $IMUNIFY_EXIT" fi done # Try to get count of malicious files from malicious list (if available) # Run once with timeout (60s) - capture output and exit code together IMUNIFY_INFECTED=$(timeout 60 imunify-antivirus malware malicious list 2>/dev/null | tail -n +2 | wc -l) IMUNIFY_MALICIOUS_EXIT=$? # Validate the count if [ "$IMUNIFY_MALICIOUS_EXIT" -ne 0 ] || ! [[ "$IMUNIFY_INFECTED" =~ ^[0-9]+$ ]]; then IMUNIFY_INFECTED=0 log_message "WARNING: Failed to get ImunifyAV malicious count (exit: $IMUNIFY_MALICIOUS_EXIT)" fi SCAN_END=$(date +%s) DURATION=$((SCAN_END - SCAN_START)) echo " ✓ ImunifyAV scan complete" echo " ⏱️ Duration: ${DURATION}s" echo "" echo "✓ ImunifyAV scan complete - Found: $IMUNIFY_INFECTED | Duration: ${DURATION}s" | tee -a "$SUMMARY_FILE" log_message "ImunifyAV: Scan complete - $IMUNIFY_INFECTED malicious files in ${DURATION}s" ;; clamav) SCAN_START=$(date +%s) if command -v freshclam &>/dev/null; then log_message "ClamAV: Updating signatures" if ! freshclam &>> "$LOG_DIR/clamav.log"; then log_message "WARNING: ClamAV signature update failed (continuing with existing signatures)" echo "⚠️ WARNING: Signature update failed, using existing signatures" fi fi log_message "ClamAV: Starting scan with activity monitoring" echo "" echo " 📁 Scanning path(s): ${SCAN_PATHS[*]}" echo " ⏳ Scanner: ClamAV (comprehensive virus scan...)" echo "" # ClamAV returns 1 if infected files found, 0 if clean, >1 for errors # Run in background with timeout (2 hours) and activity monitoring timeout 7200 clamscan --infected --recursive "${SCAN_PATHS[@]}" &>> "$LOG_DIR/clamav.log" & CLAM_PID=$! # Monitor activity by watching log file growth last_size=0 last_filename="" stall_counter=0 while kill -0 $CLAM_PID 2>/dev/null; do # Get current log size and file count from log if [ -f "$LOG_DIR/clamav.log" ]; then current_size=$(stat -c%s "$LOG_DIR/clamav.log" 2>/dev/null || echo 0) # Try to get current file being scanned current_file=$(tail -1 "$LOG_DIR/clamav.log" 2>/dev/null | grep -o '/[^:]*' | head -1) if [ -n "$current_file" ]; then filename=$(basename "$current_file" 2>/dev/null || echo "...") # Only update display when filename changes if [ "$filename" != "$last_filename" ]; then elapsed=$(($(date +%s) - SCAN_START)) printf "\r Scanning: %s | Elapsed: %s " \ "${filename:0:50}" "$(format_time $elapsed)" last_filename="$filename" fi fi # Check for stalled scan (no log growth in 60 seconds) if [ "$current_size" -eq "$last_size" ]; then stall_counter=$((stall_counter + 1)) if [ $stall_counter -eq 300 ]; then # 60 seconds (300 * 0.2s) - log only once log_message "WARNING: ClamAV scan appears stalled (no activity for 60s)" fi else stall_counter=0 fi last_size=$current_size fi sleep 0.2 done # Wait for scan to complete and get exit code wait $CLAM_PID CLAM_EXIT=$? echo "" # New line after spinner if [ "$CLAM_EXIT" -eq 124 ]; then log_message "ERROR: ClamAV scan timed out after 2 hours" echo " ⏱️ Scan timed out (exceeded 2 hour limit)" echo "ClamAV scan timed out" >> "$SUMMARY_FILE" SCAN_END=$(date +%s) DURATION=$((SCAN_END - SCAN_START)) echo "" continue elif [ "$CLAM_EXIT" -gt 1 ]; then log_message "ERROR: ClamAV scan failed with exit code $CLAM_EXIT" echo " ✗ Scan failed (exit code: $CLAM_EXIT) - check logs" echo "ClamAV scan failed (exit code: $CLAM_EXIT)" >> "$SUMMARY_FILE" SCAN_END=$(date +%s) DURATION=$((SCAN_END - SCAN_START)) echo "" continue fi # Extract infected files grep "FOUND" "$LOG_DIR/clamav.log" | cut -d: -f1 >> "$INFECTED_LIST" 2>/dev/null # Get scan stats from log FILES_SCANNED=$(grep "Scanned files:" "$LOG_DIR/clamav.log" | tail -1 | awk '{print $3}') CLAM_INFECTED=$(grep -c "FOUND" "$LOG_DIR/clamav.log" 2>/dev/null || echo 0) # Validate numbers if ! [[ "$FILES_SCANNED" =~ ^[0-9]+$ ]]; then FILES_SCANNED=0 fi SCAN_END=$(date +%s) DURATION=$((SCAN_END - SCAN_START)) echo " ✓ Scanned $FILES_SCANNED files" echo " ⏱️ Duration: ${DURATION}s" echo "" echo "✓ ClamAV scan complete - Found: $CLAM_INFECTED | Duration: ${DURATION}s" | tee -a "$SUMMARY_FILE" log_message "ClamAV: Scan complete - $CLAM_INFECTED infected files in ${DURATION}s" ;; maldet) SCAN_START=$(date +%s) log_message "Maldet: Updating signatures" if ! maldet -u &>> "$LOG_DIR/maldet.log"; then log_message "WARNING: Maldet signature update failed (continuing with existing signatures)" echo "⚠️ WARNING: Signature update failed, using existing signatures" fi log_message "Maldet: Starting scan with live progress" echo "" echo " 📁 Scanning path(s): ${SCAN_PATHS[*]}" echo " ⏳ Scanner: Maldet/LMD (Linux-specific malware detection...)" echo "" # Scan each path individually with -a (scan-all) flag # Note: -a flag scans all files regardless of modification time # Cannot combine -a with -f (file-list), so we loop through paths MALDET_EXIT=0 TOTAL_MALDET_FILES=0 TOTAL_MALDET_HITS=0 for path in "${SCAN_PATHS[@]}"; do if [ ! -d "$path" ]; then log_message "Maldet: Skipping non-existent path: $path" continue fi log_message "Maldet: Scanning $path with -a (all files)" # Run with -a (scan-all) for comprehensive scanning # Timeout after 2 hours per path timeout 7200 maldet -b -a "$path" &>> "$LOG_DIR/maldet.log" exit_code=$? if [ "$exit_code" -ne 0 ]; then MALDET_EXIT=$exit_code fi # Give scan a moment to complete sleep 2 done echo "" # New line after progress if [ "$MALDET_EXIT" -eq 124 ]; then log_message "ERROR: Maldet scan timed out after 2 hours" echo " ⏱️ Scan timed out (exceeded 2 hour limit)" echo "Maldet scan timed out" >> "$SUMMARY_FILE" SCAN_END=$(date +%s) DURATION=$((SCAN_END - SCAN_START)) echo "" continue elif [ "$MALDET_EXIT" -ne 0 ]; then log_message "ERROR: Maldet scan failed with exit code $MALDET_EXIT" echo " ✗ Scan failed (exit code: $MALDET_EXIT) - check logs" echo "Maldet scan failed (exit code: $MALDET_EXIT)" >> "$SUMMARY_FILE" SCAN_END=$(date +%s) DURATION=$((SCAN_END - SCAN_START)) echo "" continue fi # Extract scan results from event log (more reliable than parsing output) # Maldet logs to /usr/local/maldetect/logs/event_log # Use proper fallback: assign first, then check if empty and fallback to 0 FILES_SCANNED=$(grep "scan completed" /usr/local/maldetect/logs/event_log 2>/dev/null | tail -1 | grep -oP 'files \K[0-9]+') || FILES_SCANNED="0" MALDET_HITS=$(grep "scan completed" /usr/local/maldetect/logs/event_log 2>/dev/null | tail -1 | grep -oP 'malware hits \K[0-9]+') || MALDET_HITS="0" # Validate numbers if ! [[ "$FILES_SCANNED" =~ ^[0-9]+$ ]]; then FILES_SCANNED=0 fi if ! [[ "$MALDET_HITS" =~ ^[0-9]+$ ]]; then MALDET_HITS=0 fi SCAN_END=$(date +%s) DURATION=$((SCAN_END - SCAN_START)) echo " ✓ Scanned $FILES_SCANNED files" echo " ⏱️ Duration: ${DURATION}s" echo "" echo "✓ Maldet scan complete - Found: ${MALDET_HITS:-0} | Duration: ${DURATION}s" | tee -a "$SUMMARY_FILE" log_message "Maldet: Scan complete - ${MALDET_HITS:-0} hits in ${DURATION}s" ;; rkhunter) SCAN_START=$(date +%s) log_message "RKHunter: Updating definitions" if ! rkhunter --update &>> "$LOG_DIR/rkhunter.log"; then log_message "WARNING: RKHunter update failed (continuing with existing definitions)" echo "⚠️ WARNING: Definition update failed, using existing definitions" fi log_message "RKHunter: Starting scan with live test display" echo "" echo " 🔍 System scan: Checking for rootkits, backdoors, exploits" echo " ⏳ Scanner: Rootkit Hunter (system-wide integrity check...)" echo "" # Run with timeout (30 minutes, RKHunter is usually fast) # Show test names as they run timeout 1800 rkhunter --check --skip-keypress --report-warnings-only 2>&1 | tee -a "$LOG_DIR/rkhunter.log" | \ while IFS= read -r line; do # Parse test names: "Checking for..." or "Testing..." if [[ "$line" =~ ^Checking\ for\ (.+)$ ]] || [[ "$line" =~ ^Testing\ (.+)$ ]]; then test_name="${BASH_REMATCH[1]}" printf "\r → %-60s" "${test_name:0:60}" elif [[ "$line" =~ ^Scanning\ (.+)$ ]]; then scan_item="${BASH_REMATCH[1]}" printf "\r → Scanning: %-50s" "${scan_item:0:50}" fi done RKH_EXIT=$? echo "" # New line after test display if [ "$RKH_EXIT" -eq 124 ]; then log_message "ERROR: RKHunter scan timed out after 30 minutes" echo " ⏱️ Scan timed out (exceeded 30 minute limit)" echo "RKHunter scan timed out" >> "$SUMMARY_FILE" SCAN_END=$(date +%s) DURATION=$((SCAN_END - SCAN_START)) echo "" continue elif [ "$RKH_EXIT" -gt 1 ] && [ "$RKH_EXIT" -ne 127 ]; then log_message "WARNING: RKHunter scan completed with exit code $RKH_EXIT" echo " ⚠️ Scan completed with warnings (exit code: $RKH_EXIT)" fi # Extract warnings RKH_WARNINGS=$(grep -c "Warning:" "$LOG_DIR/rkhunter.log" 2>/dev/null || echo 0) # Extract any rootkits found grep "Rootkit" "$LOG_DIR/rkhunter.log" | grep -i "found" >> "$INFECTED_LIST" 2>/dev/null SCAN_END=$(date +%s) DURATION=$((SCAN_END - SCAN_START)) echo " ✓ System integrity check complete" echo " ⏱️ Duration: ${DURATION}s" echo "" echo "✓ RKHunter scan complete - Warnings: $RKH_WARNINGS | Duration: ${DURATION}s" | tee -a "$SUMMARY_FILE" log_message "RKHunter: Scan complete - $RKH_WARNINGS warnings in ${DURATION}s" ;; esac echo "" | tee -a "$SUMMARY_FILE" ((SCANNERS_COMPLETED++)) # Wait between scanners if [ ${SCANNERS_COMPLETED:-0} -lt $TOTAL_SCANNERS ]; then echo "Waiting 3 seconds before next scanner..." sleep 3 fi done # Finalize report with consolidated summary { echo "==========================================" echo "Scan Session Complete" echo "Completed: $(date)" echo "==========================================" echo "" # Consolidated Scanner Results Table echo "SCANNER RESULTS SUMMARY:" echo "────────────────────────────────────────" # ImunifyAV results if echo "${AVAILABLE_SCANNERS[*]}" | grep -q "imunify"; then IMUNIFY_COUNT=$(grep -o "ImunifyAV scan complete - Found: [0-9]*" "$SUMMARY_FILE" | grep -o "[0-9]*$" || echo "N/A") printf "%-20s %s\n" "ImunifyAV:" "$IMUNIFY_COUNT threats detected" fi # ClamAV results if echo "${AVAILABLE_SCANNERS[*]}" | grep -q "clamav"; then CLAM_COUNT=$(grep -o "ClamAV scan complete - Found: [0-9]*" "$SUMMARY_FILE" | grep -o "[0-9]*$" || echo "N/A") printf "%-20s %s\n" "ClamAV:" "$CLAM_COUNT infected files" fi # Maldet results if echo "${AVAILABLE_SCANNERS[*]}" | grep -q "maldet"; then printf "%-20s %s\n" "Maldet:" "Scan complete (check logs)" fi # RKHunter results if echo "${AVAILABLE_SCANNERS[*]}" | grep -q "rkhunter"; then RKH_COUNT=$(grep -o "RKHunter scan complete - Warnings: [0-9]*" "$SUMMARY_FILE" | grep -o "[0-9]*$" || echo "N/A") printf "%-20s %s\n" "Rootkit Hunter:" "$RKH_COUNT warnings" fi echo "────────────────────────────────────────" echo "" if [ -f "$INFECTED_LIST" ] && [ -s "$INFECTED_LIST" ]; then echo "⚠️ INFECTED FILES DETECTED:" echo "" sort -u "$INFECTED_LIST" echo "" 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 flagged_ips=0 while read -r infected_file; do # Extract file path components filename=$(basename "$infected_file") filepath=$(dirname "$infected_file") # Try to find corresponding Apache access logs # Look for POST requests to the directory containing the infected file # Use system-detected log directory with control panel-specific search if [ "$CONTROL_PANEL" = "interworx" ]; then # InterWorx: Search /home/*/var/*/logs/transfer.log (VERIFIED: uses 'transfer.log') # Search last 7 days of logs for POST requests to this path find /home/*/var/*/logs -type f -name 'transfer.log' 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 ip=$(awk '{print $1}' <<< "$logline") 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 elif [ "$CONTROL_PANEL" = "plesk" ]; then # Plesk: Search /var/www/vhosts/*/logs/access*log # Plesk stores logs in /var/www/vhosts/domain.com/logs/access_log or access_ssl_log find /var/www/vhosts/*/logs -type f \( -name 'access_log' -o -name 'access_ssl_log' \) 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 ip=$(awk '{print $1}' <<< "$logline") 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 elif [ "$CONTROL_PANEL" = "cpanel" ]; then # cPanel: Search domlogs directory # cPanel stores logs as domain.com, domain.net, etc. in /var/log/apache2/domlogs/ if [ -n "$SYS_LOG_DIR" ] && [ -d "$SYS_LOG_DIR" ]; then find "$SYS_LOG_DIR" -type f \( -name '*.com' -o -name '*.net' -o -name '*.org' -o -name '*.info' -o -name '*.biz' \) 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 ip=$(awk '{print $1}' <<< "$logline") 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 fi done < <(sort -u "$INFECTED_LIST" | head -20) # Limit to first 20 files to avoid long processing if [ "${flagged_ips:-0}" -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 echo "✓ No infected files detected by automated scan." echo "" echo "Review individual scanner logs for detailed information:" echo " • ImunifyAV: $LOG_DIR/imunify.log" echo " • ClamAV: $LOG_DIR/clamav.log" echo " • Maldet: $LOG_DIR/maldet.log" echo " • RKHunter: $LOG_DIR/rkhunter.log" fi } >> "$SUMMARY_FILE" # Validate scan results log_message "Validating scan results..." validation_issues=0 # Check that each scanner produced output for scanner in "${AVAILABLE_SCANNERS[@]}"; do case "$scanner" in imunify) if [ ! -s "$LOG_DIR/imunify.log" ]; then log_message "WARNING: ImunifyAV log file is empty or missing" echo "⚠️ WARNING: ImunifyAV scan may not have completed properly" >> "$SUMMARY_FILE" ((validation_issues++)) fi ;; clamav) if [ ! -s "$LOG_DIR/clamav.log" ]; then log_message "WARNING: ClamAV log file is empty or missing" echo "⚠️ WARNING: ClamAV scan may not have completed properly" >> "$SUMMARY_FILE" ((validation_issues++)) else # Verify ClamAV reached the summary line if ! grep -q "Scanned files:" "$LOG_DIR/clamav.log"; then log_message "WARNING: ClamAV scan may have been interrupted (no summary found)" echo "⚠️ WARNING: ClamAV scan may have been interrupted" >> "$SUMMARY_FILE" ((validation_issues++)) fi fi ;; maldet) if [ ! -s "$LOG_DIR/maldet.log" ]; then log_message "WARNING: Maldet log file is empty or missing" echo "⚠️ WARNING: Maldet scan may not have completed properly" >> "$SUMMARY_FILE" ((validation_issues++)) fi ;; rkhunter) if [ ! -s "$LOG_DIR/rkhunter.log" ]; then log_message "WARNING: RKHunter log file is empty or missing" echo "⚠️ WARNING: RKHunter scan may not have completed properly" >> "$SUMMARY_FILE" ((validation_issues++)) fi ;; esac done if [ $validation_issues -eq 0 ]; then log_message "All scans completed successfully - validation passed" echo "" >> "$SUMMARY_FILE" echo "✓ Scan Validation: All scanners completed successfully" >> "$SUMMARY_FILE" else log_message "WARNING: $validation_issues validation issue(s) found - review logs carefully" echo "" >> "$SUMMARY_FILE" echo "⚠️ Scan Validation: $validation_issues issue(s) found - review logs" >> "$SUMMARY_FILE" fi # Generate client report automatically (inline to work in standalone scripts) log_message "Generating client-facing security report" # Check if function exists, if not generate inline if declare -f generate_client_report > /dev/null 2>&1; then generate_client_report "$SCAN_DIR" > /dev/null 2>&1 else # Inline client report generation for standalone scripts client_report_file="$RESULTS_DIR/client_report.txt" # Extract scan info scan_date=$(grep "Started:" "$SUMMARY_FILE" | head -1 | sed 's/Started: //' || echo "Unknown") scan_paths=$(sed -n '/^Paths:/,/^$/p' "$SUMMARY_FILE" | tail -n +2 | grep -v "^$" | tr '\n' ', ' | sed 's/, $//' || echo "/home") # Analyze infected files for false positives real_threats_count=0 false_positives_list="" real_threats_list="" if [ -f "$RESULTS_DIR/infected_files.txt" ] && [ -s "$RESULTS_DIR/infected_files.txt" ]; then while IFS= read -r file; do if [[ "$file" =~ /logs?/.*\.(log|gz|bz2)$ ]] || \ [[ "$file" =~ /awstats/ ]] || \ [[ "$file" =~ /tmp/.*\.txt$ ]] || \ [[ "$file" =~ \.log\.[0-9]+$ ]]; then false_positives_list="${false_positives_list} • $file"$'\n' else real_threats_list="${real_threats_list}📁 $file"$'\n' ((real_threats_count++)) fi done < "$RESULTS_DIR/infected_files.txt" fi # Generate report { echo "MALWARE SCAN REPORT - $scan_date" echo "═══════════════════════════════════════════════════════════" echo "" echo "Scanned with: ImunifyAV, ClamAV, Linux Maldet, RKHunter" echo "" if [ "$real_threats_count" -eq 0 ]; then echo "RESULT: ✅ No malware found - your server is clean" else echo "RESULT: ⚠️ $real_threats_count infected file(s) detected" echo "" echo "INFECTED FILES:" echo "$real_threats_list" echo "NEXT STEPS:" echo " 1. Remove infected files immediately" echo " 2. Change all passwords" echo " 3. Update WordPress/plugins to latest versions" fi if [ -n "$false_positives_list" ]; then echo "" echo "───────────────────────────────────────────────────────────" echo "NOTE: Attack attempts were detected in your server logs." echo "These were successfully blocked. No action needed." fi echo "" echo "Scan ID: $(basename "$SCAN_DIR")" } > "$client_report_file" fi # Display completion clear echo "==========================================" echo -e "${GREEN}Malware Scan Complete!${NC}" echo "==========================================" echo "Session: $(basename "$SCAN_DIR")" echo "Completed: $(date)" echo "" echo "Results saved to:" echo " Summary: $SUMMARY_FILE" echo " Logs: $LOG_DIR/" echo "" echo -e "${CYAN}Client Report (copy/paste for tickets):${NC}" echo " $RESULTS_DIR/client_report.txt" echo "" # Show summary cat "$SUMMARY_FILE" echo "" echo "==========================================" echo "" echo -e "${CYAN}TIP:${NC} To view the client-friendly report:" echo " cat $RESULTS_DIR/client_report.txt" echo "" # Prompt for cleanup (RKHunter cleanup handled by trap) echo "" if confirm "Delete scan script? (Logs and results will be preserved)"; then log_message "User requested cleanup - deleting scan script" echo "" echo "Removing scan script..." rm -f "$SCAN_DIR/scan.sh" echo -e "${GREEN}✓ Scan script deleted${NC}" echo "" echo "Results preserved at: $SCAN_DIR" echo "" else log_message "User chose to keep scan script" fi echo "Scan script and results preserved at: $SCAN_DIR" echo "" echo "You can:" echo " • Review logs: ls $LOG_DIR" echo " • View summary: cat $SUMMARY_FILE" if [ -n "$SCAN_DIR" ] && [[ "$SCAN_DIR" != "/" ]]; then echo " • Delete scan directory manually: rm -rf \"$SCAN_DIR\"" fi echo "" echo "Press Ctrl+A then D to detach from this screen session," echo "or press Enter to open an interactive shell in this session..." echo "" read -t 30 -p "" # Keep screen session alive with an interactive shell echo "" echo "Opening interactive shell. Type 'exit' to close this screen session." echo "" log_message "Scan session ended - opening interactive shell" exec bash STANDALONE_EOF # Replace placeholder with actual paths paths_declaration="SCAN_PATHS=(" for path in "${scan_paths[@]}"; do paths_declaration+="\"$path\" " done paths_declaration+=")" # Escape special characters for sed (handle /, \, &, |, $) # CRITICAL FIX: Must escape the delimiter (|) as well since we use it in the sed command local escaped_paths=$(printf '%s\n' "$paths_declaration" | sed -e 's/[\/&|]/\\&/g') if ! sed -i "s|PLACEHOLDER_SCAN_PATHS|$escaped_paths|" "$session_dir/scan.sh"; then echo -e "${RED}ERROR: Failed to generate standalone scanner script${NC}" return 1 fi # Make executable chmod +x "$session_dir/scan.sh" # Check if screen is installed if ! command -v screen &>/dev/null; then echo -e "${YELLOW}Warning: 'screen' not installed${NC}" echo "" echo "Screen allows you to detach from the scan session." echo "" echo "Options:" echo " 1. Auto-install screen (recommended)" echo " 2. Use nohup fallback (run in background without screen)" echo " 3. Cancel" echo "" read -p "Select option: " screen_option case "$screen_option" in 1) echo "" echo "Installing screen..." if command -v yum &>/dev/null; then yum install -y screen elif command -v apt-get &>/dev/null; then apt-get update && apt-get install -y screen else echo -e "${RED}Unable to auto-install. Install manually: yum install screen${NC}" read -p "Press Enter to continue..." return 1 fi if ! command -v screen &>/dev/null; then echo -e "${RED}Installation failed${NC}" read -p "Press Enter to continue..." return 1 fi echo -e "${GREEN}✓ Screen installed successfully${NC}" echo "" ;; 2) # Use nohup fallback echo "" echo "Launching scan with nohup (background mode)..." nohup bash "$session_dir/scan.sh" > "$session_dir/logs/nohup.out" 2>&1 & scan_pid=$! sleep 1 if ps -p $scan_pid > /dev/null 2>&1; then echo "" echo -e "${GREEN}✓ Standalone scanner started successfully!${NC}" echo "" echo "Session ID: $session_id" echo "Process ID: $scan_pid" echo "Results directory: $session_dir/results/" echo "" echo -e "${CYAN}Monitor the scan:${NC}" echo " tail -f $session_dir/logs/session.log" echo "" echo -e "${CYAN}Check if still running:${NC}" echo " ps -p $scan_pid" echo "" echo -e "${GREEN}You can now safely delete the toolkit.${NC}" echo -e "${GREEN}The scan will continue running independently.${NC}" echo "" # Store session info in reference database store_reference "malware_standalone_latest" "$session_id" store_reference "malware_standalone_${session_id}_dir" "$session_dir" store_reference "malware_standalone_${session_id}_pid" "$scan_pid" read -p "Press Enter to continue..." return 0 else echo -e "${RED}Failed to start scan${NC}" echo "Run manually: bash $session_dir/scan.sh" read -p "Press Enter to continue..." return 1 fi ;; 3) echo "Cancelled." read -p "Press Enter to continue..." return 0 ;; *) echo -e "${RED}Invalid option${NC}" read -p "Press Enter to continue..." return 1 ;; esac fi # Launch in screen session echo "Launching scan in screen session..." screen -dmS "$session_id" bash "$session_dir/scan.sh" sleep 1 # Verify screen started if screen -list | grep -q "$session_id"; then echo "" echo -e "${GREEN}✓ Standalone scanner started successfully!${NC}" echo "" echo "Session ID: $session_id" echo "Screen session: $session_id" echo "Results directory: $session_dir/results/" echo "" echo -e "${CYAN}Monitor the scan:${NC}" echo " screen -r $session_id" echo "" echo -e "${CYAN}Check progress:${NC}" echo " tail -f $session_dir/logs/session.log" echo "" echo -e "${CYAN}Detach from screen:${NC}" echo " Press: Ctrl+A then D" echo "" echo -e "${GREEN}You can now safely delete the toolkit.${NC}" echo -e "${GREEN}The scan will continue running independently.${NC}" echo "" # Store session info in reference database store_reference "malware_standalone_latest" "$session_id" store_reference "malware_standalone_${session_id}_dir" "$session_dir" else echo -e "${RED}Failed to start screen session${NC}" echo "Run manually: bash $session_dir/scan.sh" fi read -p "Press Enter to continue..." } # Compare results from multiple scanners compare_scan_results() { echo "" print_header "Compare Scanner Results" # Get latest multiscan session local latest_session=$(get_reference "malware_multiscan_latest") if [ -z "$latest_session" ]; then echo "No multi-scanner sessions found." echo "" echo "Run a scan with 'All Available Scanners' option first." read -p "Press Enter to continue..." return fi local report_file=$(get_reference "malware_multiscan_${latest_session}") if [ -f "$report_file" ]; then echo "Latest multi-scanner session: $latest_session" echo "" less "$report_file" else echo "Report file not found: $report_file" fi echo "" read -p "Press Enter to continue..." } # Launch standalone scanner menu launch_standalone_scanner_menu() { local preset_scope="$1" # Optional: server, user, domain, custom echo "" print_header "Launch Standalone Scanner" echo "This will create a self-contained scanner in /opt/ that runs" echo "independently. You can safely delete the toolkit after launching." echo "" if ! detect_control_panel; then read -p "Press Enter to continue..." return 1 fi # Validate that docroots were found if [ ${#sanitized_docroot[@]} -eq 0 ]; then echo -e "${YELLOW}WARNING: No docroots found on this system${NC}" echo "You can still:" echo " 1. Scan specific paths manually (custom path option)" echo " 2. Scan from root (entire server)" echo "" fi echo "Control Panel: ${CONTROL_PANEL^}" echo "Available Scanners: ${available_scanners[@]}" echo "" local scope_choice local scan_paths=() local scan_description="" # If preset scope provided, use it; otherwise show menu if [ -n "$preset_scope" ]; then case "$preset_scope" in server) scope_choice=1 ;; all_users) scope_choice=2 ;; user) scope_choice=3 ;; domain) scope_choice=4 ;; custom) scope_choice=5 ;; *) scope_choice=0 ;; esac else echo "Select scan scope:" echo " 1. Entire server (scan from / - WARNING: may take several hours)" echo " 2. Specific user account" echo " 3. Specific domain" echo " 4. Custom path" echo " 0. Cancel" echo "" read -p "Select option: " scope_choice fi case $scope_choice in 1) # Entire server scan_paths=("/") scan_description="full server scan" echo "" echo -e "${YELLOW}WARNING: Full server scan from /${NC}" echo "This will scan the ENTIRE filesystem including:" echo " • All user directories" echo " • System files" echo " • Application files" echo "" echo "This scan may take several hours and use significant resources." echo "" read -p "Are you sure you want to proceed? (yes/no): " confirm_full_scan if [ "$confirm_full_scan" != "yes" ]; then echo "Cancelled." read -p "Press Enter to continue..." return 0 fi echo "" echo "Scan scope: Entire server from /" ;; 2) # All user accounts echo "" echo "Scanning all user home directories..." # Determine user base directory based on control panel local user_base_dir case "$CONTROL_PANEL" in plesk) user_base_dir="/var/www/vhosts" ;; cpanel|interworx|standalone) user_base_dir="/home" ;; *) user_base_dir="/home" ;; esac # Add the user base directory to scan paths scan_paths=("$user_base_dir") scan_description="all user accounts in $user_base_dir" echo "Control Panel: ${CONTROL_PANEL^}" echo "User directory: $user_base_dir" echo "Scan scope: All user home directories" ;; 3) # Specific user echo "" echo "Available users:" select_user_interactive "Select user account to scan" if [ -z "$SELECTED_USER" ]; then echo "No user selected." read -p "Press Enter to continue..." return 1 fi # Get user's docroots for docroot in "${sanitized_docroot[@]}"; do if [[ "$docroot" == *"/$SELECTED_USER/"* ]]; then scan_paths+=("$docroot") fi done if [ ${#scan_paths[@]} -eq 0 ]; then echo -e "${RED}No docroots found for user: $SELECTED_USER${NC}" read -p "Press Enter to continue..." return 1 fi scan_description="user $SELECTED_USER" echo "Found ${#scan_paths[@]} docroots for $SELECTED_USER" ;; 4) # Specific domain echo "" read -p "Enter domain name: " domain if [ -z "$domain" ]; then echo "No domain entered." read -p "Press Enter to continue..." return 1 fi # Find docroot for domain for docroot in "${sanitized_docroot[@]}"; do if [[ "$docroot" == *"/$domain"* ]] || [[ "$docroot" == *"/$domain/"* ]]; then scan_paths+=("$docroot") fi done if [ ${#scan_paths[@]} -eq 0 ]; then echo -e "${RED}No docroot found for domain: $domain${NC}" read -p "Press Enter to continue..." return 1 fi scan_description="domain $domain" echo "Found docroot: ${scan_paths[0]}" ;; 5) # Custom path echo "" read -p "Enter path to scan: " custom_path if [ -z "$custom_path" ]; then echo "No path entered." read -p "Press Enter to continue..." return 1 fi if [ ! -d "$custom_path" ]; then echo -e "${RED}Path does not exist: $custom_path${NC}" read -p "Press Enter to continue..." return 1 fi scan_paths=("$custom_path") scan_description="custom path $custom_path" ;; 0) return 0 ;; *) echo -e "${RED}Invalid option${NC}" sleep 1 return 1 ;; esac # Confirm before generating echo "" echo -e "${YELLOW}Ready to generate standalone scanner${NC}" echo "Scope: $scan_description" echo "Paths: ${#scan_paths[@]}" echo "Scanners: ${available_scanners[@]}" echo "" read -p "Generate and launch? (yes/no): " confirm if [ "$confirm" != "yes" ]; then echo "Cancelled." read -p "Press Enter to continue..." return 0 fi # Generate and launch standalone scanner generate_standalone_scanner "${scan_paths[@]}" } # Check status of all standalone scanners check_standalone_status() { echo "" print_header "Standalone Scanner Status" # Find all malware-* directories in /opt (proper array initialization to handle spaces in names) if [ ! -d /opt ]; then echo "ERROR: /opt directory not found or not accessible" read -p "Press Enter to continue..." return 1 fi local standalone_dirs=() while IFS= read -r dir; do [ -n "$dir" ] && standalone_dirs+=("$dir") done < <(find /opt -maxdepth 1 -type d -name "malware-*" 2>/dev/null | sort -r) if [ ${#standalone_dirs[@]} -eq 0 ]; then echo "No standalone scanner sessions found." echo "" read -p "Press Enter to continue..." return 0 fi echo "Active Sessions:" echo "" local running_count=0 local completed_count=0 local error_count=0 for dir in "${standalone_dirs[@]}"; do local session_name=$(basename "$dir") # Check if still running by looking for bash process executing scan.sh # Use pgrep with exact match to avoid false positives from viewers/editors if pgrep -f "bash $dir/scan.sh" > /dev/null 2>&1 || [ -f "$dir/.scan_running" ]; then echo -e " ${GREEN}●${NC} $session_name [RUNNING]" ((running_count++)) # Show progress if available if [ -f "$dir/logs/session.log" ]; then local last_log=$(tail -1 "$dir/logs/session.log" 2>/dev/null) echo " Latest: $last_log" fi elif [ -f "$dir/results/summary.txt" ]; then # Check if completed successfully if grep -q "Multi-Scanner Session Complete\|Scan session ended" "$dir/results/summary.txt" 2>/dev/null; then echo -e " ${CYAN}✓${NC} $session_name [COMPLETED]" ((completed_count++)) # Show infected count if available if [ -f "$dir/results/infected_files.txt" ] && [ -s "$dir/results/infected_files.txt" ]; then local infected_count=$(wc -l < "$dir/results/infected_files.txt") echo -e " Found: ${RED}$infected_count infected files${NC}" fi else echo -e " ${RED}✗${NC} $session_name [ERROR/INCOMPLETE]" ((error_count++)) fi else echo -e " ${YELLOW}?${NC} $session_name [UNKNOWN - no results yet]" fi echo "" done echo "Summary:" echo " Running: $running_count" echo " Completed: $completed_count" echo " Errors: $error_count" echo " Total: ${#standalone_dirs[@]}" echo "" read -p "Press Enter to continue..." } # Delete standalone scanner sessions delete_standalone_sessions() { echo "" print_header "Delete Standalone Scanner Sessions" # Find all malware-* directories in /opt (proper array initialization to handle spaces in names) if [ ! -d /opt ]; then echo "ERROR: /opt directory not found or not accessible" read -p "Press Enter to continue..." return 1 fi local standalone_dirs=() while IFS= read -r dir; do [ -n "$dir" ] && standalone_dirs+=("$dir") done < <(find /opt -maxdepth 1 -type d -name "malware-*" 2>/dev/null | sort -r) if [ ${#standalone_dirs[@]} -eq 0 ]; then echo "No standalone scanner sessions found." echo "" read -p "Press Enter to continue..." return 0 fi echo "Available sessions:" echo "" # List sessions with status local i=1 for dir in "${standalone_dirs[@]}"; do local session_name=$(basename "$dir") local status="completed" if pgrep -f "bash $dir/scan.sh" > /dev/null 2>&1 || [ -f "$dir/.scan_running" ]; then status="${GREEN}running${NC}" fi echo -e " $i. $session_name [$status]" ((i++)) done echo "" echo " A. Delete all completed sessions" echo " 0. Cancel" echo "" read -p "Select session to delete (or A for all completed): " delete_choice case "$delete_choice" in 0) return 0 ;; [Aa]) # Delete all completed sessions echo "" local deleted=0 for dir in "${standalone_dirs[@]}"; do if ! pgrep -f "$dir/scan.sh" > /dev/null 2>&1; then echo "Deleting: $(basename "$dir")" rm -rf "$dir" ((deleted++)) fi done echo "" echo -e "${GREEN}✓ Deleted $deleted completed session(s)${NC}" ;; *) # Delete specific session # Validate numeric input if ! [[ "$delete_choice" =~ ^[0-9]+$ ]]; then echo -e "${RED}Invalid choice (must be a number)${NC}" read -p "Press Enter to continue..." return 1 fi if [ "$delete_choice" -lt 1 ] || [ "$delete_choice" -gt ${#standalone_dirs[@]} ]; then echo -e "${RED}Invalid choice (out of range)${NC}" read -p "Press Enter to continue..." return 1 fi local selected_dir="${standalone_dirs[$((delete_choice-1))]}" local session_name=$(basename "$selected_dir") # Check if running if pgrep -f "$selected_dir/scan.sh" > /dev/null 2>&1; then echo "" echo -e "${YELLOW}Warning: This scan is currently running!${NC}" read -p "Stop scan and delete? (yes/no): " confirm_running if [ "$confirm_running" = "yes" ]; then pkill -f "$selected_dir/scan.sh" sleep 1 rm -rf "$selected_dir" echo -e "${GREEN}✓ Stopped and deleted: $session_name${NC}" else echo "Cancelled." fi else echo "" read -p "Delete $session_name? (yes/no): " confirm_delete if [ "$confirm_delete" = "yes" ]; then rm -rf "$selected_dir" echo -e "${GREEN}✓ Deleted: $session_name${NC}" else echo "Cancelled." fi fi ;; esac echo "" read -p "Press Enter to continue..." } # Main scan menu show_scan_menu() { # Ensure print_banner is available before calling it if ! declare -f "print_banner" &>/dev/null; then echo "ERROR: print_banner function not found" >&2 return 1 fi # Build reference database once for the entire menu session if command -v build_reference_database &>/dev/null; then build_reference_database 2>/dev/null || true clear fi while true; do # Call print_banner - MUST succeed print_banner "Malware Scanner" || { echo "ERROR: print_banner failed" >&2 return 1 } echo "Available Scanners:" if [ ${#available_scanners[@]} -eq 0 ]; then echo " (None currently installed)" else for scanner in "${available_scanners[@]}"; do echo " • ${scanner^}" done fi echo "" echo -e "${CYAN}Create New Scan:${NC}" echo -e " ${CYAN}1.${NC} Scan entire server (ClamAV, Maldet, RKHunter)" echo -e " ${CYAN}2.${NC} Scan all user accounts (All scanners - recommended)" echo -e " ${CYAN}3.${NC} Scan specific user account (All scanners)" echo -e " ${CYAN}4.${NC} Scan specific domain (All scanners)" echo -e " ${CYAN}5.${NC} Scan custom path (All scanners)" echo "" echo -e "${CYAN}Monitor & Manage:${NC}" echo -e " ${CYAN}6.${NC} Check scan status" echo -e " ${CYAN}7.${NC} View scan results" echo -e " ${CYAN}8.${NC} Delete scan sessions" echo "" echo -e "${CYAN}Configuration:${NC}" echo -e " ${CYAN}9.${NC} Install all scanners" echo -e " ${CYAN}10.${NC} Scanner settings" echo "" echo -e " ${RED}0.${NC} Back" echo "" # Validate choice input with retry loop while true; do read -p "Select option (0-10): " choice if ! [[ "$choice" =~ ^([0-9]|10)$ ]]; then echo -e "${RED}Invalid option${NC}" sleep 1 continue fi case $choice in 1) launch_standalone_scanner_menu "server"; break ;; 2) launch_standalone_scanner_menu "all_users"; break ;; 3) launch_standalone_scanner_menu "user"; break ;; 4) launch_standalone_scanner_menu "domain"; break ;; 5) launch_standalone_scanner_menu "custom"; break ;; 6) check_standalone_status; break ;; 7) view_scan_results; break ;; 8) delete_standalone_sessions; break ;; 9) install_all_scanners; break ;; 10) scanner_settings; break ;; 0) return 0 ;; esac done done } # View scan results view_scan_results() { echo "" print_header "Scan Results" echo "Select results to view:" echo " 1. Toolkit scan results" echo " 2. Standalone scanner results (/opt)" echo " 0. Back" echo "" read -p "Option: " result_type case "$result_type" in 1) # Toolkit scan results echo "" echo "Select scanner to view results:" local i=1 for scanner in "${available_scanners[@]}"; do echo " $i. ${scanner^}" ((i++)) done echo "" read -p "Scanner: " scanner_choice # Validate numeric input if ! [[ "$scanner_choice" =~ ^[0-9]+$ ]]; then echo -e "${RED}Invalid choice (must be a number)${NC}" read -p "Press Enter to continue..." return 1 fi if [ "$scanner_choice" -lt 1 ] || [ "$scanner_choice" -gt ${#available_scanners[@]} ]; then echo -e "${RED}Invalid choice (out of range)${NC}" read -p "Press Enter to continue..." return 1 fi local selected_scanner="${available_scanners[$((scanner_choice-1))]}" echo "" case "$selected_scanner" in imunify) echo "Recent ImunifyAV scans:" imunify-antivirus malware on-demand list --since $(date --date="7 days ago" '+%s') 2>/dev/null || echo "No scans found" ;; clamav) echo "Recent ClamAV scans:" find "$SCRIPT_DIR/logs/malware-scans" -name "clamav_*.log" -mtime -7 2>/dev/null | sort -r | head -5 || echo "No scans found" ;; maldet) echo "Recent Maldet scans:" maldet -l 2>/dev/null || echo "No scans found" ;; esac ;; 2) # Standalone scanner results echo "" echo "Standalone scanner sessions:" echo "" # Find all malware-* directories in /opt local standalone_dirs=($(find /opt -maxdepth 1 -type d -name "malware-*" 2>/dev/null | sort -r)) if [ ${#standalone_dirs[@]} -eq 0 ]; then echo "No standalone scanner sessions found in /opt" echo "" read -p "Press Enter to continue..." return 0 fi # List sessions local i=1 for dir in "${standalone_dirs[@]}"; do local session_name=$(basename "$dir") local scan_date=$(echo "$session_name" | sed 's/malware-//') # Check if still running local status="completed" if pgrep -f "$dir/scan.sh" > /dev/null 2>&1; then status="running" fi echo " $i. $session_name [$status]" ((i++)) done echo "" read -p "Select session (or 0 to cancel): " session_choice # Validate numeric input if ! [[ "$session_choice" =~ ^[0-9]+$ ]]; then echo -e "${RED}Invalid choice (must be a number)${NC}" read -p "Press Enter to continue..." return 1 fi if [ "$session_choice" = "0" ]; then return 0 fi if [ "$session_choice" -lt 1 ] || [ "$session_choice" -gt ${#standalone_dirs[@]} ]; then echo -e "${RED}Invalid choice (out of range)${NC}" read -p "Press Enter to continue..." return 1 fi local selected_dir="${standalone_dirs[$((session_choice-1))]}" echo "" echo "Session: $(basename $selected_dir)" echo "Location: $selected_dir" echo "" # Show results if [ -f "$selected_dir/results/summary.txt" ]; then echo "=== Summary ===" cat "$selected_dir/results/summary.txt" echo "" else echo "Summary not yet available (scan may still be running)" echo "" fi # Show infected files if any if [ -f "$selected_dir/results/infected_files.txt" ] && [ -s "$selected_dir/results/infected_files.txt" ]; then echo "=== Infected Files ===" cat "$selected_dir/results/infected_files.txt" echo "" fi # Show recent log entries if [ -f "$selected_dir/logs/session.log" ]; then echo "=== Recent Log Entries ===" tail -20 "$selected_dir/logs/session.log" echo "" fi echo "View full logs:" echo " tail -f $selected_dir/logs/session.log" echo "" # Offer to generate client report echo -e "${CYAN}Actions:${NC}" echo " 1. Generate client-facing security report" echo " 0. Back to menu" echo "" read -p "Select action (or press Enter to continue): " action_choice case "$action_choice" in 1) generate_client_report "$selected_dir" ;; 0|"") # Continue ;; *) echo -e "${RED}Invalid option${NC}" ;; esac ;; 0) return 0 ;; *) echo -e "${RED}Invalid option${NC}" ;; esac echo "" read -p "Press Enter to continue..." } # Scanner settings scanner_settings() { echo "" print_header "Scanner Settings" echo "Settings (placeholder for future enhancements):" echo " • Auto-quarantine infected files" echo " • Email notifications" echo " • Scheduled scans" echo " • Custom exclusions" echo "" echo "Coming soon..." read -p "Press Enter to continue..." } # Generate client-facing security report generate_client_report() { local scan_dir="$1" if [ ! -d "$scan_dir" ]; then echo -e "${RED}Scan directory not found${NC}" return 1 fi local summary_file="$scan_dir/results/summary.txt" local infected_file="$scan_dir/results/infected_files.txt" local clamav_log="$scan_dir/logs/clamav.log" local session_log="$scan_dir/logs/session.log" local report_file="$scan_dir/results/client_report.txt" if [ ! -f "$summary_file" ]; then echo -e "${RED}Summary file not found - scan may not be complete${NC}" return 1 fi # Extract scan info local session_name=$(basename "$scan_dir") local scan_date=$(grep "Started:" "$summary_file" | head -1 | sed 's/Started: //') local scan_paths=$(sed -n '/^Paths:/,/^$/p' "$summary_file" | tail -n +2 | grep -v "^$" | tr '\n' ', ' | sed 's/, $//') # Count threats local total_threats=0 local imunify_count=$(grep -o "ImunifyAV:.*[0-9]* threats" "$summary_file" | grep -o "[0-9]*" || echo "0") local clamav_count=$(grep -o "ClamAV:.*[0-9]* infected" "$summary_file" | grep -o "[0-9]*" || echo "0") local maldet_hits=$(grep -o "Maldet:.*[0-9]* hits" "$summary_file" | grep -o "[0-9]*" || echo "0") # Calculate total (only real malware, not rootkit warnings) total_threats=$((imunify_count + clamav_count + maldet_hits)) # Analyze infected files for false positives local real_threats=() local false_positives=() if [ -f "$infected_file" ] && [ -s "$infected_file" ]; then while IFS= read -r file; do # Check if likely false positive (logs, stats, cache) if [[ "$file" =~ /logs?/.*\.(log|gz|bz2)$ ]] || \ [[ "$file" =~ /awstats/ ]] || \ [[ "$file" =~ /tmp/.*\.txt$ ]] || \ [[ "$file" =~ \.log\.[0-9]+$ ]]; then false_positives+=("$file") else real_threats+=("$file") fi done < "$infected_file" fi # Generate report { echo "MALWARE SCAN REPORT - $scan_date" echo "═══════════════════════════════════════════════════════════" echo "" echo "Scanned with: ImunifyAV, ClamAV, Linux Maldet, RKHunter" echo "" if [ ${#real_threats[@]} -eq 0 ]; then echo "RESULT: ✅ No malware found - your server is clean" else echo "RESULT: ⚠️ ${#real_threats[@]} infected file(s) detected" echo "" echo "INFECTED FILES:" for file in "${real_threats[@]}"; do echo " • $file" done echo "" echo "NEXT STEPS:" echo " 1. Remove infected files immediately" echo " 2. Change all passwords" echo " 3. Update WordPress/plugins to latest versions" fi if [ ${#false_positives[@]} -gt 0 ]; then echo "" echo "───────────────────────────────────────────────────────────" echo "NOTE: Attack attempts were detected in your server logs." echo "These were successfully blocked. No action needed." fi echo "" echo "Scan ID: $session_name" } > "$report_file" # Display the report echo "" print_header "Client Security Report Generated" echo "" cat "$report_file" echo "" echo -e "${GREEN}Report saved to:${NC} $report_file" echo "" echo "You can now copy/paste this report into your support ticket." echo "" } # Main execution main() { # Detect scanners (populate available_scanners array) # Don't exit if none found - menu option 9 allows installation detect_scanners || true # Verify show_scan_menu exists and is callable if ! declare -f "show_scan_menu" &>/dev/null; then echo "ERROR: show_scan_menu function not found" >&2 return 1 fi # Call the menu function show_scan_menu } # Run if executed directly if [ "${BASH_SOURCE[0]}" = "${0}" ]; then main "$@" fi