#!/usr/bin/env bash
#=============================================================================
# Sentinela ? Intelligent Linux Security Framework
# ids/ssh.sh ? Detección de ataques de fuerza bruta SSH
#=============================================================================
#
# Analiza /var/log/auth.log en busca de:
# - Múltiples intentos fallidos de SSH
# - Conexiones desde IPs sospechosas
# - Usuarios inexistentes
# - Tiempos entre intentos (for Brute Force timing)
#=============================================================================
# shellcheck source=lib/common.sh
source "${SENTINELA_DIR}/lib/common.sh"
#=============================================================================
# Variables de estado
#=============================================================================
SSH_BRUTE_THRESHOLD="${SSH_BRUTE_THRESHOLD:-5}" # Intentos fallidos antes de ban
SSH_BRUTE_WINDOW="${SSH_BRUTE_WINDOW:-300}" # Ventana en segundos (5 min)
#=============================================================================
# Leer nuevas líneas del log SSH
# Soporta rsyslog (tail -c) y journald (journalctl --after-cursor)
#=============================================================================
ssh_read_new_lines() {
log_read_new_lines "ssh_auth"
}
#=============================================================================
# Detectar fuerza bruta SSH
#=============================================================================
ssh_brute_force_detect() {
if ! log_check_exists "ssh_auth"; then
return 0
fi
local new_lines
new_lines=$(ssh_read_new_lines) || return 0
if [[ -z "${new_lines}" ]]; then
return 0
fi
# Cache de intentos por IP (formato: IP:timestamp)
local fail_cache="${SENTINELA_DIR}/logs/.ssh_fail_cache"
[[ -f "${fail_cache}" ]] || touch "${fail_cache}"
local now
now=$(date +%s)
while IFS= read -r line; do
[[ -z "${line}" ]] && continue
# Detectar intentos fallidos
if echo "${line}" | grep -qiP '(Failed password|authentication failure|Connection closed by.*\[preauth\]|Invalid user|Connection reset by peer)'; then
local ip=""
local user=""
# Extraer IP
ip=$(echo "${line}" | grep -oP 'from \K[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' || echo "")
if [[ -z "${ip}" ]]; then
ip=$(echo "${line}" | grep -oP '\b[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\b' | head -1 || echo "")
fi
# Extraer usuario
user=$(echo "${line}" | grep -oP 'for \K[a-zA-Z0-9_.-]+' | head -1 || echo "unknown")
if [[ -z "${ip}" ]] || ! is_valid_ip "${ip}"; then
continue
fi
# Actualizar cache de intentos
local entry
entry=$(grep "^${ip} " "${fail_cache}" 2>/dev/null || true)
if [[ -n "${entry}" ]]; then
local count last_time
last_time=$(echo "${entry}" | awk '{print $2}')
count=$(echo "${entry}" | awk '{print $3}')
if (( now - last_time < SSH_BRUTE_WINDOW )); then
count=$((count + 1))
sed -i "s/^${ip} .*/${ip} ${now} ${count}/" "${fail_cache}" 2>/dev/null || true
# Si supera el threshold, banear
if (( count >= SSH_BRUTE_THRESHOLD )); then
log_attack "SSH_BRUTE|${ip}|${count} intentos fallidos (último user: ${user})"
source "${SENTINELA_DIR}/firewall/blacklist.sh"
blacklist_add "${ip}" "ssh_bruteforce: ${count} intentos"
# Resetear contador
sed -i "s/^${ip} .*/${ip} ${now} 0/" "${fail_cache}" 2>/dev/null || true
# Telegram alert
if [[ "${TELEGRAM_ENABLE}" == "yes" ]]; then
source "${SENTINELA_DIR}/telegram/telegram.sh"
telegram_send_alert "${ip}" "ssh_bruteforce" \
"Fuerza bruta SSH: ${count} intentos (usuario: ${user})" \
"" "" "" "SSH"
fi
fi
else
# Resetear (ventana expiró)
sed -i "s/^${ip} .*/${ip} ${now} 1/" "${fail_cache}" 2>/dev/null || true
fi
else
echo "${ip} ${now} 1" >> "${fail_cache}" 2>/dev/null || true
fi
fi
done <<< "${new_lines}"
# Limpiar cache antiguo (> 1h)
local cleanup_cutoff=$((now - 3600))
awk -v cutoff="${cleanup_cutoff}" '$2 >= cutoff' "${fail_cache}" > "${fail_cache}.tmp" 2>/dev/null || true
mv "${fail_cache}.tmp" "${fail_cache}" 2>/dev/null || true
return 0
}
|