0598bbd5ff
Problem: Client report file was not being created during scans. The cat command showed: No such file or directory Root Cause: When standalone scans are launched, the script is COPIED to /opt/malware-*/. The generate_client_report() function exists in the main malware-scanner.sh, but NOT in the standalone copy. When completion code tried to call the function, it silently failed because function didn't exist. Solution: Replaced function call with inline client report generation. Added check: if function exists, use it; otherwise generate inline. This ensures client reports work in BOTH contexts: 1. Interactive menu scans (function exists) 2. Standalone copied scripts (uses inline version) The inline version: - Extracts scan date and paths from summary file - Analyzes infected_files.txt for false positives - Categorizes: logs/awstats = false positive, others = real threat - Generates same format report as function version - Writes to: /opt/malware-*/results/client_report.txt Now client reports are ALWAYS generated at scan completion, regardless of how the scan was launched.
2636 lines
100 KiB
Bash
Executable File
2636 lines
100 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 ""
|
||
|
||
# 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
|
||
local 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 -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
|
||
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
|
||
|
||
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 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo "SECURITY SCAN REPORT"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
echo "Scan Date: $scan_date"
|
||
echo "Scan Coverage: $scan_paths"
|
||
echo ""
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo "OVERALL STATUS"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
|
||
if [ "$real_threats_count" -eq 0 ]; then
|
||
echo "✅ NO ACTIVE MALWARE DETECTED"
|
||
echo ""
|
||
echo "Your server is clean. No malicious files were found in"
|
||
echo "web-accessible directories or user content areas."
|
||
else
|
||
echo "⚠️ MALWARE DETECTED - ACTION REQUIRED"
|
||
echo ""
|
||
echo "Found $real_threats_count infected file(s) that require immediate attention."
|
||
fi
|
||
echo ""
|
||
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo "SCAN DETAILS"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
echo "We performed a comprehensive security scan using multiple"
|
||
echo "industry-standard malware detection engines:"
|
||
echo ""
|
||
echo " • ImunifyAV - Advanced threat detection"
|
||
echo " • ClamAV - Open-source antivirus engine"
|
||
echo " • Linux Maldet - Web malware specialist"
|
||
echo " • Rootkit Hunter - System integrity checker"
|
||
echo ""
|
||
|
||
if [ "$real_threats_count" -gt 0 ]; then
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo "INFECTED FILES REQUIRING ATTENTION"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
echo "$real_threats_list"
|
||
echo "RECOMMENDED ACTIONS:"
|
||
echo ""
|
||
echo "1. Review each file to confirm it is malicious"
|
||
echo "2. Remove or quarantine infected files immediately"
|
||
echo "3. Change all passwords (hosting, FTP, database, CMS admin)"
|
||
echo "4. Review file upload functionality in web applications"
|
||
echo "5. Update all web applications, plugins, and themes"
|
||
echo "6. Check access logs for unauthorized access patterns"
|
||
echo ""
|
||
fi
|
||
|
||
if [ -n "$false_positives_list" ]; then
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo "INFORMATIONAL DETECTIONS (No Action Required)"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
echo "The following files triggered alerts but are likely false"
|
||
echo "positives. These are log files that contain records of"
|
||
echo "attack attempts against your server (which were blocked):"
|
||
echo ""
|
||
echo "$false_positives_list"
|
||
echo "These files are safe and contain evidence of your server"
|
||
echo "correctly blocking malicious requests. No action needed."
|
||
echo ""
|
||
fi
|
||
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo "ONGOING SECURITY RECOMMENDATIONS"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
echo "To maintain server security, we recommend:"
|
||
echo ""
|
||
echo " ✓ Run malware scans monthly (or after any security incident)"
|
||
echo " ✓ Keep all software updated (WordPress, plugins, PHP, etc.)"
|
||
echo " ✓ Use strong, unique passwords for all accounts"
|
||
echo " ✓ Enable automatic security updates where possible"
|
||
echo " ✓ Review file permissions regularly"
|
||
echo " ✓ Monitor server logs for suspicious activity"
|
||
echo " ✓ Maintain regular backups (stored off-server)"
|
||
echo ""
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo "TECHNICAL DETAILS"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
echo "Scan ID: $(basename $SCAN_DIR)"
|
||
echo "Report Generated: $(date)"
|
||
echo ""
|
||
echo "For technical details and full scan logs, please contact"
|
||
echo "your system administrator."
|
||
echo ""
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
} > "$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 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo "SECURITY SCAN REPORT"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
echo "Scan Date: $scan_date"
|
||
echo "Scan Coverage: $scan_paths"
|
||
echo ""
|
||
|
||
# Overall status
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo "OVERALL STATUS"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
|
||
if [ ${#real_threats[@]} -eq 0 ]; then
|
||
echo "✅ NO ACTIVE MALWARE DETECTED"
|
||
echo ""
|
||
echo "Your server is clean. No malicious files were found in"
|
||
echo "web-accessible directories or user content areas."
|
||
else
|
||
echo "⚠️ MALWARE DETECTED - ACTION REQUIRED"
|
||
echo ""
|
||
echo "Found ${#real_threats[@]} infected file(s) that require immediate attention."
|
||
fi
|
||
echo ""
|
||
|
||
# Scan details
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo "SCAN DETAILS"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
echo "We performed a comprehensive security scan using multiple"
|
||
echo "industry-standard malware detection engines:"
|
||
echo ""
|
||
echo " • ImunifyAV - Advanced threat detection"
|
||
echo " • ClamAV - Open-source antivirus engine"
|
||
echo " • Linux Maldet - Web malware specialist"
|
||
echo " • Rootkit Hunter - System integrity checker"
|
||
echo ""
|
||
|
||
# Real threats section
|
||
if [ ${#real_threats[@]} -gt 0 ]; then
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo "INFECTED FILES REQUIRING ATTENTION"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
|
||
for file in "${real_threats[@]}"; do
|
||
echo "📁 $file"
|
||
|
||
# Get detection details from ClamAV log
|
||
if [ -f "$clamav_log" ]; then
|
||
local detection=$(grep "$file" "$clamav_log" | grep "FOUND" | sed 's/.*: / /' || echo " Detection: Unknown signature")
|
||
echo "$detection"
|
||
fi
|
||
echo ""
|
||
done
|
||
|
||
echo "RECOMMENDED ACTIONS:"
|
||
echo ""
|
||
echo "1. Review each file to confirm it is malicious"
|
||
echo "2. Remove or quarantine infected files immediately"
|
||
echo "3. Change all passwords (hosting, FTP, database, CMS admin)"
|
||
echo "4. Review file upload functionality in web applications"
|
||
echo "5. Update all web applications, plugins, and themes"
|
||
echo "6. Check access logs for unauthorized access patterns"
|
||
echo ""
|
||
fi
|
||
|
||
# False positives section
|
||
if [ ${#false_positives[@]} -gt 0 ]; then
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo "INFORMATIONAL DETECTIONS (No Action Required)"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
echo "The following files triggered alerts but are likely false"
|
||
echo "positives. These are log files that contain records of"
|
||
echo "attack attempts against your server (which were blocked):"
|
||
echo ""
|
||
|
||
for file in "${false_positives[@]}"; do
|
||
echo " • $file"
|
||
done
|
||
echo ""
|
||
echo "These files are safe and contain evidence of your server"
|
||
echo "correctly blocking malicious requests. No action needed."
|
||
echo ""
|
||
fi
|
||
|
||
# Security observations
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo "SECURITY OBSERVATIONS"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
|
||
# Check for attack patterns in logs
|
||
if [ -f "$clamav_log" ]; then
|
||
local attack_signatures=$(grep -o "YARA\.[a-z0-9_]*" "$clamav_log" | sort -u | sed 's/YARA\.//' | head -5)
|
||
if [ -n "$attack_signatures" ]; then
|
||
echo "Attack Patterns Detected in Logs:"
|
||
echo ""
|
||
echo "$attack_signatures" | while read sig; do
|
||
case "$sig" in
|
||
*r57*|*c99*|*shell*)
|
||
echo " • Web shell upload attempts (${sig})"
|
||
;;
|
||
*sql*)
|
||
echo " • SQL injection attempts (${sig})"
|
||
;;
|
||
*)
|
||
echo " • Malicious activity pattern: ${sig}"
|
||
;;
|
||
esac
|
||
done
|
||
echo ""
|
||
echo "These attack attempts were blocked by your server, but"
|
||
echo "they indicate your site is being actively targeted."
|
||
echo ""
|
||
fi
|
||
fi
|
||
|
||
# General recommendations
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo "ONGOING SECURITY RECOMMENDATIONS"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
echo "To maintain server security, we recommend:"
|
||
echo ""
|
||
echo " ✓ Run malware scans monthly (or after any security incident)"
|
||
echo " ✓ Keep all software updated (WordPress, plugins, PHP, etc.)"
|
||
echo " ✓ Use strong, unique passwords for all accounts"
|
||
echo " ✓ Enable automatic security updates where possible"
|
||
echo " ✓ Review file permissions regularly"
|
||
echo " ✓ Monitor server logs for suspicious activity"
|
||
echo " ✓ Maintain regular backups (stored off-server)"
|
||
echo ""
|
||
|
||
# Footer
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo "TECHNICAL DETAILS"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
echo "Scan ID: $session_name"
|
||
echo "Report Generated: $(date)"
|
||
echo ""
|
||
echo "For technical details and full scan logs, please contact"
|
||
echo "your system administrator."
|
||
echo ""
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
|
||
} > "$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
|