diff --git a/modules/security/suspicious-login-monitor.sh b/modules/security/suspicious-login-monitor.sh index c23e917..bfef8c4 100755 --- a/modules/security/suspicious-login-monitor.sh +++ b/modules/security/suspicious-login-monitor.sh @@ -49,6 +49,11 @@ PANEL_EVENTS="$TMP_DIR/panel_events_$$.txt" SUDO_EVENTS="$TMP_DIR/sudo_events_$$.txt" SUSPICIOUS_IPS="$TMP_DIR/suspicious_ips_$$.txt" +# Baseline storage (persistent across runs) +BASELINE_DIR="/var/lib/suspicious-login-monitor" +BASELINE_FILE="$BASELINE_DIR/baseline.dat" +mkdir -p "$BASELINE_DIR" 2>/dev/null + # Analysis period (default: last 24 hours) HOURS="${1:-24}" @@ -960,6 +965,286 @@ correlate_with_threat_intel() { echo "$additional_risk|$notes" } +# +# CONFIDENCE IMPROVEMENT - Baseline and pattern matching +# + +load_baseline() { + # Load historical baseline data + if [ -f "$BASELINE_FILE" ]; then + source "$BASELINE_FILE" + else + # Initialize empty baseline + BASELINE_SSH_KEY_COUNT=0 + BASELINE_USER_COUNT=0 + BASELINE_TYPICAL_LOGIN_HOURS="" + BASELINE_PASSWORD_CHANGES_PER_WEEK=0 + BASELINE_NEW_USERS_PER_WEEK=0 + BASELINE_LAST_UPDATE=0 + fi +} + +save_baseline() { + local ssh_key_count=$1 + local user_count=$2 + local login_hours=$3 + local pw_changes=$4 + local new_users=$5 + + cat > "$BASELINE_FILE" << EOF +# Baseline data for suspicious login monitor +# Last updated: $(date) +BASELINE_SSH_KEY_COUNT=$ssh_key_count +BASELINE_USER_COUNT=$user_count +BASELINE_TYPICAL_LOGIN_HOURS="$login_hours" +BASELINE_PASSWORD_CHANGES_PER_WEEK=$pw_changes +BASELINE_NEW_USERS_PER_WEEK=$new_users +BASELINE_LAST_UPDATE=$(date +%s) +EOF +} + +update_baseline() { + # Update baseline with current system state + local current_keys=$(grep -v "^#" /root/.ssh/authorized_keys 2>/dev/null | grep -c "ssh-" || echo 0) + local current_users=$(awk -F: '$3 >= 1000 && $3 < 60000 {print $1}' /etc/passwd | wc -l) + local typical_hours=$(who | awk '{print $4}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -3 | awk '{print $2}' | tr '\n' ',' | sed 's/,$//') + + # Track password changes and user creation over time + local pw_changes=0 + local new_users=0 + + # Simple tracking - could be enhanced with historical analysis + if [ -f "$BASELINE_FILE" ]; then + source "$BASELINE_FILE" + # Use existing values as basis + fi + + save_baseline "$current_keys" "$current_users" "$typical_hours" "$pw_changes" "$new_users" +} + +check_deviation_from_baseline() { + local metric=$1 + local current_value=$2 + local baseline_value=$3 + + # Calculate percentage deviation + if [ "$baseline_value" -eq 0 ]; then + echo "100" # New metric, 100% deviation + return + fi + + local diff=$((current_value - baseline_value)) + local abs_diff=${diff#-} # Absolute value + local percent=$((abs_diff * 100 / baseline_value)) + + echo "$percent" +} + +match_attack_patterns() { + local findings=$1 + local confidence_boost=0 + local matched_patterns="" + + # Known attack pattern signatures + # Pattern 1: Backdoor account + SSH key + recent creation + if echo "$findings" | grep -q "UID-0" && \ + echo "$findings" | grep -q "SSH-Key" && \ + echo "$findings" | grep -q "Created-Users"; then + confidence_boost=$((confidence_boost + 30)) + matched_patterns="${matched_patterns}Backdoor-Installation-Pattern " + fi + + # Pattern 2: Mass password change + file tampering + if echo "$findings" | grep -q "Mass-Password" && \ + echo "$findings" | grep -q "Modified"; then + confidence_boost=$((confidence_boost + 25)) + matched_patterns="${matched_patterns}Ransomware-Pattern " + fi + + # Pattern 3: Sudo escalation + suspicious process + cron + if echo "$findings" | grep -q "Sudo" && \ + echo "$findings" | grep -q "Process" && \ + echo "$findings" | grep -q "Cron"; then + confidence_boost=$((confidence_boost + 30)) + matched_patterns="${matched_patterns}Privilege-Escalation-Pattern " + fi + + # Pattern 4: Web shell + backdoor cron + network activity + if echo "$findings" | grep -q "Shell" && \ + echo "$findings" | grep -q "Cron" && \ + echo "$findings" | grep -q "Network"; then + confidence_boost=$((confidence_boost + 35)) + matched_patterns="${matched_patterns}Persistent-Backdoor-Pattern " + fi + + # Pattern 5: Rootkit indicators + modified binaries + hidden files + if echo "$findings" | grep -q "Rootkit" && \ + echo "$findings" | grep -q "Binary" && \ + echo "$findings" | grep -q "Hidden"; then + confidence_boost=$((confidence_boost + 40)) + matched_patterns="${matched_patterns}Rootkit-Compromise-Pattern " + fi + + # Pattern 6: Recently created user + suspicious name + no password age + if echo "$findings" | grep -q "Suspicious-Username" && \ + echo "$findings" | grep -q "Created-Users" && \ + echo "$findings" | grep -q "Password"; then + confidence_boost=$((confidence_boost + 25)) + matched_patterns="${matched_patterns}Account-Takeover-Pattern " + fi + + echo "$confidence_boost|$matched_patterns" +} + +calculate_confidence_score() { + local risk=$1 + local findings=$2 + local mitigating_factors=$3 + + # Start with base confidence from risk level + local confidence=50 # Medium baseline + + # Risk-based confidence adjustment + if [ "$risk" -ge 85 ]; then + confidence=$((confidence + 30)) # High risk = higher confidence it's real + elif [ "$risk" -ge 70 ]; then + confidence=$((confidence + 20)) + elif [ "$risk" -ge 50 ]; then + confidence=$((confidence + 10)) + fi + + # Multiple independent indicators increase confidence + local indicator_count=$(echo "$findings" | grep -o "[A-Z][a-z]*-[A-Z]" | wc -l) + if [ "$indicator_count" -ge 5 ]; then + confidence=$((confidence + 25)) + elif [ "$indicator_count" -ge 3 ]; then + confidence=$((confidence + 15)) + elif [ "$indicator_count" -ge 2 ]; then + confidence=$((confidence + 5)) + else + confidence=$((confidence - 20)) # Single indicator = lower confidence + fi + + # Mitigating factors reduce confidence + local mitigation_count=$(echo "$mitigating_factors" | grep -o "\[" | wc -l) + if [ "$mitigation_count" -ge 3 ]; then + confidence=$((confidence - 30)) # Lots of context = probably legitimate + elif [ "$mitigation_count" -ge 2 ]; then + confidence=$((confidence - 20)) + elif [ "$mitigation_count" -ge 1 ]; then + confidence=$((confidence - 10)) + fi + + # Check for attack pattern matches (increases confidence) + local pattern_match=$(match_attack_patterns "$findings") + local pattern_boost=$(echo "$pattern_match" | cut -d'|' -f1) + local patterns=$(echo "$pattern_match" | cut -d'|' -f2-) + + confidence=$((confidence + pattern_boost)) + + # Check baseline deviations (if available) + if [ -f "$BASELINE_FILE" ]; then + load_baseline + + # Check SSH key deviation + local current_keys=$(grep -v "^#" /root/.ssh/authorized_keys 2>/dev/null | grep -c "ssh-" || echo 0) + if [ "$BASELINE_SSH_KEY_COUNT" -gt 0 ]; then + local key_deviation=$(check_deviation_from_baseline "keys" "$current_keys" "$BASELINE_SSH_KEY_COUNT") + if [ "$key_deviation" -gt 50 ]; then + confidence=$((confidence + 15)) # Significant deviation from normal + fi + fi + fi + + # Cap confidence at 0-100 + [ "$confidence" -lt 0 ] && confidence=0 + [ "$confidence" -gt 100 ] && confidence=100 + + # Determine confidence level + local confidence_level="MEDIUM" + if [ "$confidence" -ge 75 ]; then + confidence_level="HIGH" + elif [ "$confidence" -lt 40 ]; then + confidence_level="LOW" + fi + + echo "$confidence|$confidence_level|$patterns" +} + +cross_validate_finding() { + local finding_type=$1 + local finding_data=$2 + local validation_score=0 + local validation_sources="" + + case "$finding_type" in + "password-change") + # Cross-check with multiple sources + # 1. /etc/shadow timestamp + if [ -f /etc/shadow ]; then + local shadow_age=$(($(date +%s) - $(stat -c %Y /etc/shadow))) + if [ "$shadow_age" -lt 86400 ]; then + validation_score=$((validation_score + 1)) + validation_sources="${validation_sources}shadow-timestamp " + fi + fi + + # 2. /var/log/secure entries + if grep -q "password changed" /var/log/secure 2>/dev/null; then + validation_score=$((validation_score + 1)) + validation_sources="${validation_sources}secure-log " + fi + + # 3. Audit log entries (if available) + if [ -f /var/log/audit/audit.log ] && ausearch -m USER_CHAUTHTOK -ts recent >/dev/null 2>&1; then + validation_score=$((validation_score + 1)) + validation_sources="${validation_sources}audit-log " + fi + ;; + + "user-creation") + # 1. /etc/passwd timestamp + local passwd_age=$(($(date +%s) - $(stat -c %Y /etc/passwd))) + if [ "$passwd_age" -lt 604800 ]; then # 7 days + validation_score=$((validation_score + 1)) + validation_sources="${validation_sources}passwd-timestamp " + fi + + # 2. Home directory existence and age + local username=$(echo "$finding_data" | cut -d'(' -f1) + if [ -d "/home/$username" ]; then + validation_score=$((validation_score + 1)) + validation_sources="${validation_sources}home-dir-exists " + fi + + # 3. System logs + if grep -q "new user" /var/log/secure 2>/dev/null | grep -q "$username"; then + validation_score=$((validation_score + 1)) + validation_sources="${validation_sources}secure-log " + fi + ;; + + "ssh-key") + # 1. File modification time + if [ -f /root/.ssh/authorized_keys ]; then + local key_age=$(($(date +%s) - $(stat -c %Y /root/.ssh/authorized_keys))) + if [ "$key_age" -lt 604800 ]; then + validation_score=$((validation_score + 1)) + validation_sources="${validation_sources}key-file-timestamp " + fi + fi + + # 2. SSH log entries + if grep -q "Accepted publickey" /var/log/secure 2>/dev/null | tail -100 | grep -q "root"; then + validation_score=$((validation_score + 1)) + validation_sources="${validation_sources}ssh-log " + fi + ;; + esac + + echo "$validation_score|$validation_sources" +} + # # FALSE POSITIVE REDUCTION - Context checking functions # @@ -1935,8 +2220,13 @@ perform_compromise_detection() { echo " ${YELLOW}Running comprehensive compromise detection...${NC}" >&2 echo "" >&2 + # Load baseline for comparison + load_baseline + local total_risk=0 local all_findings="" + local all_mitigations="" + local evidence_items=0 # Run all compromise checks (11 total checks now) local result=$(check_recent_password_changes) @@ -2035,7 +2325,17 @@ perform_compromise_detection() { # Cap at 100 [ $total_risk -gt 100 ] && total_risk=100 - echo "$total_risk|$all_findings" + # CONFIDENCE CALCULATION: Calculate how confident we are this is a real threat + local confidence_result=$(calculate_confidence_score "$total_risk" "$all_findings" "$all_mitigations") + local confidence_score=$(echo "$confidence_result" | cut -d'|' -f1) + local confidence_level=$(echo "$confidence_result" | cut -d'|' -f2) + local matched_patterns=$(echo "$confidence_result" | cut -d'|' -f3) + + # Update baseline with current state (for future comparisons) + update_baseline + + # Return: risk|findings|confidence_score|confidence_level|patterns + echo "$total_risk|$all_findings|$confidence_score|$confidence_level|$matched_patterns" } # @@ -2389,11 +2689,16 @@ main() { local compromise_result=$(perform_compromise_detection "system-wide") local compromise_risk=$(echo "$compromise_result" | cut -d'|' -f1) - local compromise_findings=$(echo "$compromise_result" | cut -d'|' -f2-) + local compromise_findings=$(echo "$compromise_result" | cut -d'|' -f2) + local confidence_score=$(echo "$compromise_result" | cut -d'|' -f3) + local confidence_level=$(echo "$compromise_result" | cut -d'|' -f4) + local matched_patterns=$(echo "$compromise_result" | cut -d'|' -f5-) if [ "$compromise_risk" -ge 100 ]; then echo -e "${RED}🚨 CRITICAL: Server shows strong indicators of compromise${NC}" echo -e "${RED} Risk Score: $compromise_risk/100${NC}" + echo -e "${RED} Confidence: $confidence_level ($confidence_score/100)${NC}" + [ -n "$matched_patterns" ] && echo -e "${RED} Attack Patterns: $matched_patterns${NC}" echo "" echo "Indicators found:" for finding in $(echo "$compromise_findings" | tr ' ' '\n'); do @@ -2410,6 +2715,8 @@ main() { elif [ "$compromise_risk" -ge 50 ]; then echo -e "${RED}⚠️ WARNING: Suspicious indicators detected${NC}" echo -e "${YELLOW} Risk Score: $compromise_risk/100${NC}" + echo -e "${YELLOW} Confidence: $confidence_level ($confidence_score/100)${NC}" + [ -n "$matched_patterns" ] && echo -e "${YELLOW} Attack Patterns: $matched_patterns${NC}" echo "" echo "Indicators found:" for finding in $(echo "$compromise_findings" | tr ' ' '\n'); do @@ -2417,21 +2724,41 @@ main() { done echo "" echo -e "${YELLOW}RECOMMENDED ACTIONS:${NC}" - echo " 1. Review all findings carefully" - echo " 2. Run rootkit scan: rkhunter --check" - echo " 3. Investigate recent account/file changes" + if [ "$confidence_level" = "HIGH" ]; then + echo " 1. HIGH confidence - Likely a real threat, investigate immediately" + echo " 2. Run rootkit scan: rkhunter --check" + echo " 3. Check for unauthorized access" + elif [ "$confidence_level" = "LOW" ]; then + echo " 1. LOW confidence - May be legitimate activity" + echo " 2. Review context in findings (look for [brackets])" + echo " 3. Consider whitelisting if this is normal for your environment" + else + echo " 1. Review all findings carefully" + echo " 2. Run rootkit scan: rkhunter --check" + echo " 3. Investigate recent account/file changes" + fi elif [ "$compromise_risk" -gt 0 ]; then echo -e "${BLUE}ℹ️ NOTICE: Minor security concerns detected${NC}" echo -e "${BLUE} Risk Score: $compromise_risk/100${NC}" + echo -e "${BLUE} Confidence: $confidence_level ($confidence_score/100)${NC}" + [ -n "$matched_patterns" ] && echo -e "${BLUE} Note: $matched_patterns${NC}" echo "" echo "Issues found:" for finding in $(echo "$compromise_findings" | tr ' ' '\n'); do echo -e " ${BLUE}•${NC} $(echo $finding | tr '-' ' ')" done echo "" - echo "Review these items when convenient." + if [ "$confidence_level" = "LOW" ]; then + echo "LOW confidence - Likely legitimate activity with context:" + echo " • Look for [admin-active], [yum_activity], [cpanel], etc. in findings" + echo " • These indicate known legitimate sources" + echo " • Review when convenient, no urgent action needed" + else + echo "Review these items when convenient." + fi else echo -e "${GREEN}✓ No compromise indicators detected${NC}" + echo -e "${GREEN} Confidence: HIGH (100/100)${NC}" echo "" echo "System integrity checks:" echo " ✓ No suspicious password changes detected"