Files
Linux-Server-Management-Too…/modules/security/malware-scanner.sh
T
cschantz 46d6885682 Fix stall warning spam in ClamAV scanner
Bug: Stall warning was logging every 0.2s after reaching 60s threshold
Fix: Changed >= to == so it only logs once when counter hits 300

Before: if [ stall_counter -ge 300 ]  (fires forever)
After:  if [ stall_counter -eq 300 ]  (fires once)
2025-12-22 20:18:39 -05:00

2193 lines
78 KiB
Bash
Executable File

#!/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
################################################################################
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
source "$SCRIPT_DIR/lib/common-functions.sh" 2>/dev/null || true
source "$SCRIPT_DIR/lib/system-detect.sh" 2>/dev/null || true
source "$SCRIPT_DIR/lib/user-manager.sh" 2>/dev/null || true
source "$SCRIPT_DIR/lib/reference-db.sh" 2>/dev/null || true
source "$SCRIPT_DIR/lib/ip-reputation.sh" 2>/dev/null || true
# Arrays for docroots and scanners
declare -a docroot_array
declare -a sanitized_docroot
declare -a remove_docroot
declare -a available_scanners
# 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
if [ ${#available_scanners[@]} -eq 0 ]; then
echo -e "${RED}No malware scanners detected!${NC}"
echo ""
show_scanner_installation_guide
return 1
fi
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
/scripts/check_cpanel_rpms --fix --targets=clamav 2>&1 | grep -E "Installing|Updating|up to date"
fi
elif command -v yum &>/dev/null; then
yum install -y clamav clamav-update 2>&1 | grep -E "Installing|Updating|already installed"
elif command -v apt-get &>/dev/null; then
apt-get update && apt-get install -y clamav clamav-daemon
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)..."
$freshclam_bin 2>&1 | grep -E "updated|Downloaded|up-to-date" || $freshclam_bin &>/dev/null
echo -e " ${GREEN}${NC} Signatures updated"
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
wget -q http://www.rfxn.com/downloads/maldetect-current.tar.gz
if [ -f maldetect-current.tar.gz ]; then
tar -xzf maldetect-current.tar.gz
cd maldetect-* 2>/dev/null
./install.sh &>/dev/null
cd /tmp
rm -rf maldetect-*
fi
if is_maldet_installed; then
echo -e "${GREEN}✓ Maldet installed${NC}"
# Update malware signatures immediately
echo " → Updating malware signatures..."
maldet -u 2>&1 | grep -E "update completed|signatures" || maldet -u &>/dev/null
echo -e " ${GREEN}${NC} Signatures updated"
else
echo -e "${RED}✗ Maldet installation failed${NC}"
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" || bash imav-deploy.sh
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..."
$imunify_bin update 2>&1 | grep -E "updated|Success|completed" || $imunify_bin update &>/dev/null
echo -e " ${GREEN}${NC} Signatures updated"
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)
docroot=$(echo "$line" | awk -F'==' '{print $5}')
[ -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=$(echo "$line" | awk -F'==' '{print $5}')
[ -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 ""
# 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
yum remove -y rkhunter &>/dev/null 2>&1
if [ -f "$SESSION_LOG" ]; then
log_message "RKHunter removed"
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"
# 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 [ ${#AVAILABLE_SCANNERS[@]} -eq 0 ]; then
log_message "ERROR: No scanners found!"
echo -e "${RED}No malware scanners detected!${NC}"
exit 1
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)
SCAN_START=$(date +%s)
log_message "ImunifyAV: Updating signatures"
if ! 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 (synchronous)"
# Use on-demand start with background monitoring for progress
LAST_SCAN=""
TOTAL_FILES_SCANNED=0
for path in "${SCAN_PATHS[@]}"; do
if [ -d "$path" ]; then
log_message "ImunifyAV: Scanning $path"
echo ""
echo " 📁 Scanning path: $path"
echo " ⏳ Scanner: ImunifyAV (monitoring progress...)"
echo ""
# Start scan (ImunifyAV runs async, command returns immediately)
imunify-antivirus malware on-demand start --path="$path" &>> "$LOG_DIR/imunify.log"
START_EXIT=$?
if [ $START_EXIT -ne 0 ]; then
log_message "ERROR: ImunifyAV scan failed to start for $path (exit code: $START_EXIT)"
echo " ✗ Scan failed to start for $path (check logs)"
continue
fi
# Monitor progress by polling scan status
# ImunifyAV runs scans asynchronously, we poll the status
sleep 3 # Give scan time to initialize
last_count=0
timeout_counter=0
max_timeout=7200 # 2 hour timeout
scan_running=true
while [ "$scan_running" = true ]; do
# Get current scan status from most recent scan
scan_info=$(imunify-antivirus malware on-demand list 2>/dev/null | tail -n +2 | head -1)
if [ -n "$scan_info" ]; then
completed_time=$(echo "$scan_info" | awk '{print $1}') # Field 1 is COMPLETED timestamp
created_time=$(echo "$scan_info" | awk '{print $2}') # Field 2 is CREATED
current_files=$(echo "$scan_info" | awk '{print $11}') # Field 11 is TOTAL
current_status=$(echo "$scan_info" | awk '{print $7}') # Field 7 is SCAN_STATUS
# Check if this is our scan (created after we started)
if [ "$created_time" -ge "$SCAN_START" ]; then
# Check if scan is complete (COMPLETED field has timestamp)
if [ -n "$completed_time" ] && [ "$completed_time" != "COMPLETED" ] && [ "$completed_time" -gt 0 ]; then
scan_running=false
echo "" # New line after progress
log_message "ImunifyAV scan finished for $path (status: $current_status)"
break
fi
# Update progress if file count changed
if [[ "$current_files" =~ ^[0-9]+$ ]]; then
if [ "$current_files" != "$last_count" ]; then
elapsed=$(($(date +%s) - SCAN_START))
printf "\r Files scanned: %s | Elapsed: %s " \
"$current_files" "$(format_time $elapsed)"
last_count=$current_files
timeout_counter=0
fi
fi
fi
fi
sleep 3
timeout_counter=$((timeout_counter + 3))
if [ $timeout_counter -ge $max_timeout ]; then
log_message "ERROR: ImunifyAV scan timed out after 2 hours for $path"
echo -e "\n ⏱️ Scan timed out (exceeded 2 hour limit)"
# Try to stop the scan
imunify-antivirus malware on-demand stop --path="$path" &>/dev/null
continue 2
fi
done
# Get final scan results
LAST_SCAN=$(imunify-antivirus malware on-demand list 2>/dev/null | tail -n +2 | head -1)
FILES_SCANNED=$(echo "$LAST_SCAN" | awk '{print $11}')
if ! [[ "$FILES_SCANNED" =~ ^[0-9]+$ ]]; then
FILES_SCANNED=0
fi
TOTAL_FILES_SCANNED=$((TOTAL_FILES_SCANNED + FILES_SCANNED))
echo " ✓ Scanned $FILES_SCANNED files in this path"
fi
done
FILES_SCANNED=$TOTAL_FILES_SCANNED
# Extract malicious file count
# Skip header line and count data rows, or use TOTAL_MALICIOUS from most recent scan
if [ -n "$LAST_SCAN" ]; then
IMUNIFY_INFECTED=$(echo "$LAST_SCAN" | awk '{print $12}')
else
IMUNIFY_INFECTED=0
fi
# Verify we got a valid number, otherwise try malicious list
if ! [[ "$IMUNIFY_INFECTED" =~ ^[0-9]+$ ]]; then
IMUNIFY_INFECTED=$(imunify-antivirus malware malicious list 2>/dev/null | tail -n +2 | wc -l || echo 0)
fi
SCAN_END=$(date +%s)
DURATION=$((SCAN_END - SCAN_START))
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
spin_chars='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
spin_index=0
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 "...")
elapsed=$(($(date +%s) - SCAN_START))
spin_char="${spin_chars:$spin_index:1}"
printf "\r Scanning... %s | Last file: %s | Elapsed: %s " \
"$spin_char" "${filename:0:40}" "$(format_time $elapsed)"
else
elapsed=$(($(date +%s) - SCAN_START))
spin_char="${spin_chars:$spin_index:1}"
printf "\r Scanning... %s | Elapsed: %s " "$spin_char" "$(format_time $elapsed)"
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
spin_index=$(( (spin_index + 1) % 10 ))
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
# Create temp path list
TEMP_PATHLIST="/tmp/maldet_paths_$$.txt"
printf '%s\n' "${SCAN_PATHS[@]}" > "$TEMP_PATHLIST"
log_message "Maldet: Starting scan with live progress"
echo ""
echo " 📁 Scanning path(s): ${SCAN_PATHS[*]}"
echo " ⏳ Scanner: Maldet/LMD (Linux-specific malware detection...)"
echo ""
# Run with --progress for real-time percentage updates
# Timeout after 2 hours
timeout 7200 maldet -b -f "$TEMP_PATHLIST" 2>&1 | tee -a "$LOG_DIR/maldet.log" | while IFS= read -r line; do
# Parse progress lines: "files: 1234 (45%)"
if [[ "$line" =~ files:\ ([0-9]+)\ \(([0-9]+)%\) ]]; then
files_so_far="${BASH_REMATCH[1]}"
percent="${BASH_REMATCH[2]}"
printf "\r Progress: %3d%% (%s files scanned) " "$percent" "$files_so_far"
fi
done
MALDET_EXIT=$?
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"
rm -f "$TEMP_PATHLIST"
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"
rm -f "$TEMP_PATHLIST"
SCAN_END=$(date +%s)
DURATION=$((SCAN_END - SCAN_START))
echo ""
continue
fi
# Extract scan results
FILES_SCANNED=$(grep "files scanned" "$LOG_DIR/maldet.log" | tail -1 | awk '{print $1}')
MALDET_HITS=$(grep "malware hits" "$LOG_DIR/maldet.log" | tail -1 | awk '{print $1}')
# Validate numbers
if ! [[ "$FILES_SCANNED" =~ ^[0-9]+$ ]]; then
FILES_SCANNED=0
fi
if ! [[ "$MALDET_HITS" =~ ^[0-9]+$ ]]; then
MALDET_HITS=0
fi
rm -f "$TEMP_PATHLIST"
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=$(echo "$logline" | awk '{print $1}')
if [ -n "$ip" ] && [[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
# Flag this IP in reputation database
if type flag_ip_attack &>/dev/null; then
flag_ip_attack "$ip" "RCE" 25 "Malware scanner: Uploaded $filename" >/dev/null 2>&1
echo " → Flagged IP: $ip (uploaded to $filepath)" >> "$LOG_DIR/flagged_ips.log"
((flagged_ips++))
fi
fi
done
done
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=$(echo "$logline" | awk '{print $1}')
if [ -n "$ip" ] && [[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
# Flag this IP in reputation database
if type flag_ip_attack &>/dev/null; then
flag_ip_attack "$ip" "RCE" 25 "Malware scanner: Uploaded $filename" >/dev/null 2>&1
echo " → Flagged IP: $ip (uploaded to $filepath)" >> "$LOG_DIR/flagged_ips.log"
((flagged_ips++))
fi
fi
done
done
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=$(echo "$logline" | awk '{print $1}')
if [ -n "$ip" ] && [[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
# Flag this IP in reputation database
if type flag_ip_attack &>/dev/null; then
flag_ip_attack "$ip" "RCE" 25 "Malware scanner: Uploaded $filename" >/dev/null 2>&1
echo " → Flagged IP: $ip (uploaded to $filepath)" >> "$LOG_DIR/flagged_ips.log"
((flagged_ips++))
fi
fi
done
done
fi
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
# 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 ""
# Show summary
cat "$SUMMARY_FILE"
echo ""
echo "=========================================="
echo ""
# Prompt for cleanup (RKHunter cleanup handled by trap)
read -p "Delete scan script? (Logs and results will be preserved) (yes/no): " cleanup_choice
if [ "$cleanup_choice" = "yes" ]; 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"
echo ""
echo "Scan script and results preserved at: $SCAN_DIR"
echo ""
fi
echo "You can:"
echo " • Review logs: ls $LOG_DIR"
echo " • View summary: cat $SUMMARY_FILE"
echo " • Delete scan directory manually: rm -rf $SCAN_DIR"
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+=")"
sed -i "s|PLACEHOLDER_SCAN_PATHS|$paths_declaration|" "$session_dir/scan.sh"
# 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
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 ;;
user) scope_choice=2 ;;
domain) scope_choice=3 ;;
custom) scope_choice=4 ;;
*) 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)
# 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"
;;
3)
# 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]}"
;;
4)
# 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
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."
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
if pgrep -f "$dir/scan.sh" > /dev/null 2>&1; 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
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."
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 "$dir/scan.sh" > /dev/null 2>&1; 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() {
# Build reference database once for the entire menu session
if command -v build_reference_database &>/dev/null; then
echo "Building system reference database..."
build_reference_database 2>/dev/null || true
clear
fi
while true; do
print_banner "Malware Scanner"
echo "Available Scanners:"
for scanner in "${available_scanners[@]}"; do
echo "${scanner^}"
done
echo ""
echo -e "${CYAN}Create New Scan:${NC}"
echo " 1. Scan entire server"
echo " 2. Scan specific user"
echo " 3. Scan specific domain"
echo " 4. Scan custom path"
echo ""
echo -e "${CYAN}Monitor & Manage:${NC}"
echo " 5. Check scan status"
echo " 6. View scan results"
echo " 7. Delete scan sessions"
echo ""
echo -e "${CYAN}Configuration:${NC}"
echo " 8. Install all scanners"
echo " 9. Scanner settings"
echo ""
echo -e " ${RED}0.${NC} Back"
echo ""
read -p "Select option: " choice
case $choice in
1) launch_standalone_scanner_menu "server" ;;
2) launch_standalone_scanner_menu "user" ;;
3) launch_standalone_scanner_menu "domain" ;;
4) launch_standalone_scanner_menu "custom" ;;
5) check_standalone_status ;;
6) view_scan_results ;;
7) delete_standalone_sessions ;;
8) install_all_scanners ;;
9) scanner_settings ;;
0) return 0 ;;
*) echo -e "${RED}Invalid option${NC}"; sleep 1 ;;
esac
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"
;;
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..."
}
# Main execution
main() {
if ! detect_scanners; then
exit 1
fi
show_scan_menu
}
# Run if executed directly
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
main "$@"
fi