Files
Linux-Server-Management-Too…/modules/security/malware-scanner.sh
T
cschantz 72047b4098 Fix Maldet directory detection after extraction
Problem:
- cd maldetect-* was failing because glob expansion doesn't work
  reliably in this context
- Error: "Cannot find extracted directory"

Solution:
- Use find command to locate extracted directory explicitly
- Store directory path in variable before cd
- Add diagnostic output showing available directories on failure
- More robust error handling with explicit directory checks
2026-01-02 21:29:37 -05:00

2518 lines
91 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 || { 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..."
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}"
# 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" || 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 ""
# 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
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"
# 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 [ ${#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)
# 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 ! 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"
# Use on-demand start with background monitoring for progress
LAST_SCAN=""
TOTAL_FILES_SCANNED=0
# For user-focused scans, use paths as-is
IMUNIFY_SCAN_PATHS=("${SCAN_PATHS[@]}")
for path in "${IMUNIFY_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:-1}" -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" =~ ^[0-9]+$ ]] && [ "$created_time" -ge "$SCAN_START" ]; then
# Check if scan is complete (COMPLETED field has timestamp)
if [ -n "$completed_time" ] && [ "$completed_time" != "COMPLETED" ] && [[ "$completed_time" =~ ^[0-9]+$ ]] && [ "$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
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
FILES_SCANNED=$(grep "scan completed" /usr/local/maldetect/logs/event_log | tail -1 | grep -oP 'files \K[0-9]+' || echo 0)
MALDET_HITS=$(grep "scan completed" /usr/local/maldetect/logs/event_log | tail -1 | grep -oP 'malware hits \K[0-9]+' || echo 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=$(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
# 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)
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 ;;
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
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 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
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 "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() {
# 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 (ClamAV, Maldet, RKHunter)"
echo " 2. Scan all user accounts (All scanners - recommended)"
echo " 3. Scan specific user account (All scanners)"
echo " 4. Scan specific domain (All scanners)"
echo " 5. Scan custom path (All scanners)"
echo ""
echo -e "${CYAN}Monitor & Manage:${NC}"
echo " 6. Check scan status"
echo " 7. View scan results"
echo " 8. Delete scan sessions"
echo ""
echo -e "${CYAN}Configuration:${NC}"
echo " 9. Install all scanners"
echo " 10. 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 "all_users" ;;
3) launch_standalone_scanner_menu "user" ;;
4) launch_standalone_scanner_menu "domain" ;;
5) launch_standalone_scanner_menu "custom" ;;
6) check_standalone_status ;;
7) view_scan_results ;;
8) delete_standalone_sessions ;;
9) install_all_scanners ;;
10) 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"
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() {
if ! detect_scanners; then
exit 1
fi
show_scan_menu
}
# Run if executed directly
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
main "$@"
fi