Un servidor lento, con la CPU al 100 % o con miles de conexiones abiertas puede estar sufriendo un ataque, o puede que ya esté comprometido. En esta guía seguirás un procedimiento ordenado para averiguarlo en Ubuntu 24.04: primero la red y los accesos, después los procesos, la persistencia y los ficheros, y por último la contención y la prevención. Todos los comandos son de solo lectura hasta llegar al paso de contención.

Requisitos previos

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath. En Debian 12 los comandos son los mismos.
  • Un usuario no root con privilegios sudo.
  • Saber qué es "normal" en tu servidor (servicios, puertos y usuarios esperados). Sin esa referencia es difícil distinguir un ataque de un pico de tráfico legítimo.

Paso 1: Instalar las herramientas de diagnóstico

La mayoría de comandos de esta guía vienen con Ubuntu, pero algunos (tráfico por conexión, estadísticas históricas, captura de paquetes) necesitan paquetes adicionales:

sudo apt update
sudo apt install sysstat iftop tcpdump lsof debsums

Averigua también el nombre de tu interfaz de red pública, porque lo usarás en varios comandos:

ip -br addr
lo               UNKNOWN        127.0.0.1/8 ::1/128
eth0             UP             203.0.113.25/24 2001:db8::25/64

En los ejemplos se usa eth0. Sustitúyelo por el nombre que veas (por ejemplo ens18).

Paso 2: Hacer una evaluación rápida

Antes de profundizar, estos comandos te dan una foto del estado del servidor en menos de un minuto. Carga del sistema y usuarios conectados:

uptime
w
 10:42:01 up 12 days,  3:10,  1 user,  load average: 7.85, 6.20, 3.11
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
admin    pts/0    198.51.100.7     10:40    0.00s  0.03s  0.00s w

Resumen de sockets y procesos que más CPU y memoria consumen:

ss -s
ps -eo pid,user,%cpu,%mem,etime,cmd --sort=-%cpu | head -10

Fíjate en tres cosas: una carga muy superior al número de núcleos (nproc), un número de conexiones TCP muy por encima de lo habitual y procesos que no reconoces o que se ejecutan con un usuario inesperado. Cada una te lleva a uno de los pasos siguientes.

Paso 3: Analizar las conexiones de red

Conexiones por IP de origen

Un ataque de denegación de servicio a nivel de aplicación suele verse como muchas conexiones desde pocas IP. Este comando cuenta las conexiones establecidas por IP remota:

ss -Htn state established | awk '{print $4}' | sed 's/:[0-9]*$//' | sort | uniq -c | sort -rn | head -15
    842 198.51.100.44
    611 198.51.100.45
     12 203.0.113.80
      3 192.0.2.10

Cientos de conexiones desde una sola IP hacia un servidor web pequeño no son normales. Si las conexiones vienen de miles de IP distintas con pocas conexiones cada una, se trata de un ataque distribuido y bloquear IP una a una no servirá.

Inundación SYN

En un ataque SYN flood el servidor acumula conexiones a medio abrir en estado SYN-RECV:

ss -Htn state syn-recv | wc -l

Unas pocas son normales; cientos o miles de forma sostenida indican un ataque. Comprueba que las SYN cookies están activas (en Ubuntu lo están por defecto), porque permiten seguir aceptando conexiones legítimas durante la inundación:

sysctl net.ipv4.tcp_syncookies
net.ipv4.tcp_syncookies = 1

Ancho de banda y paquetes

Para ver en tiempo real qué conexiones consumen más ancho de banda, usa iftop sin resolución de nombres (-n) y mostrando puertos (-P). Pulsa q para salir:

sudo iftop -i eth0 -nP

Para ver el tráfico por segundo de la interfaz, usa sar (del paquete sysstat), que muestra 5 muestras de 1 segundo:

sar -n DEV 1 5

Si sospechas de un SYN flood, captura 2000 paquetes SYN y cuenta sus orígenes con tcpdump:

sudo tcpdump -ni eth0 -c 2000 'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0' 2>/dev/null \
  | awk '{print $3}' | cut -d. -f1-4 | sort | uniq -c | sort -rn | head -10

