Files
Linux-Server-Management-Too…/modules/security/malware-scanner.sh
T
cschantz 5151a79d5f Add automated multi-scanner support and result comparison
New Features:
- 'All Available Scanners' option in all scan modes (server/user/domain/custom)
- Runs ImunifyAV, ClamAV, and Maldet sequentially with progress tracking
- Creates consolidated multi-scanner session reports
- Shows [1/3], [2/3], [3/3] progress indicators
- 3-second wait between scanners to prevent system overload
- Session reports saved to logs/malware-scans/multiscan_*.txt
- Stores session IDs in reference database for cross-module access
- New 'Compare scanner results' option (menu option 6)
- View consolidated reports from multiple scanners

Workflow:
1. Select any scan scope (server/user/domain/path)
2. Choose 'All Available Scanners' option
3. All installed scanners run automatically one after another
4. Single consolidated report with all results
5. Use option 6 to compare/view latest multi-scanner session

Much more automated - no need to run each scanner separately!
2025-11-11 18:50:48 -05:00

773 lines
21 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
# Arrays for docroots and scanners
declare -a docroot_array
declare -a sanitized_docroot
declare -a remove_docroot
declare -a available_scanners
# Scanner detection
detect_scanners() {
available_scanners=()
if command -v imunify-antivirus &>/dev/null; then
available_scanners+=("imunify")
fi
if command -v clamscan &>/dev/null; then
available_scanners+=("clamav")
fi
if command -v maldet &>/dev/null; then
available_scanners+=("maldet")
fi
if [ ${#available_scanners[@]} -eq 0 ]; then
echo -e "${RED}No malware scanners detected!${NC}"
echo ""
echo "Available scanners to install:"
echo " • ImunifyAV - Commercial, real-time protection"
echo " • ClamAV - Open source antivirus"
echo " • Maldet (LMD) - Linux Malware Detect"
echo ""
return 1
fi
return 0
}
# Detect control panel and gather docroots
detect_control_panel() {
docroot_array=()
# Detect cPanel
if [ -f "/etc/userdatadomains" ]; then
CONTROL_PANEL="cpanel"
export PATH=/usr/local/cpanel/3rdparty/bin/:$PATH
while IFS= read -r docroot; do
[ -n "$docroot" ] && docroot_array+=("$docroot")
done < <(cut -d= -f5 /etc/userdatadomains | sed 's/==/=/g' | sort -u)
# Detect Plesk
elif [ -f "/usr/local/psa/version" ]; then
CONTROL_PANEL="plesk"
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)
# Detect Interworx
elif [ -d "/usr/local/interworx/" ]; then
CONTROL_PANEL="interworx"
while IFS= read -r docroot; do
[ -n "$docroot" ] && docroot_array+=("$docroot")
done < <(grep -rh "DocumentRoot" /etc/httpd/conf* 2>/dev/null | grep -Ev '^\s*#|/var/www/html($|$)' | sed 's/DocumentRoot//g' | tr -d " " | 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 -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 docroot; do
[ -n "$docroot" ] && user_docroots+=("$docroot")
done < <(grep "^${username}:" /etc/userdatadomains | cut -d= -f5 | sed 's/==/=/g' | sort -u)
else
echo -e "${RED}User-specific scanning only supported on cPanel${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}')
else
echo -e "${RED}Domain lookup only supported on cPanel/Plesk${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
scan_imunify() {
local scan_paths=("$@")
if ! command -v imunify-antivirus &>/dev/null; then
echo -e "${RED}ImunifyAV not installed${NC}"
return 1
fi
echo -e "${CYAN}Starting ImunifyAV scan...${NC}"
echo ""
# Update signatures
echo "→ Updating signatures..."
imunify-antivirus update 2>/dev/null
# Queue scan paths
for path in "${scan_paths[@]}"; do
if [ -d "$path" ]; then
echo "→ Queuing: $path"
imunify-antivirus malware on-demand queue put "$path" 2>/dev/null
fi
done
echo ""
echo -e "${GREEN}✓ Scan queued${NC}"
echo ""
echo "Monitor progress:"
echo " imunify-antivirus malware on-demand list"
}
# ClamAV scanner
scan_clamav() {
local scan_paths=("$@")
if ! command -v clamscan &>/dev/null; then
echo -e "${RED}ClamAV not installed${NC}"
return 1
fi
# Create log directory
local log_dir="$SCRIPT_DIR/logs/malware-scans"
mkdir -p "$log_dir"
local log_file="$log_dir/clamav_$(date +%Y%m%d_%H%M%S).log"
echo -e "${CYAN}Starting ClamAV scan...${NC}"
echo ""
# Update signatures
if command -v freshclam &>/dev/null; then
echo "→ Updating signatures..."
freshclam 2>/dev/null || true
fi
echo "→ Scanning paths..."
echo ""
# Log scan details
{
echo "ClamAV Malware Scan"
echo "Date: $(date)"
echo "Paths:"
printf '%s\n' "${scan_paths[@]}"
echo ""
echo "Results:"
echo "========================================"
} > "$log_file"
# Run scan
clamscan --infected --recursive "${scan_paths[@]}" >> "$log_file" 2>&1 &
local scan_pid=$!
echo "Scan running in background (PID: $scan_pid)"
echo "Log file: $log_file"
echo ""
echo "Monitor with: tail -f $log_file"
# Store scan info in reference DB
store_reference "malware_scan_clamav_latest" "$log_file"
}
# Maldet scanner
scan_maldet() {
local scan_paths=("$@")
if ! command -v maldet &>/dev/null; then
echo -e "${RED}Maldet not installed${NC}"
return 1
fi
# Create temp file with paths
local path_file="/tmp/maldet_paths_$$.txt"
printf '%s\n' "${scan_paths[@]}" > "$path_file"
echo -e "${CYAN}Starting Maldet scan...${NC}"
echo ""
# Update signatures
echo "→ Updating signatures..."
maldet -u 2>/dev/null || true
echo "→ Starting scan..."
maldet -b -f "$path_file"
rm -f "$path_file"
echo ""
echo "View results:"
echo " maldet -l"
}
# Run all available scanners sequentially
run_all_scanners() {
local scan_paths=("$@")
if [ ${#scan_paths[@]} -eq 0 ]; then
echo -e "${RED}No paths to scan${NC}"
return 1
fi
# Create session ID for this multi-scanner run
local session_id="multiscan_$(date +%Y%m%d_%H%M%S)"
local report_file="$SCRIPT_DIR/logs/malware-scans/${session_id}_summary.txt"
mkdir -p "$SCRIPT_DIR/logs/malware-scans"
echo ""
print_header "Multi-Scanner Session: $session_id"
echo "Running ${#available_scanners[@]} scanner(s) on ${#scan_paths[@]} path(s)"
echo "Session report: $report_file"
echo ""
# Initialize report
{
echo "=========================================="
echo "Multi-Scanner Malware Detection Report"
echo "=========================================="
echo "Session ID: $session_id"
echo "Date: $(date)"
echo "Scanners: ${available_scanners[*]}"
echo "Paths: ${#scan_paths[@]}"
echo ""
printf '%s\n' "${scan_paths[@]}"
echo ""
echo "=========================================="
echo ""
} > "$report_file"
local scanner_num=1
local total_scanners=${#available_scanners[@]}
# Run each scanner
for scanner in "${available_scanners[@]}"; do
echo -e "${CYAN}[$scanner_num/$total_scanners] Starting ${scanner^} scan...${NC}"
echo ""
{
echo "Scanner: ${scanner^}"
echo "Started: $(date)"
echo "---"
} >> "$report_file"
case "$scanner" in
imunify)
scan_imunify "${scan_paths[@]}" | tee -a "$report_file"
;;
clamav)
scan_clamav "${scan_paths[@]}" | tee -a "$report_file"
;;
maldet)
scan_maldet "${scan_paths[@]}" | tee -a "$report_file"
;;
esac
echo "" | tee -a "$report_file"
echo "---" >> "$report_file"
echo "" >> "$report_file"
((scanner_num++))
# Wait a moment between scanners
if [ $scanner_num -le $total_scanners ]; then
echo ""
echo "Waiting 3 seconds before next scanner..."
sleep 3
echo ""
fi
done
# Finalize report
{
echo "=========================================="
echo "Multi-Scanner Session Complete"
echo "Completed: $(date)"
echo "=========================================="
} >> "$report_file"
echo ""
echo -e "${GREEN}✓ All scanners completed${NC}"
echo ""
echo "Session report saved: $report_file"
echo ""
echo "View individual scanner results using option 5 from main menu"
# Store in reference database
store_reference "malware_multiscan_latest" "$session_id"
store_reference "malware_multiscan_${session_id}" "$report_file"
echo ""
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..."
}
# Main scan menu
show_scan_menu() {
while true; do
print_banner "Malware Scanner"
echo "Available Scanners:"
for scanner in "${available_scanners[@]}"; do
echo "${scanner^}"
done
echo ""
echo "Scan Scope:"
echo " 1. Scan entire server"
echo " 2. Scan specific user"
echo " 3. Scan specific domain"
echo " 4. Scan custom path"
echo ""
echo "Results & Management:"
echo " 5. View scan results"
echo " 6. Compare scanner results"
echo " 7. Scanner settings"
echo ""
echo " 0. Back to main menu"
echo ""
read -p "Select option: " choice
case $choice in
1) scan_entire_server ;;
2) scan_user_account ;;
3) scan_domain ;;
4) scan_custom_path ;;
5) view_scan_results ;;
6) compare_scan_results ;;
7) scanner_settings ;;
0) return 0 ;;
*) echo -e "${RED}Invalid option${NC}"; sleep 1 ;;
esac
done
}
# Scan entire server
scan_entire_server() {
echo ""
print_header "Full Server Scan"
if ! detect_control_panel; then
read -p "Press Enter to continue..."
return 1
fi
echo "Control Panel: ${CONTROL_PANEL^}"
echo "Docroots found: ${#sanitized_docroot[@]}"
echo ""
if ! check_memory; then
read -p "Press Enter to continue..."
return 1
fi
echo "Select scanner:"
local i=1
for scanner in "${available_scanners[@]}"; do
echo " $i. ${scanner^}"
((i++))
done
echo " $i. All Available Scanners (run sequentially)"
echo ""
read -p "Scanner: " scanner_choice
# Check for "All Scanners" option
if [ "$scanner_choice" -eq "$i" ]; then
run_all_scanners "${sanitized_docroot[@]}"
elif [ "$scanner_choice" -lt 1 ] || [ "$scanner_choice" -gt ${#available_scanners[@]} ]; then
echo -e "${RED}Invalid choice${NC}"
read -p "Press Enter to continue..."
return 1
else
local selected_scanner="${available_scanners[$((scanner_choice-1))]}"
case "$selected_scanner" in
imunify) scan_imunify "${sanitized_docroot[@]}" ;;
clamav) scan_clamav "${sanitized_docroot[@]}" ;;
maldet) scan_maldet "${sanitized_docroot[@]}" ;;
esac
fi
echo ""
read -p "Press Enter to continue..."
}
# Scan user account
scan_user_account() {
echo ""
print_header "Scan User Account"
if ! detect_control_panel; then
read -p "Press Enter to continue..."
return 1
fi
# Use user manager to select user
select_user_interactive "Select user to scan"
if [ -z "$SELECTED_USER" ]; then
echo "No user selected"
read -p "Press Enter to continue..."
return 1
fi
echo ""
echo "Getting docroots for: $SELECTED_USER"
local user_paths=($(get_user_docroots "$SELECTED_USER"))
if [ ${#user_paths[@]} -eq 0 ]; then
echo -e "${RED}No docroots found for user${NC}"
read -p "Press Enter to continue..."
return 1
fi
echo "Paths to scan: ${#user_paths[@]}"
printf ' %s\n' "${user_paths[@]}"
echo ""
echo "Select scanner:"
local i=1
for scanner in "${available_scanners[@]}"; do
echo " $i. ${scanner^}"
((i++))
done
echo " $i. All Available Scanners (run sequentially)"
echo ""
read -p "Scanner: " scanner_choice
# Check for "All Scanners" option
if [ "$scanner_choice" -eq "$i" ]; then
run_all_scanners "${user_paths[@]}"
elif [ "$scanner_choice" -lt 1 ] || [ "$scanner_choice" -gt ${#available_scanners[@]} ]; then
echo -e "${RED}Invalid choice${NC}"
read -p "Press Enter to continue..."
return 1
else
local selected_scanner="${available_scanners[$((scanner_choice-1))]}"
case "$selected_scanner" in
imunify) scan_imunify "${user_paths[@]}" ;;
clamav) scan_clamav "${user_paths[@]}" ;;
maldet) scan_maldet "${user_paths[@]}" ;;
esac
fi
echo ""
read -p "Press Enter to continue..."
}
# Scan domain
scan_domain() {
echo ""
print_header "Scan Domain"
if ! detect_control_panel; then
read -p "Press Enter to continue..."
return 1
fi
read -p "Enter domain name: " domain
if [ -z "$domain" ]; then
echo "No domain entered"
read -p "Press Enter to continue..."
return 1
fi
local domain_path=$(get_domain_docroot "$domain")
if [ -z "$domain_path" ] || [ ! -d "$domain_path" ]; then
echo -e "${RED}Domain not found or docroot doesn't exist${NC}"
read -p "Press Enter to continue..."
return 1
fi
echo "Docroot: $domain_path"
echo ""
echo "Select scanner:"
local i=1
for scanner in "${available_scanners[@]}"; do
echo " $i. ${scanner^}"
((i++))
done
echo " $i. All Available Scanners (run sequentially)"
echo ""
read -p "Scanner: " scanner_choice
# Check for "All Scanners" option
if [ "$scanner_choice" -eq "$i" ]; then
run_all_scanners "$domain_path"
elif [ "$scanner_choice" -lt 1 ] || [ "$scanner_choice" -gt ${#available_scanners[@]} ]; then
echo -e "${RED}Invalid choice${NC}"
read -p "Press Enter to continue..."
return 1
else
local selected_scanner="${available_scanners[$((scanner_choice-1))]}"
case "$selected_scanner" in
imunify) scan_imunify "$domain_path" ;;
clamav) scan_clamav "$domain_path" ;;
maldet) scan_maldet "$domain_path" ;;
esac
fi
echo ""
read -p "Press Enter to continue..."
}
# Scan custom path
scan_custom_path() {
echo ""
print_header "Scan Custom Path"
read -p "Enter path to scan: " custom_path
if [ -z "$custom_path" ] || [ ! -d "$custom_path" ]; then
echo -e "${RED}Path doesn't exist${NC}"
read -p "Press Enter to continue..."
return 1
fi
echo "Path: $custom_path"
echo ""
echo "Select scanner:"
local i=1
for scanner in "${available_scanners[@]}"; do
echo " $i. ${scanner^}"
((i++))
done
echo " $i. All Available Scanners (run sequentially)"
echo ""
read -p "Scanner: " scanner_choice
# Check for "All Scanners" option
if [ "$scanner_choice" -eq "$i" ]; then
run_all_scanners "$custom_path"
elif [ "$scanner_choice" -lt 1 ] || [ "$scanner_choice" -gt ${#available_scanners[@]} ]; then
echo -e "${RED}Invalid choice${NC}"
read -p "Press Enter to continue..."
return 1
else
local selected_scanner="${available_scanners[$((scanner_choice-1))]}"
case "$selected_scanner" in
imunify) scan_imunify "$custom_path" ;;
clamav) scan_clamav "$custom_path" ;;
maldet) scan_maldet "$custom_path" ;;
esac
fi
echo ""
read -p "Press Enter to continue..."
}
# View scan results
view_scan_results() {
echo ""
print_header "Scan Results"
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
if [ "$scanner_choice" -lt 1 ] || [ "$scanner_choice" -gt ${#available_scanners[@]} ]; then
echo -e "${RED}Invalid choice${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
echo ""
read -p "Press Enter to continue..."
}
# Scanner settings
scanner_settings() {
echo ""
print_header "Scanner Settings"
echo "Settings (placeholder for future enhancements):"
echo " • Auto-quarantine infected files"
echo " • Email notifications"
echo " • Scheduled scans"
echo " • Custom exclusions"
echo ""
echo "Coming soon..."
read -p "Press Enter to continue..."
}
# Main execution
main() {
if ! detect_scanners; then
exit 1
fi
show_scan_menu
}
# Run if executed directly
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
main "$@"
fi