Anatomía de una botnet SSH: 121,222 ataques a un servidor en 7 días
Construimos el software que entregamos y además lo operamos. Esa segunda mitad es la que la mayoría de los estudios devuelve junto con el código fuente, y es de donde sale este artículo: Appluex mantiene la infraestructura de producción detrás de las aplicaciones que construye, lo que significa que también leemos los registros, atendemos las alertas y auditamos las máquinas. Esta es una de esas auditorías, publicada completa.
Empezó con un cliente haciendo una pregunta sencilla sobre uno de nuestros servidores de producción: ¿está expuesta en algún lado la IP de origen? Es una buena pregunta, porque un servidor detrás de un CDN pierde casi toda su protección en el momento en que se filtra su dirección real. Fuimos a buscar, confirmamos que la dirección estaba bien oculta, y encontramos otra cosa completamente distinta.
En siete días, esa máquina absorbió 121,222 intentos fallidos de inicio de sesión SSH desde 925 direcciones IP distintas. No fue un ataque dirigido. Es la radiación de fondo normal de internet, que resulta ser mucho más pesada de lo que imagina la mayoría de quienes operan un servidor.
Los números
| Medición | Valor |
|---|---|
| Ventana medida | 7 días |
| Intentos de autenticación fallidos | 121,222 |
| Intentos fallidos, últimas 24h | 21,272 |
| Direcciones IP de origen distintas | 925 |
| Nombres de usuario distintos probados | 4,201 |
| Intentos solo contra root | 26,419 |
| Inicios de sesión por contraseña exitosos, histórico | 0 |
La última fila es la que importa. No cero durante la semana. Cero durante toda la vida de la máquina.
Un segundo servidor, medido el mismo día
Todo lo anterior viene de una sola máquina. Después medimos un segundo servidor, sin relación con el primero, el mismo día: otra región, otro propósito, otro software, ningún nombre DNS compartido, nada que los conecte públicamente. Si este tráfico fuera dirigido a alguien, los dos deberían verse muy distintos.
| Medición | Primer servidor | Segundo servidor |
|---|---|---|
| Ventana medida | 7 días | ~28 horas |
| Intentos fallidos | 121,222 | 22,479 |
| Tasa diaria implícita | ~17,300 | ~19,300 |
| IPs de origen distintas | 925 | 1,830 |
| Usuarios distintos probados | 4,201 | 3,178 |
| Inicios de sesión por contraseña exitosos | 0 | 0 |
Las tasas diarias quedan a menos del 10% una de otra. Dos máquinas que no comparten nada reciben el mismo ritmo de ataque, que es la evidencia más clara disponible de que nadie eligió a ninguna de las dos. El segundo servidor vio incluso el doble de direcciones distintas en la cuarta parte del tiempo.
Y las mismas máquinas aparecieron en ambos: dos direcciones de 2.57.121.0/24 atacaron al primero y al segundo. No son dos campañas que se parecen, es una campaña que encontró a los dos.
Pero adivinaban cosas distintas
Aquí los dos divergen, y es el resultado más interesante de todo el ejercicio. Al primer servidor lo sondearon con una lista genérica de infraestructura: deploy, ansible, puppet, oracle, minecraft. Al segundo, con una lista de criptomonedas: admin (1,445), ubuntu (417), wallet (292), crypto (216), bitcoin (157), solana (138), ethereum (125), blockchain (120).
Esos seis nombres de cripto suman 1,048 intentos y casi no aparecen en el primer servidor. Ninguna distribución de Linux crea esas cuentas. Son cuentas que crea una persona que corre un nodo, un bot de trading o una integración con un exchange, y alguien apuesta a que una máquina con una cuenta así tiene una contraseña débil. ubnt es otra apuesta distinta: el usuario por defecto de los routers Ubiquiti.
Así que la botnet no corre un solo script en todas partes: corre variantes, y cada servidor recibe la que le llegue.
El único ajuste que sacó a un servidor de casi toda la campaña
En el primer servidor, root fue atacado 26,419 veces. En el segundo, una.
La diferencia es una línea: el segundo tiene PermitRootLogin no. Cuando sshd rechaza root de plano, el intento nunca llega a ser una adivinanza de contraseña y el host deja de valer la pena para esa parte de la campaña. Los ajustes más baratos no solo defienden: te sacan de categorías enteras de tráfico.
No es un atacante. Es infraestructura alquilada
Del tráfico que pudimos atribuir, el 84.6% provino de IPs de centros de datos y no de conexiones residenciales. Este es el dato más útil de todo el conjunto. No son computadoras domésticas infectadas. Son máquinas virtuales que alguien alquiló, por docenas, específicamente para esto.
Se nota en cómo se agrupan las direcciones. Los ataques no llegaron desde 925 lugares dispersos. Llegaron en bloques. Un solo bloque, 109.160.32.0/24, produjo 51,785 intentos desde 25 hosts distintos dentro del mismo /24: el 43% de todo, desde lo que en la práctica es un estante en un centro de datos.
Siguiendo el rastro hasta una empresa real
Los datos de registro son públicos y llegan más lejos de lo que la gente espera. Siguiendo ese /24 a través de RIPE: el bloque se llama GBTCloud, lo anuncia AS197170, TechTies Inc., su dirección registrada está en Cornelius, Carolina del Norte, y sus objetos de mantenimiento resuelven a Global Communication Net Plc, un Registro Local de Internet de RIPE en Sofía, Bulgaria, cuyo contacto de abuso sí funciona: [email protected].
Nada de eso es ilegal. Todo eso hace más difícil que prospere una denuncia, que es precisamente el motivo de organizarlo así.
Qué estaban adivinando
Se probaron 4,201 nombres de usuario distintos. Los más frecuentes fueron admin (1,572), ubuntu (1,468), user (949), oracle (443), test (430), debian (417) y deploy (391). Es una lista de palabras comercial, sin cambios en años, rociada contra todo.
Vale la pena mirar qué contiene: ubuntu y debian, las cuentas por defecto de las imágenes en la nube. deploy, ansible, puppet y git, las cuentas de servicio que la automatización crea y nadie audita. minecraft y steam, porque los servidores de juegos los administra gente que no es administradora de sistemas. Y claude, intentado 150 veces: las listas ya incluyen nombres de herramientas de IA, suponiendo que alguien creó una cuenta de servicio así con una contraseña débil.
Los nombres de usuario revelan la intención
Esa lista no es aleatoria, y sus categorías se corresponden con lo que busca el operador:
- Cuentas por defecto de la nube (
ubuntu,debian,pi): una apuesta a que el usuario por defecto de la imagen todavía tiene contraseña. - Cuentas de servicio de automatización (
deploy,ansible,puppet,chef,git). Esta es la categoría valiosa. Esas cuentas suelen existir, a menudo tienen sudo o llaves de despliegue, y con frecuencia se crean una vez con una contraseña compartida que nadie rota. Una cuentadeploycomprometida no es un servidor: es el pipeline, y todo aquello a lo que ese pipeline publica. - Restos de desarrollo abandonados (
test,dev,student,guest,aa,a): cuentas creadas para una tarde y nunca eliminadas. - Nombres que no pertenecen a ninguna distribución (
binux,wkh,panda). No son cuentas por defecto en ningún lado. La lectura razonable es que son cuentas de persistencia dejadas por malware anterior, lo que significa que parte de esta campaña no busca servidores seguros que vulnerar, sino servidores que alguien más ya vulneró.
Una segunda campaña, en paralelo, buscando credenciales
SSH era solo la mitad. Los registros web del mismo host mostraron un esfuerzo automatizado aparte con un objetivo mucho más específico: 474 peticiones buscando archivos de secretos, en 104 variantes distintas de ruta de un solo nombre de archivo.
/.env /v1/.env /v2/.env
/staging/.env /production/.env /portal/.env
/secrets.env /ses.env /sendgrid.env
/uploads../.env /userfiles/x?path=../../.env
/.git/HEAD /phpinfo.php /wp-admin/install.phpHay que leer los nombres de archivo, no las rutas. ses.env y sendgrid.env llevan el nombre de proveedores concretos de envío de correo. No buscan configuración en general: quieren credenciales de envío de correo, porque una llave robada de SES o SendGrid se monetiza el mismo día como relay de spam operando sobre la reputación de otra persona.
El resto completa el cuadro. Las variantes con ../ son traversal de directorios contra fallos conocidos de frameworks. Las peticiones a .git/HEAD comprueban si el sitio se desplegó copiando un repositorio al servidor, lo que expondría todo su historial de código, incluido lo que se subió por accidente.
El hilo común entre ambas campañas es el mismo: el objetivo son las credenciales, no el servidor. El servidor es solo donde se guardan.
Por qué: cuánto vale realmente un servidor comprometido
Es tentador suponer que quien ataca un servidor pequeño tiene algo personal. Casi nadie lo tiene. Existe un mercado de hosts comprometidos, y una máquina vale la pena por razones que no tienen nada que ver con quién es su dueño:
- Minería de criptomonedas. La opción de menor esfuerzo. Tu CPU, sus monedas, tu electricidad y tu factura de hosting.
- Ampliar la botnet. Un host comprometido se pone a buscar más hosts. Casi seguro es la razón de que el 84.6% de las direcciones que nos atacaron fueran máquinas de centros de datos y no computadoras domésticas: buena parte de los atacantes son ellos mismos víctimas previas, reclutadas para buscar a la siguiente. La campaña se autofinancia en máquinas.
- Reventa de ancho de banda como proxy. El tráfico se vende a servicios que necesitan que las peticiones salgan de algún lugar poco llamativo: fraude publicitario, scraping, relleno de credenciales contra terceros. Te conviertes en el remitente del abuso de otro.
- Relay de spam y phishing. El motivo de que esas sondas
.envnombren proveedores de correo. Un servidor con reputación de IP limpia, o mejor, una llave robada de un servicio de correo establecido, vale dinero real para una operación de phishing. - Saltar a la cuenta de nube, que es la cara. Los servidores de aplicaciones modernos guardan credenciales del proveedor en disco para que la aplicación pueda usarlas. Quien las lee ya no tiene un servidor: tiene una cuenta. Capacidad de levantar instancias a tu nombre, alcanzar buckets de almacenamiento y, en el peor arreglo, alcanzar las copias de seguridad de los mismos datos que acaba de cifrar. Aquí una molestia se convierte en un evento que termina con una empresa, y es la razón de que la búsqueda de credenciales sea más peligrosa que la fuerza bruta.
Nada de esto requiere que alguien haya oído hablar de ti. Esa es la parte que conviene interiorizar: no te están apuntando, te están enumerando, y la economía funciona a una escala donde una tasa de éxito mínima sobre millones de hosts es un negocio rentable.
Por qué nada de eso funcionó
Cero inicios de sesión por contraseña exitosos, jamás, contra 121,222 intentos en una semana. No porque la contraseña fuera fuerte, sino porque todos los accesos a esa máquina siempre usaron una llave SSH, y una llave no se adivina con una lista de palabras.
Pero conviene ser honestos, porque aquí está la lección real: el servidor todavía tenía la autenticación por contraseña habilitada y todavía permitía iniciar sesión como root. Nadie usaba contraseñas. Simplemente seguían siendo posibles. Se había puesto un cuidado enorme en ocultar el servidor detrás de un CDN y restringir sus puertos web a los rangos del CDN. Todo eso protege el puerto 443. Mientras tanto el puerto 22 estaba abierto a todo internet, aceptando contraseñas, para root.
La solución, y los dos detalles fáciles de arruinar
Primero: los archivos drop-in se leen en orden y gana el primer valor. En Ubuntu, cloud-init escribe 50-cloud-init.conf con PasswordAuthentication yes. SSH toma el primer valor que obtiene, así que un archivo llamado 90-hardening.conf se lee después y se ignora en silencio. El nuestro está numerado 10. Una configuración que se ve bien, valida bien y no hace nada es peor que no tener configuración.
Segundo: PermitRootLogin prohibit-password, no no. prohibit-password permite que root entre por llave, que es como se conecta la mayoría de los despliegues. no bloquea root por completo, y si tu automatización entra como root, acabas de dejarte fuera de tu propio servidor.
El procedimiento seguro: respalda la configuración, valida con sshd -t antes de recargar, programa una reversión automática en cinco minutos, usa reload y no restart, abre una conexión completamente nueva para comprobar que funciona, y solo entonces cancela la reversión. El tercer paso es el que todos se saltan, y convierte “me quedé fuera de producción” en “espero cinco minutos”.
Una falsa alarma que vale la pena publicar
Durante el escaneo, la auditoría de binarios SUID marcó /usr/lib/cargo/bin/sudo, /usr/bin/sudo.ws y binarios llamados gnuuname, gnuid o gnumv. Binarios privilegiados en un directorio inesperado, más copias renombradas de utilidades base, es casi la descripción de manual de un rootkit.
Son completamente legítimos. Ubuntu 26.04 incluye sudo-rs y la reescritura uutils de coreutils, que toman los nombres por defecto mediante alternatives y renombran los originales de GNU con el prefijo gnu. Un comando lo resuelve: dpkg -S. Si un archivo pertenece a un paquete, llegó por el gestor de paquetes. Comprobar la pertenencia antes de dar la alarma es la diferencia entre una auditoría y un simulacro de incendio.
Qué hacer al respecto
- Desactiva la autenticación por contraseña. Este solo cambio vuelve irrelevante toda la campaña.
- Confirma que tu configuración se está leyendo de verdad.
sshd -Timprime los valores en efecto. Confía en eso, no en el archivo que editaste. - Root solo por llave, con
prohibit-password. - Instala fail2ban y pon tu propia dirección en la lista de exclusión.
- Lee tus registros de autenticación una vez. El volumen sorprende la primera vez.
- Si usas un CDN, verifica que el origen no sea accesible directamente y que tu lista de rangos del CDN esté actualizada.
La conclusión tranquilizadora es que la defensa aquí es vieja, aburrida y completamente efectiva. 121,222 intentos, 925 hosts, cuatro mil nombres de usuario, una campaña en ascenso en diez países, y todo quedó derrotado de antemano por usar llaves SSH. Los atacantes no son sofisticados. Son pacientes y numerosos, y cuentan con la pequeña cantidad de servidores que todavía aceptan una contraseña.
Por qué publicamos algo así
Una auditoría vale lo que cambia. Esta produjo una lista corta de correcciones sobre nuestra propia infraestructura y las aplicamos todas el mismo día, que es el estándar con el que tratamos también la infraestructura de nuestros clientes. Lanzar una aplicación es la primera mitad del trabajo. Saber qué le está pasando a las tres de la mañana, seis meses después, es la mitad que decide si sigue en pie.
Si tienes software en producción y nunca has leído tus propios registros de autenticación, esa es la hora mejor invertida de este mes. Y si prefieres que alguien lo haga a fondo, ese es trabajo que hacemos, tanto para aplicaciones que construimos nosotros como para las que no.
Metodología: las cifras se midieron desde los registros de autenticación de dos hosts Linux de producción que operamos, en ventanas de siete días y de veintiocho horas respectivamente, ambas terminando el 14 de septiembre de 2026, enriquecidas con geolocalización de IP pública y registros de RIPE/ARIN. La atribución se detiene en los datos públicos de registro: no escaneamos, sondeamos ni nos conectamos a ninguna máquina atacante, lo que sería acceso no autorizado a sistemas de terceros sin importar lo que hayan hecho primero, y buena parte de esas máquinas son ellas mismas víctimas. Se omiten deliberadamente los detalles que identifican a nuestros hosts, sus redes o sus administradores.
Preguntas frecuentes
How many SSH attacks does a normal server get?
More than most operators expect. A single production host with no public profile absorbed 121,222 failed SSH login attempts from 925 distinct IP addresses in seven days, around 200 per minute sustained, and the daily volume rose 41% across the window. This is untargeted background scanning, not a directed attack.
Are SSH brute-force attacks coming from hacked home computers?
Mostly no. In this dataset 84.6% of attributable traffic came from datacenter IP addresses rather than residential connections, and the addresses arrived in tight blocks: 25 hosts inside a single /24 produced 43% of all traffic. These are rented virtual machines, not an infected consumer botnet.
Does disabling SSH password authentication actually stop them?
Completely. Across 121,222 attempts there were zero successful password logins, because every account used key-based authentication and a key cannot be guessed from a wordlist. Turning password authentication off makes the entire campaign irrelevant rather than merely slower.
Why is my sshd hardening config being ignored?
Almost always drop-in ordering. SSH takes the first value it obtains for a keyword, and on Ubuntu cloud-init writes /etc/ssh/sshd_config.d/50-cloud-init.conf setting PasswordAuthentication yes. A hardening file numbered above 50 is read afterwards and silently ignored. Name it below 50 and verify with sshd -T, which prints the values actually in effect.
What are SSH brute-force attackers actually looking for?
Credentials, not the server itself. The username list they guess is weighted toward automation service accounts like deploy, ansible, puppet and git, because those often hold sudo rights or deployment keys, which turns one server into a whole pipeline. A parallel campaign against the same host made 474 HTTP requests across 104 path variants hunting for .env secrets files, including ones named after specific email providers such as ses.env and sendgrid.env.
What do attackers do with a compromised server?
There is a commodity market for hosts. The common uses are cryptomining, conscripting the machine into scanning for more victims (which is why 84.6% of the attacking addresses in this dataset were datacenter machines, many of them previous victims), reselling its bandwidth as proxy traffic for ad fraud and scraping, running spam and phishing relays, and reading cloud provider credentials off disk to pivot into the billing account. That last one is the expensive outcome, because it can reach storage and backups.
Why would anyone attack a small server nobody has heard of?
Nobody chose it. This is untargeted enumeration of the whole address space, and the economics work because a tiny success rate across millions of hosts is profitable. A meaningful share of the attacking machines are themselves compromised servers that were recruited to scan for the next one, which makes the campaign self-funding in hardware.
Is PermitRootLogin no safer than prohibit-password?
Safer, but it will lock you out if your deployment automation connects as root. prohibit-password still permits root to authenticate by SSH key while refusing passwords, which is the setting most infrastructure actually needs.
¿Estás pensando en construir esto?
Appluex diseña y lanza apps móviles y web en producción, incluidas funciones de inteligencia artificial. Hablemos.