Si el volumen de tráfico satura el enlace del servidor, ninguna regla local lo va a solucionar, porque los paquetes ya han consumido el ancho de banda al llegar. En ese caso la mitigación tiene que hacerse antes de que el tráfico llegue a tu servidor: contacta con el soporte de CubePath con las IP de destino, los puertos y la hora de inicio del ataque.

Tráfico saliente

Tu servidor también puede ser el origen del ataque si está comprometido y forma parte de una botnet. Revisa las conexiones salientes y qué proceso las abre:

sudo ss -tunp state established | grep -v -E ':22 |:80 |:443 ' | head -30

Conexiones a puertos como 3333, 4444 o 14444 suelen corresponder a pools de minería de criptomonedas.

Paso 4: Detectar ataques de fuerza bruta contra SSH

Los intentos de login fallidos se registran en /var/log/auth.log. Este comando cuenta los intentos fallidos por IP de origen, incluidos los de usuarios que no existen:

sudo grep -hE 'Failed password|Invalid user' /var/log/auth.log \
  | grep -oE 'from [0-9a-fA-F.:]+' | awk '{print $2}' | sort | uniq -c | sort -rn | head -10
   1893 198.51.100.23
    744 203.0.113.199
     35 192.0.2.61

Para ver cuántos intentos ha habido en la última hora, usa el journal del servicio SSH (en Ubuntu 24.04 la unidad se llama ssh):

sudo journalctl -u ssh --since "1 hour ago" | grep -c 'Failed password'

Qué usuarios intentan adivinar:

sudo grep -h 'Invalid user' /var/log/auth.log | awk '{for (i=1;i<=NF;i++) if ($i=="user") print $(i+1)}' | sort | uniq -c | sort -rn | head

Los intentos fallidos en sí no son un problema si solo permites autenticación por clave. Lo que de verdad importa es si alguno ha tenido éxito. Revisa los inicios de sesión aceptados y el historial de accesos:

sudo grep 'Accepted' /var/log/auth.log | tail -20
last -n 20
sudo lastb -n 20

Cualquier Accepted password o Accepted publickey desde una IP que no reconoces es una señal de compromiso. Comprueba también que no se han añadido claves SSH desconocidas:

sudo find /root /home -name authorized_keys -exec ls -l {} \; -exec cat {} \;

Paso 5: Buscar procesos sospechosos

Un servidor comprometido casi siempre tiene algún proceso ajeno: un minero, un bot de DDoS o una shell inversa. Empieza por los procesos que escuchan en puertos:

sudo ss -tulpn

Cada puerto debe corresponder a un servicio que conoces (sshd, nginx, mysqld...). Después busca procesos cuyo binario se ejecuta desde directorios temporales o ha sido borrado del disco, dos técnicas habituales del malware:

sudo ls -l /proc/[0-9]*/exe 2>/dev/null | grep -E '\(deleted\)|/tmp/|/var/tmp/|/dev/shm/'
lrwxrwxrwx 1 www-data www-data 0 Sep 25 10:12 /proc/48213/exe -> /tmp/.x/kworkerd (deleted)

Si encuentras un PID sospechoso, examínalo antes de matarlo. Sustituye PID por el número del proceso:

ps -fp PID
sudo ls -l /proc/PID/exe /proc/PID/cwd
sudo tr '\0' ' ' < /proc/PID/cmdline; echo
sudo lsof -nP -p PID -i

Estas son señales típicas de un proceso malicioso:

  • Nombre que imita un hilo del kernel (kworkerd, [kthreadd]) pero con un binario en disco.
  • Se ejecuta como www-data u otro usuario de servicio y no pertenece a ese servicio.
  • Consume casi el 100 % de la CPU de forma constante (típico de los mineros como xmrig).
  • Tiene conexiones abiertas hacia IP externas desconocidas.

Paso 6: Revisar los mecanismos de persistencia

Tras entrar, los atacantes suelen asegurarse de volver aunque reinicies el servidor o mates sus procesos. Revisa las tareas cron de todos los usuarios y del sistema:

