Intentos de acceso SSH en mis logs: ¿me están hackeando?
Entras en tu servidor, echas un vistazo a /var/log/auth.log y te encuentras miles de líneas de Failed password for root desde direcciones de medio mundo. Parece un asedio. Es sobre todo meteorología.
El puerto 22 es la puerta más previsible de internet, así que los scripts automáticos llaman sin parar, en todas partes, a todas horas. No van a por ti. Prueban una lista corta de usuarios (root, admin, oracle, ubuntu, test) con las contraseñas más elegidas y siguen su camino. Un intento fallido no les cuesta nada y a ti solo te dice que SSH es accesible. Tu tarea es responder a dos preguntas: ¿algún intento salió bien?, ¿podría salir bien alguno en el futuro?
Dónde mirar
En Debian y Ubuntu con rsyslog, SSH escribe en /var/log/auth.log. En Red Hat, Fedora, Rocky y Alma es /var/log/secure. Las instalaciones recientes de Debian pueden no escribir ese archivo y guardarlo todo en el journal de systemd. El journal funciona allí donde haya systemd; el servicio se llama ssh en Debian y Ubuntu y sshd en la familia Red Hat:
sudo journalctl -u ssh --since today # Debian, Ubuntu
sudo journalctl -u sshd --since "1 hour ago" # Red Hat, Fedora, Rocky, Alma
Tres recuentos cuentan casi toda la historia:
sudo grep -c "Failed password" /var/log/auth.log
sudo grep "Accepted" /var/log/auth.log
sudo grep "Failed password" /var/log/auth.log | grep -oE "from [0-9.]+" | sort | uniq -c | sort -rn | head
El primero mide el ruido. El segundo es el que importa: cada acceso correcto. El tercero muestra qué direcciones insisten más.
Un ejemplo real: una noche en un VPS pequeño
Doce líneas de un servidor llamado web1. Las dos direcciones de personas (el atacante que prueba con admin y el dueño que entra a la mañana siguiente) son direcciones de documentación, así que aquí no aparece ninguna persona real. Las demás son infraestructura pública.
Nov 6 03:12:41 web1 sshd[24811]: Invalid user admin from 203.0.113.77 port 51022
Nov 6 03:12:43 web1 sshd[24811]: Failed password for invalid user admin from 203.0.113.77 port 51022 ssh2
Nov 6 03:12:44 web1 sshd[24811]: Connection closed by invalid user admin 203.0.113.77 port 51022 [preauth]
Nov 6 03:12:50 web1 sshd[24815]: Failed password for root from 185.220.101.1 port 40122 ssh2
Nov 6 03:12:53 web1 sshd[24815]: Failed password for root from 185.220.101.1 port 40122 ssh2
Nov 6 03:12:55 web1 sshd[24815]: Disconnected from authenticating user root 185.220.101.1 port 40122 [preauth]
Nov 6 03:14:02 web1 sshd[24830]: Invalid user oracle from 3.5.140.1 port 33412
Nov 6 03:14:04 web1 sshd[24830]: Failed password for invalid user oracle from 3.5.140.1 port 33412 ssh2
Nov 6 03:14:04 web1 sshd[24830]: Received disconnect from 3.5.140.1 port 33412:11: Bye Bye [preauth]
Nov 6 03:20:17 web1 sshd[24902]: Connection closed by 167.94.138.35 port 49212 [preauth]
Nov 6 08:41:09 web1 sshd[25510]: Accepted publickey for deploy from 198.51.100.20 port 52814 ssh2: ED25519 SHA256:k3X0exampleexampleexampleexampleexampleexa
Nov 6 08:41:09 web1 sshd[25510]: pam_unix(sshd:session): session opened for user deploy(uid=1001) by (uid=0)Pasadas por el comprobador de logs de iplover, las cinco direcciones quedan así:
| Dirección IP | Líneas | Categoría | Tipo de red |
|---|---|---|---|
| 203.0.113.77 | 3 | Privadas o reservadas | Dirección privada o reservada (no comprobada) |
| 185.220.101.1 | 3 | Nodos de salida de Tor | Nodo de salida de Tor |
| 3.5.140.1 | 3 | Nube y hosting | Hosting, nube o centro de datos |
| 167.94.138.35 | 1 | Escáneres de internet | No identificado |
| 198.51.100.20 | 1 | Privadas o reservadas | Dirección privada o reservada (no comprobada) |
(Las dos direcciones de documentación salen como reservadas; en tu log se comprobarían las direcciones reales.) Ahora lee las líneas, porque las categorías solo dicen quién llamó, no si la puerta se abrió.
Invalid user admin from 203.0.113.77: en este servidor no existe la cuentaadmin, así que este intento nunca podía funcionar. La mayor parte de un auth.log típico son líneas así.Failed password for root from 185.220.101.1, dos veces: alguien probando la contraseña de root a través de Tor. La cuenta existe, así que esto pesa más.Disconnected from authenticating user root … [preauth]significa que la sesión terminó antes del acceso: preauth quiere decir literalmente «antes de la autenticación».Invalid user oracle from 3.5.140.1: un script en un servidor de la nube de Amazon que prueba el nombre de una cuenta de base de datos.Bye Bye [preauth]es el cliente rindiéndose.Connection closed by 167.94.138.35 … [preauth]: Censys se conectó, leyó el banner con la versión de SSH y se fue sin probar contraseñas. Es un escáner haciendo inventario, no un ataque.Accepted publickey for deploy from 198.51.100.20: el único acceso correcto de la noche, con una clave, del dueño. La líneasession openedjusto después confirma que la sesión empezó.
Veredicto: un solo acceso aceptado, y es del dueño. No ha entrado nadie. Pero root todavía acepta contraseñas, así que los intentos de la cuarta línea podrían salir bien algún día si la contraseña de root es débil. Eso es lo que hay que arreglar.
¿Ha entrado alguien de verdad?
Revisa cada línea Accepted. En cada una pregúntate: ¿es mi usuario, mi clave o contraseña, y una dirección que reconozco (casa, oficina, VPN)? Luego compáralo con los registros de acceso:
last -a | head -20 # últimos accesos correctos, con la dirección remota
sudo lastb | head -20 # últimos accesos fallidos (en sistemas que los guardan)
who # quién está conectado ahora mismo
Algunas distribuciones recientes han sustituido last y lastb por otras herramientas; si no están, el journal y auth.log tienen la misma información.
Una línea Accepted password o Accepted publickey que no sabes explicar es el único resultado que lo cambia todo. En ese caso, da la cuenta por comprometida:
- Revisa
~/.ssh/authorized_keysde cada usuario (root incluido) y quita las claves que no reconozcas. - Busca cuentas nuevas (
/etc/passwd), permisos desudonuevos y tareas cron nuevas (crontab -lpara cada usuario, además de/etc/cron.*). - Cambia las contraseñas de todas las cuentas y renueva los secretos guardados en el servidor.
- Si root estuvo implicado, la respuesta honesta es reinstalar el servidor desde una imagen limpia y restaurar tus datos. Un atacante con root puede esconder casi cualquier cosa.
Si ves esto, haz esto
| Lo que ves | Qué significa | Qué hacer |
|---|---|---|
Invalid user … con nombres que no tienes | Intentos que no pueden funcionar | Nada urgente. Desaparecen cuando quitas las contraseñas. |
Failed password for root o para otra cuenta real | Intentos contra una cuenta que existe | Desactiva el acceso con contraseña (más abajo), o al menos el de root. |
Connection closed by … [preauth] sin contraseñas probadas | Un escáner leyendo tu banner de SSH | Nada. |
| Miles de fallos al día desde pocas direcciones | Bots insistentes | Instala fail2ban, o permite SSH solo desde tus direcciones. |
Líneas Accepted … que son todas tuyas | No ha entrado nadie más | Nada, aparte de reforzar la configuración. |
Accepted … desde una dirección o un usuario que no sabes explicar | Probable intrusión | Sigue los pasos de arriba, empezando por authorized_keys. |
Haz que los intentos no sirvan: solo claves
Una contraseña se puede adivinar; una clave SSH, en la práctica, no. Cuando solo se aceptan claves, los bots pueden seguir probando para siempre.
Primero crea una clave en tu ordenador y cópiala al servidor. macOS y Linux traen el cliente OpenSSH. En Windows es una característica opcional, a menudo ya instalada; si ssh no se reconoce, la guía de Microsoft Introducción a OpenSSH para Windows explica cómo añadirlo:
ssh-keygen -t ed25519
ssh-copy-id tu@tu-servidor # en Windows, pega ~/.ssh/id_ed25519.pub en ~/.ssh/authorized_keys del servidor
Después, en el servidor, pon esto en /etc/ssh/sshd_config:
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin prohibit-password
prohibit-password deja entrar a root solo con clave; usa no para dejar a root fuera de SSH del todo. Antes de reiniciar, comprueba qué va a usar SSH de verdad. También se leen los archivos de /etc/ssh/sshd_config.d/, y algunas imágenes en la nube traen uno que vuelve a activar las contraseñas. En la mayoría de opciones gana el primer valor que lee sshd:
sudo sshd -T | grep -Ei "passwordauthentication|kbdinteractive|permitrootlogin"
Reinicia SSH (sudo systemctl restart ssh, o sshd en la familia Red Hat) y deja abierta tu sesión actual mientras pruebas un acceso nuevo en otra ventana. Si el nuevo acceso falla, todavía puedes arreglarlo desde la sesión antigua.
Reduce el ruido: fail2ban y cortafuegos
Las claves hacen inofensivos los intentos. fail2ban hace legible el log: vigila los fallos repetidos y bloquea esas direcciones un tiempo en el cortafuegos. En Debian y Ubuntu, sudo apt install fail2ban, y luego crea /etc/fail2ban/jail.local:
[sshd]
enabled = true
maxretry = 5
findtime = 10m
bantime = 1h
# Sin /var/log/auth.log (Debian recientes), lee el journal:
# backend = systemd
Reinícialo con sudo systemctl restart fail2ban y compruébalo con sudo fail2ban-client status sshd.
Si solo tú necesitas SSH, el mejor filtro de ruido es una regla de cortafuegos que permita el puerto 22 solo desde tus direcciones, o una VPN delante del servidor. Mover SSH a otro puerto reduce mucho el ruido, pero es camuflaje, no protección: un escáner lo encontrará igualmente.
Lo que las direcciones no pueden decirte
Las direcciones de auth.log son el origen de las conexiones: a menudo un nodo de Tor, un servidor en la nube alquilado o el ordenador secuestrado de otra persona. No identifican a nadie, y bloquearlas una a una no da abasto. Denunciar ataques insistentes al contacto de abusos de la red (lo muestra la búsqueda de IP) tiene sentido para un servidor en la nube que te machaca durante días; por unos pocos accesos fallidos no merece la pena.
Fuentes
Documentos oficiales que usamos para comprobar los datos de esta guía.