diff --git a/modules/security/suspicious-login-monitor.sh b/modules/security/suspicious-login-monitor.sh index 9de34d7..23a7552 100755 --- a/modules/security/suspicious-login-monitor.sh +++ b/modules/security/suspicious-login-monitor.sh @@ -1,8 +1,9 @@ #!/bin/bash # -# Suspicious Login Monitor - Integrated Security Analysis -# Detects suspicious login patterns and correlates with web attack activity +# Suspicious Login Monitor - Integrated Security Analysis & Compromise Detection +# Detects suspicious login patterns, correlates with web attack activity, +# and checks for actual system compromise indicators # Supports: cPanel, Plesk, InterWorx, Standalone # @@ -945,6 +946,419 @@ correlate_with_threat_intel() { echo "$additional_risk|$notes" } +# +# COMPROMISE DETECTION - Check for actual root compromise indicators +# + +check_backdoor_accounts() { + echo " Checking for backdoor user accounts..." >&2 + + local findings="" + local risk=0 + + # Check for multiple UID 0 accounts (besides root) + local uid0_accounts=$(awk -F: '$3 == 0 && $1 != "root" {print $1}' /etc/passwd) + if [ -n "$uid0_accounts" ]; then + findings="${findings}Unauthorized-UID-0-Accounts:$(echo $uid0_accounts | tr '\n' ',') " + risk=$((risk + 50)) + fi + + # Check for accounts with no password + local no_pass=$(awk -F: '$2 == "" {print $1}' /etc/shadow 2>/dev/null) + if [ -n "$no_pass" ]; then + findings="${findings}No-Password-Accounts:$(echo $no_pass | tr '\n' ',') " + risk=$((risk + 30)) + fi + + # Check for recently added users (last 7 days) + local recent_users=$(awk -F: -v cutoff=$(date -d '7 days ago' +%s) ' + $3 >= 1000 { + cmd = "stat -c %Y /home/" $1 " 2>/dev/null" + cmd | getline created + close(cmd) + if (created > cutoff) print $1 + } + ' /etc/passwd) + + if [ -n "$recent_users" ]; then + findings="${findings}Recently-Added-Users:$(echo $recent_users | tr '\n' ',') " + risk=$((risk + 20)) + fi + + # Check for suspicious usernames (common backdoor names) + local suspicious_users=$(awk -F: ' + $1 ~ /^(test|temp|backup|hacker|admin|ftpuser|nobody2|bin2|daemon2)$/ && $3 >= 1000 { + print $1 + } + ' /etc/passwd) + + if [ -n "$suspicious_users" ]; then + findings="${findings}Suspicious-Usernames:$(echo $suspicious_users | tr '\n' ',') " + risk=$((risk + 25)) + fi + + echo "$risk|$findings" +} + +check_unauthorized_ssh_keys() { + echo " Checking for unauthorized SSH keys..." >&2 + + local findings="" + local risk=0 + + # Check root's authorized_keys + if [ -f /root/.ssh/authorized_keys ]; then + local key_count=$(grep -v "^#" /root/.ssh/authorized_keys 2>/dev/null | grep -c "ssh-") + if [ "$key_count" -gt 5 ]; then + findings="${findings}Excessive-Root-SSH-Keys:$key_count " + risk=$((risk + 20)) + fi + + # Check for keys with suspicious comments + local suspicious_keys=$(grep -v "^#" /root/.ssh/authorized_keys 2>/dev/null | grep -i "hacker\|backdoor\|pwned\|rooted") + if [ -n "$suspicious_keys" ]; then + findings="${findings}Suspicious-SSH-Key-Comments " + risk=$((risk + 40)) + fi + + # Check file permissions (should be 600) + local perms=$(stat -c %a /root/.ssh/authorized_keys 2>/dev/null) + if [ "$perms" != "600" ] && [ "$perms" != "400" ]; then + findings="${findings}Incorrect-SSH-Key-Permissions:$perms " + risk=$((risk + 15)) + fi + fi + + # Check for authorized_keys in unusual locations + local unusual_keys=$(find /tmp /var/tmp /dev/shm -name "authorized_keys" 2>/dev/null) + if [ -n "$unusual_keys" ]; then + findings="${findings}SSH-Keys-In-Unusual-Locations " + risk=$((risk + 35)) + fi + + echo "$risk|$findings" +} + +check_system_file_tampering() { + echo " Checking for system file tampering..." >&2 + + local findings="" + local risk=0 + + # Check /etc/passwd modification time (recent changes suspicious) + local passwd_age=$(($(date +%s) - $(stat -c %Y /etc/passwd 2>/dev/null))) + if [ "$passwd_age" -lt 86400 ]; then # Modified in last 24 hours + findings="${findings}/etc/passwd-Modified-Recently " + risk=$((risk + 25)) + fi + + # Check /etc/shadow modification time + local shadow_age=$(($(date +%s) - $(stat -c %Y /etc/shadow 2>/dev/null))) + if [ "$shadow_age" -lt 86400 ]; then + findings="${findings}/etc/shadow-Modified-Recently " + risk=$((risk + 25)) + fi + + # Check for suspicious entries in /etc/passwd + local backdoor_shells=$(awk -F: '$7 !~ /\/(bash|sh|nologin|false)$/ && $7 != "" {print $1":"$7}' /etc/passwd) + if [ -n "$backdoor_shells" ]; then + findings="${findings}Suspicious-Login-Shells " + risk=$((risk + 30)) + fi + + # Check sudoers for unauthorized entries + if [ -f /etc/sudoers ]; then + local suspicious_sudo=$(grep -v "^#" /etc/sudoers 2>/dev/null | grep "NOPASSWD" | grep -v "root") + if [ -n "$suspicious_sudo" ]; then + findings="${findings}Suspicious-Sudoers-Entries " + risk=$((risk + 35)) + fi + fi + + echo "$risk|$findings" +} + +check_suspicious_processes() { + echo " Checking for suspicious processes..." >&2 + + local findings="" + local risk=0 + + # Check for processes with suspicious names + local suspicious_procs=$(ps aux | grep -E "nc -l|ncat -l|/dev/tcp|bash -i|perl.*socket|python.*socket" | grep -v grep) + if [ -n "$suspicious_procs" ]; then + findings="${findings}Reverse-Shell-Processes " + risk=$((risk + 50)) + fi + + # Check for hidden processes (spaces in name) + local hidden_procs=$(ps aux | awk '$11 ~ /^[ ]+$/ {print $2}') + if [ -n "$hidden_procs" ]; then + findings="${findings}Hidden-Processes " + risk=$((risk + 40)) + fi + + # Check for processes running from /tmp or /dev/shm + local tmp_procs=$(lsof -p $(ps aux | awk '$11 ~ /\/(tmp|dev\/shm)/ {print $2}' | tr '\n' ',') 2>/dev/null | grep -c "^COMMAND") + if [ "$tmp_procs" -gt 0 ]; then + findings="${findings}Processes-From-Tmp:$tmp_procs " + risk=$((risk + 30)) + fi + + # Check for unusual network connections + local suspicious_conns=$(netstat -antp 2>/dev/null | grep ESTABLISHED | awk '{print $5}' | cut -d: -f1 | sort -u | wc -l) + if [ "$suspicious_conns" -gt 50 ]; then + findings="${findings}Excessive-Network-Connections:$suspicious_conns " + risk=$((risk + 20)) + fi + + echo "$risk|$findings" +} + +check_backdoor_cron_jobs() { + echo " Checking for backdoor cron jobs..." >&2 + + local findings="" + local risk=0 + + # Check root crontab + local suspicious_cron=$(crontab -l 2>/dev/null | grep -v "^#" | grep -E "wget|curl|nc |bash -i|/tmp/|/dev/shm|base64") + if [ -n "$suspicious_cron" ]; then + findings="${findings}Suspicious-Root-Cron-Jobs " + risk=$((risk + 45)) + fi + + # Check /etc/cron.d for suspicious entries + local suspicious_cron_d=$(grep -r "wget\|curl\|nc \|bash -i" /etc/cron.d/ 2>/dev/null | grep -v "^#") + if [ -n "$suspicious_cron_d" ]; then + findings="${findings}Suspicious-Cron.d-Entries " + risk=$((risk + 45)) + fi + + # Check for cron jobs in unusual locations + local unusual_crons=$(find /tmp /var/tmp /dev/shm -name "cron*" -o -name "*.cron" 2>/dev/null) + if [ -n "$unusual_crons" ]; then + findings="${findings}Cron-Jobs-In-Unusual-Locations " + risk=$((risk + 40)) + fi + + echo "$risk|$findings" +} + +check_bash_history_malicious_commands() { + echo " Analyzing bash history for malicious commands..." >&2 + + local findings="" + local risk=0 + + if [ -f /root/.bash_history ]; then + # Check for common attack commands + local malicious_cmds=$(grep -E "wget.*\/tmp\/|curl.*bash|nc -l|bash -i|chmod \+s|chattr \+i" /root/.bash_history 2>/dev/null) + if [ -n "$malicious_cmds" ]; then + findings="${findings}Malicious-Commands-In-History " + risk=$((risk + 40)) + fi + + # Check for history clearing attempts + local history_tampering=$(grep -E "history -c|rm.*bash_history|unset HISTFILE" /root/.bash_history 2>/dev/null) + if [ -n "$history_tampering" ]; then + findings="${findings}History-Tampering-Detected " + risk=$((risk + 35)) + fi + + # Check for password hash modifications + local passwd_mods=$(grep -E "echo.*\/etc\/passwd|echo.*\/etc\/shadow|vipw|usermod.*-p" /root/.bash_history 2>/dev/null) + if [ -n "$passwd_mods" ]; then + findings="${findings}Password-File-Manipulation " + risk=$((risk + 45)) + fi + fi + + # Check if history is disabled + if ! grep -q "^HISTFILE=" /root/.bashrc 2>/dev/null && [ ! -f /root/.bash_history ]; then + findings="${findings}Bash-History-Disabled " + risk=$((risk + 25)) + fi + + echo "$risk|$findings" +} + +check_web_shells() { + echo " Scanning for web shells..." >&2 + + local findings="" + local risk=0 + local web_roots="" + + # Determine web roots based on panel + if [ -d /home ]; then + web_roots="/home/*/public_html /var/www/html /usr/local/apache/htdocs" + fi + + # Scan for common web shell patterns (limit to recent files for performance) + local suspicious_files=$(find $web_roots -type f -name "*.php" -mtime -7 2>/dev/null | head -50 | xargs grep -l "eval(\|base64_decode(\|system(\|exec(\|passthru(\|shell_exec(" 2>/dev/null | head -10) + + if [ -n "$suspicious_files" ]; then + local file_count=$(echo "$suspicious_files" | wc -l) + findings="${findings}Potential-Web-Shells:$file_count " + risk=$((risk + 35)) + fi + + # Check for suspicious PHP files in unusual locations + local unusual_php=$(find /tmp /var/tmp /dev/shm -name "*.php" 2>/dev/null) + if [ -n "$unusual_php" ]; then + findings="${findings}PHP-Files-In-Tmp " + risk=$((risk + 40)) + fi + + echo "$risk|$findings" +} + +check_rootkit_indicators() { + echo " Checking for rootkit indicators..." >&2 + + local findings="" + local risk=0 + + # Check for common rootkit files + local rootkit_files="/dev/.hid /usr/bin/.sniffer /usr/local/hide /lib/libproc.a" + for file in $rootkit_files; do + if [ -e "$file" ]; then + findings="${findings}Rootkit-File:$file " + risk=$((risk + 50)) + fi + done + + # Check for suspicious kernel modules + local suspicious_modules=$(lsmod | grep -E "^(diamond|phalanx|beastkit)") + if [ -n "$suspicious_modules" ]; then + findings="${findings}Suspicious-Kernel-Modules " + risk=$((risk + 50)) + fi + + # Check for modified binaries (ls, ps, netstat) + for cmd in /bin/ls /bin/ps /bin/netstat; do + if [ -f "$cmd" ]; then + local strings_check=$(strings "$cmd" 2>/dev/null | grep -i "backdoor\|rootkit\|hide") + if [ -n "$strings_check" ]; then + findings="${findings}Modified-Binary:$cmd " + risk=$((risk + 50)) + fi + fi + done + + # Check for hidden directories (... or . or . ) + local hidden_dirs=$(find / -maxdepth 3 -type d -name "..." -o -name ". " -o -name ". " 2>/dev/null) + if [ -n "$hidden_dirs" ]; then + findings="${findings}Hidden-Directories " + risk=$((risk + 35)) + fi + + echo "$risk|$findings" +} + +check_suspicious_network_activity() { + echo " Analyzing network connections..." >&2 + + local findings="" + local risk=0 + + # Check for connections to unusual ports (reverse shells often use 4444, 5555, 6666, 1337) + local suspicious_ports=$(netstat -antp 2>/dev/null | grep ESTABLISHED | awk '{print $4}' | cut -d: -f2 | grep -E "^(4444|5555|6666|1337|31337)$") + if [ -n "$suspicious_ports" ]; then + findings="${findings}Suspicious-Port-Connections " + risk=$((risk + 45)) + fi + + # Check for IRC connections (common in botnets) + local irc_conns=$(netstat -antp 2>/dev/null | grep ESTABLISHED | grep ":6667\|:6666\|:7000") + if [ -n "$irc_conns" ]; then + findings="${findings}IRC-Connections-Detected " + risk=$((risk + 30)) + fi + + # Check for excessive outbound connections from web server + if command -v pgrep &>/dev/null; then + local httpd_conns=$(lsof -p $(pgrep -d, httpd 2>/dev/null) 2>/dev/null | grep -c ESTABLISHED) + if [ "$httpd_conns" -gt 100 ]; then + findings="${findings}Excessive-Web-Server-Connections:$httpd_conns " + risk=$((risk + 25)) + fi + fi + + echo "$risk|$findings" +} + +perform_compromise_detection() { + local ip=$1 + + echo " ${YELLOW}Running comprehensive compromise detection...${NC}" >&2 + echo "" >&2 + + local total_risk=0 + local all_findings="" + + # Run all compromise checks + local result=$(check_backdoor_accounts) + local check_risk=$(echo "$result" | cut -d'|' -f1) + local check_findings=$(echo "$result" | cut -d'|' -f2-) + total_risk=$((total_risk + check_risk)) + [ -n "$check_findings" ] && all_findings="${all_findings}${check_findings}" + + result=$(check_unauthorized_ssh_keys) + check_risk=$(echo "$result" | cut -d'|' -f1) + check_findings=$(echo "$result" | cut -d'|' -f2-) + total_risk=$((total_risk + check_risk)) + [ -n "$check_findings" ] && all_findings="${all_findings}${check_findings}" + + result=$(check_system_file_tampering) + check_risk=$(echo "$result" | cut -d'|' -f1) + check_findings=$(echo "$result" | cut -d'|' -f2-) + total_risk=$((total_risk + check_risk)) + [ -n "$check_findings" ] && all_findings="${all_findings}${check_findings}" + + result=$(check_suspicious_processes) + check_risk=$(echo "$result" | cut -d'|' -f1) + check_findings=$(echo "$result" | cut -d'|' -f2-) + total_risk=$((total_risk + check_risk)) + [ -n "$check_findings" ] && all_findings="${all_findings}${check_findings}" + + result=$(check_backdoor_cron_jobs) + check_risk=$(echo "$result" | cut -d'|' -f1) + check_findings=$(echo "$result" | cut -d'|' -f2-) + total_risk=$((total_risk + check_risk)) + [ -n "$check_findings" ] && all_findings="${all_findings}${check_findings}" + + result=$(check_bash_history_malicious_commands) + check_risk=$(echo "$result" | cut -d'|' -f1) + check_findings=$(echo "$result" | cut -d'|' -f2-) + total_risk=$((total_risk + check_risk)) + [ -n "$check_findings" ] && all_findings="${all_findings}${check_findings}" + + result=$(check_web_shells) + check_risk=$(echo "$result" | cut -d'|' -f1) + check_findings=$(echo "$result" | cut -d'|' -f2-) + total_risk=$((total_risk + check_risk)) + [ -n "$check_findings" ] && all_findings="${all_findings}${check_findings}" + + result=$(check_rootkit_indicators) + check_risk=$(echo "$result" | cut -d'|' -f1) + check_findings=$(echo "$result" | cut -d'|' -f2-) + total_risk=$((total_risk + check_risk)) + [ -n "$check_findings" ] && all_findings="${all_findings}${check_findings}" + + result=$(check_suspicious_network_activity) + check_risk=$(echo "$result" | cut -d'|' -f1) + check_findings=$(echo "$result" | cut -d'|' -f2-) + total_risk=$((total_risk + check_risk)) + [ -n "$check_findings" ] && all_findings="${all_findings}${check_findings}" + + # Cap at 100 + [ $total_risk -gt 100 ] && total_risk=100 + + echo "$total_risk|$all_findings" +} + # # AUTOMATED RESPONSE # @@ -1128,8 +1542,45 @@ generate_report() { fi echo " │" + + # COMPROMISE DETECTION - Check if server is actually rooted echo " ├─────────────────────────────────────────────────────────" - echo -e " │ ${RED}FINAL RISK SCORE: $risk/100 - CRITICAL${NC}" + echo -e " │ ${RED}COMPROMISE DETECTION - System Integrity Check${NC}" + echo " ├─────────────────────────────────────────────────────────" + local compromise_result=$(perform_compromise_detection "$ip") + local compromise_risk=$(echo "$compromise_result" | cut -d'|' -f1) + local compromise_findings=$(echo "$compromise_result" | cut -d'|' -f2-) + + if [ "$compromise_risk" -ge 50 ]; then + echo -e " │ ${RED}🚨 COMPROMISE CONFIRMED - $compromise_risk risk points${NC}" + echo " │" + echo " │ Indicators of compromise found:" + # Parse and display findings + for finding in $(echo "$compromise_findings" | tr ' ' '\n'); do + echo " │ • $finding" + done + risk=$((risk + compromise_risk)) + [ $risk -gt 100 ] && risk=100 + elif [ "$compromise_risk" -gt 0 ]; then + echo -e " │ ${YELLOW}⚠️ Suspicious indicators found - $compromise_risk risk points${NC}" + echo " │" + for finding in $(echo "$compromise_findings" | tr ' ' '\n'); do + echo " │ • $finding" + done + risk=$((risk + compromise_risk)) + [ $risk -gt 100 ] && risk=100 + else + echo -e " │ ${GREEN}✓ No compromise indicators detected${NC}" + echo " │ System integrity checks passed" + fi + + echo " │" + echo " ├─────────────────────────────────────────────────────────" + if [ "$compromise_risk" -ge 50 ]; then + echo -e " │ ${RED}FINAL RISK SCORE: $risk/100 - SERVER LIKELY COMPROMISED${NC}" + else + echo -e " │ ${RED}FINAL RISK SCORE: $risk/100 - CRITICAL${NC}" + fi echo " └─────────────────────────────────────────────────────────" # Trigger automated response