sudo ls -la /var/spool/cron/crontabs/
sudo cat /var/spool/cron/crontabs/* 2>/dev/null
sudo cat /etc/crontab /etc/cron.d/*

Busca líneas que descarguen algo con curl o wget, que ejecuten ficheros de /tmp o que contengan cadenas en base64.

Revisa los temporizadores y servicios de systemd, y en especial las unidades modificadas en los últimos días:

systemctl list-timers --all
sudo find /etc/systemd/system /usr/lib/systemd/system -type f -mtime -7 -ls

Comprueba que no hay usuarios nuevos con UID 0 y que no existe /etc/ld.so.preload, un fichero que se usa a menudo para ocultar procesos:

awk -F: '$3 == 0 {print $1}' /etc/passwd
ls -l /etc/ld.so.preload
root
ls: cannot access '/etc/ld.so.preload': No such file or directory

Solo debe aparecer root, y lo normal es que /etc/ld.so.preload no exista.

Paso 7: Comprobar la integridad de los ficheros

Binarios del sistema

debsums compara los ficheros instalados con las sumas MD5 de los paquetes Debian. La opción -c muestra solo los ficheros que han cambiado:

sudo debsums -c

Una salida vacía es la situación esperada. Si aparece un binario como /usr/bin/ps o /usr/bin/ss, el sistema está comprometido. Ten en cuenta que los ficheros de configuración de /etc que tú has editado no se comprueban con -c, así que no generarán ruido.

Directorios temporales y web

Revisa los ficheros recientes en los directorios con permiso de escritura para todos, donde el malware suele dejar sus binarios:

sudo find /tmp /var/tmp /dev/shm -type f -mtime -3 -ls

Si alojas aplicaciones web, busca ficheros PHP modificados en la última semana y funciones típicas de las web shells:

sudo find /var/www -type f -name '*.php' -mtime -7 -ls
sudo grep -rlE 'eval\s*\(\s*(base64_decode|gzinflate|str_rot13)' /var/www --include='*.php'

Un fichero PHP dentro de un directorio de subidas (uploads/) casi nunca es legítimo.

Escáneres de rootkits

rkhunter y chkrootkit buscan firmas de rootkits conocidos y configuraciones anómalas:

sudo apt install rkhunter chkrootkit
sudo rkhunter --check --skip-keypress --report-warnings-only
sudo chkrootkit -q

Ambos generan falsos positivos (por ejemplo, avisos sobre scripts en /usr/bin o ficheros ocultos legítimos). Úsalos como pista para investigar, no como veredicto.

Paso 8: Analizar los logs del servidor web

Muchos ataques se dirigen a la aplicación y no al sistema. Con el formato de log por defecto de Nginx (combined), estas consultas muestran las IP con más peticiones y las peticiones por minuto:

sudo awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10
sudo awk '{print $4}' /var/log/nginx/access.log | cut -d: -f1-3 | uniq -c | tail -10
   4210 [25/Sep/2026:10:38
   4388 [25/Sep/2026:10:39
   9876 [25/Sep/2026:10:40

Un salto brusco de peticiones por minuto marca el inicio del ataque. Para ver qué URL se atacan y con qué códigos responde el servidor:

sudo awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10
sudo awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn

Miles de peticiones a /wp-login.php o /xmlrpc.php indican fuerza bruta contra WordPress; muchas respuestas 404 a rutas como /.env o /.git/config indican un escaneo automatizado. Con Apache, los logs están en /var/log/apache2/access.log y usan el mismo formato.

Paso 9: Contener el ataque

Cuando tengas claro qué ocurre, actúa por este orden.

Recoger evidencias

Guarda el estado del sistema antes de cambiar nada, porque matar procesos o reiniciar borra información útil:

INCIDENT_DIR="/root/incident-$(date +%Y%m%d-%H%M%S)"
sudo mkdir -p "$INCIDENT_DIR"
ps auxf | sudo tee "$INCIDENT_DIR/processes.txt" > /dev/null
sudo ss -tunap | sudo tee "$INCIDENT_DIR/connections.txt" > /dev/null
last -F | sudo tee "$INCIDENT_DIR/logins.txt" > /dev/null
sudo cp /var/log/auth.log /var/log/syslog "$INCIDENT_DIR/"

Si hay un proceso malicioso, copia también su binario, que sigue accesible desde /proc aunque se haya borrado del disco:

sudo cp /proc/PID/exe "$INCIDENT_DIR/malware.bin"

Bloquear IP atacantes

Con UFW, inserta la regla de denegación en la primera posición para que se evalúe antes que las reglas allow:

sudo ufw insert 1 deny from 198.51.100.23
sudo ufw status numbered

Para bloquear una red completa, usa la notación CIDR (198.51.100.0/24).

Detener procesos y cuentas comprometidas

Mata el proceso y bloquea la cuenta que lo lanzó, cerrando además todas sus sesiones (sustituye your_user por el usuario afectado):

sudo kill -9 PID
sudo usermod -L -e 1 your_user
sudo pkill -KILL -u your_user

Elimina después las entradas de cron, unidades de systemd y claves SSH que encontraste en los pasos 4 y 6. Si el atacante obtuvo root o debsums detectó binarios modificados, no intentes limpiar el sistema: restaura desde una copia de seguridad anterior al compromiso o reinstala, y cambia todas las credenciales.

Paso 10: Prevenir nuevos ataques

Bloqueo automático con Fail2ban

Fail2ban lee los logs y bloquea temporalmente las IP que fallan demasiadas veces. En Ubuntu 24.04 conviene que lea directamente del journal, para lo que necesita python3-systemd:

sudo apt install fail2ban python3-systemd

Crea la configuración local de la jaula de SSH:

sudo nano /etc/fail2ban/jail.local
[sshd]
enabled  = true
backend  = systemd
maxretry = 5
findtime = 10m
bantime  = 1h

Activa el servicio, reinícialo para que cargue la nueva configuración y comprueba que la jaula está funcionando:

sudo systemctl enable fail2ban
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed: 3
|  |- Total failed:     27
|  `- Journal matches:  _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
   |- Currently banned: 2
   |- Total banned:     2
   `- Banned IP list:   198.51.100.23 203.0.113.199

Endurecer SSH

La medida más eficaz contra la fuerza bruta es desactivar las contraseñas. Asegúrate primero de que puedes entrar con tu clave SSH, y después crea un fichero de configuración adicional:

sudo nano /etc/ssh/sshd_config.d/10-hardening.conf
PasswordAuthentication no
PermitRootLogin no

Valida la sintaxis y recarga el servicio:

sudo sshd -t
sudo systemctl reload ssh

Guardar histórico de métricas

Para poder comparar la próxima vez con lo "normal", activa la recogida periódica de sysstat. Edita /etc/default/sysstat y cambia ENABLED="false" por ENABLED="true", y después:

sudo systemctl enable --now sysstat

A partir de ahí, sar -n DEV y sar -q muestran el tráfico y la carga de todo el día.

Solución de problemas

  • /var/log/auth.log no existe: el sistema no tiene rsyslog. Usa sudo journalctl -u ssh en su lugar, o instala rsyslog.
  • ss muestra direcciones como [::ffff:198.51.100.44]: son conexiones IPv4 aceptadas por un socket IPv6. Es normal y el conteo sigue siendo válido.
  • Fail2ban no arranca con el error Have not found any log file for sshd jail: falta backend = systemd en la jaula o no está instalado python3-systemd.
  • Te has bloqueado a ti mismo con UFW: accede por la consola del servidor y elimina la regla con sudo ufw delete NUMERO, usando el número que muestra sudo ufw status numbered.

Conclusión

Ya tienes un procedimiento para diagnosticar un posible ataque: identificar el origen del tráfico y de los accesos, localizar procesos y mecanismos de persistencia ajenos, verificar la integridad del sistema y contener el problema sin perder evidencias. Como siguientes pasos, configura copias de seguridad automáticas que te permitan restaurar un sistema limpio, limita el acceso a SSH por IP con UFW y pon en marcha una monitorización que te avise de picos de conexiones o de CPU antes de que los notes tú.