De un VPS Vulnerable a una Fortaleza Automatizada - Parte I
Muchas veces montamos un VPS como laboratorio, y como sysadmin analizas tu propio servidor y piensas: "esto es un desastre". No porque algo haya explotado todavía, sino porque sabes que es cuestión de tiempo para que eso pase. Este post documenta exactamente eso: un VPS expuesto (un lab para probar muchas cosas, casi un HoneyPot) a proposito, un VPS desordenado y ruidoso, y despues de ver todo el ruido que hay, poco a poco te propones en convertirlo en una infraestructura que trabaja sola mientras duermes.
Arquitecto: 0n3Z3r0 | Roles: SysAdmin Senior & Ethical Hacker.
Objetivo: Pasar de superficie de ataque total a infraestructura bajo Privilegio Mínimo y Monitoreo Continuo.
Serie: Parte I de III — Auditoría, Permisos, Tráfico y Almacenamiento.
1. Lo primero que hice: que el servidor me hable solo
Antes de tocar el firewall, antes de mover usuarios, lo primero que necesitaba era visibilidad. Porque no puedes defender lo que no ves, y revisar /var/log/auth.log a mano cada día no es administrar un servidor, es torturarse.
La solución fue simple: un script que corre solo cada noche a las 23:00 y me deja un resumen en el log. Sin intervención. Sin acordarme. Si algo raro pasó durante el día, lo sé al día siguiente sin haber hecho nada.
crontab -l
# La línea que hace la magia:
0 23 * * * /usr/local/bin/ssh-summary.sh >> /var/log/ssh-summary.log 2>&1
Lo que hace ese script es centralizar tres cosas: los intentos de acceso SSH fallidos, las IPs que fueron bloqueadas, y los accesos exitosos del día. Nada más. Pero esas tres cosas, juntas, te cuentan la historia completa de lo que pasó mientras no estabas mirando.
| Componente | Qué hace por ti |
|---|---|
ssh-summary.sh |
Corre cada noche a las 23:00. Agrupa intentos fallidos, IPs bloqueadas y accesos reales del día en un solo sitio. |
/var/log/ssh-summary.log |
Tu historial de inteligencia. Con el tiempo empiezas a ver patrones: rangos de IPs recurrentes, horarios de escaneo, países de origen. |
crontab -l |
Úsalo también para auditar: si alguien comprometió el sistema y plantó persistencia, muchas veces aparece aquí. Revísalo de vez en cuando. |
Explicacion no tecnica
Tu servidor recibe cientos de golpes al día de bots escaneando internet. Revisar el log completo es como ver horas de cámara de seguridad buscando algo sospechoso. El ssh-summary.sh es contratar a alguien que lo hace por ti y te deja una nota cada mañana: "847 intentos fallidos, 23 IPs bloqueadas, solo tú entraste a las 14:32". Eso es inteligencia. Lo otro es ruido.
2. Quién puede hacer qué: separar identidades de verdad
Este es el cambio que más cuesta entender hasta que lo ves en acción. Mucha gente opera su VPS entero desde root. Todo. Siempre. Y mientras nada falla, parece cómodo. El problema es que cuando algo falla, y va a fallar, ese atacante tiene las llaves de todo.
La solución no es compleja. Es simplemente decidir qué hace cada quien y no cruzar esa línea. Tres usuarios, tres funciones, sin excepciones:
- labadmin: El día a día: despliegues, gestión de laboratorios, administración general.
- services: Corre los contenedores Docker. Sin shell interactiva. Sin sudo. Una jaula.
Y lo mismo aplica al filesystem. Mover las herramientas de recon y exploit a /opt, fuera del PATH estándar, no es solo orden: es que un exploit automatizado que entre al servidor no va a encontrar esas herramientas a mano. Tendría que saber exactamente dónde buscar. Ese fricción extra puede ser la diferencia.
/opt/ ├── recon/ # Herramientas de reconocimiento (fuera del PATH estándar) └── exploit/ # Herramientas ofensivas (permisos restrictivos, aisladas)
| Usuario / Directorio | Por qué importa |
|---|---|
root |
Acceso directo por SSH deshabilitado (PermitRootLogin no). Si alguien quiere root, primero tiene que comprometer otra cuenta. Un paso más. |
labadmin |
Si el entorno de lab es comprometido, el atacante se queda ahí. No puede tocar producción ni los servicios del sistema. |
services |
Si un contenedor Docker es vulnerado, el atacante cae en este usuario. Sin shell, sin sudo, sin movimiento lateral posible. |
/opt/recon y /opt/exploit |
Fuera del PATH del sistema. Un exploit automatizado no las encuentra. Si alguien quiere usarlas, tiene que saber exactamente dónde están. |
Explicacion no tecnica
Piensa en las llaves de un edificio. El conserje tiene la del cuarto de limpieza. El técnico tiene la del sótano. El director tiene la del servidor. Ninguno tiene todas. Si alguien le roba la llave al conserje, puede fregar el suelo, pero no puede llegar al servidor. Eso y nada más es el Principio de Privilegio Mínimo: no es desconfianza, es contención de daños.
3. El firewall no es magia, es política
Cuando haces ss -tulpen en un VPS recién configurado, el resultado da miedo. Puertos por todos lados. Y la reacción instintiva es cerrar todo a lo loco. Pero no es así como funciona: la clave no es cuántos puertos escuchan internamente, sino cuántos puertos deja pasar el firewall desde fuera.
La política es simple y no tiene excepciones: todo denegado por defecto, y solo abres lo que puedes justificar.
ufw status verbose
# La política base, el verdadero Kill Switch:
Default: deny (incoming) → Todo lo que no invitaste: bloqueado
Default: allow (outgoing) → El servidor puede salir sin problemas
# Lo único que pasa desde internet:
22/tcp ALLOW IN → SSH
80/tcp ALLOW IN → HTTP (solo para redirigir a HTTPS)
443/tcp ALLOW IN → HTTPS
Y aquí está la parte que me parece más elegante de toda la configuración: Tailscale. Con tailscaled.service corriendo, Portainer, las bases de datos, cualquier servicio interno, solo son accesibles desde mi red privada virtual. No hace falta abrir ningún puerto extra. Para cualquier bot escaneando internet, esos servicios directamente no existen.
| Capa de defensa | Qué resuelve |
|---|---|
| UFW deny (default) | Corta de raíz el ruido: bots de escaneo, intentos de fuerza bruta contra puertos aleatorios, tráfico basura en general. |
| Solo puertos 22, 80, 443 | Superficie de ataque mínima. Tres vectores a vigilar, no treinta. Mucho más fácil de auditar. |
| Tailscale VPN | Servicios sensibles invisibles desde internet. No necesitas abrir puertos extra. Si no está en la VPN, no lo ve nadie. |
Explicacion no tecnica
Imagina un edificio de oficinas en plena ciudad. UFW es el portero: solo deja entrar por tres puertas contadas (22, 80, 443). Todo lo demás está tapiado. Tailscale es un túnel secreto que va directo desde tu casa hasta tu despacho en el décimo piso, sin pasar por recepción. Para alguien mirando desde la calle, ese despacho no existe. Eso es lo que queremos.
4. El disco no miente: Docker y Snap se comen el espacio
Hay una cosa que nadie te dice cuando empiezas a meter contenedores y paquetes Snap en un servidor: se comen el disco sin que te des cuenta. No de golpe, sino poco a poco. Y un día haces df -h y el disco está al 87% sin saber bien por qué.
El du -sh /* es la autopsia. Te dice exactamente dónde está el peso y ya no puedes ignorarlo:
du -sh /* 2>/dev/null | sort -rh | head -10
# Lo que encontré:
22G /var → Volúmenes Docker acumulados + logs sin rotar
12G /snap → El precio de los paquetes Snap
4.2G /usr → Sistema base, esto es normal
Los 22GB de /var son principalmente volúmenes Docker huérfanos: imágenes de contenedores que ya no usas, capas de builds anteriores, datos de contenedores borrados que siguen ocupando espacio. Un docker system prune bien dado puede recuperar bastante. Para Snap, el problema es que por defecto guarda las dos últimas versiones de cada paquete. Con unos pocos paquetes instalados eso se acumula. La solución es decirle que guarde solo dos revisiones.
Y para que nada de esto sea un problema de seguridad mientras duermes: unattended-upgrades activo. Los parches críticos se aplican solos. Sin recordatorios, sin ventanas de vulnerabilidad innecesarias.
| El problema | El correctivo |
|---|---|
/var a 22 GB |
docker system prune -a --volumes para limpiar. Después, automatizar la limpieza periódica vía cron y activar logrotate para los logs. |
/snap a 12 GB |
snap set system refresh.retain=2 para que Snap deje de acumular versiones antiguas sin control. |
| Parches manuales olvidados | unattended-upgrades activo y configurado. Los CVEs críticos no esperan a que te acuerdes de actualizar. |
Explicacion no tecnica
Docker y Snap son como dos inquilinos que nunca tiran la basura. Cada semana traen cosas nuevas pero las viejas se quedan ahí. Con el tiempo el piso se colapsa aunque nadie esté haciendo nada malo. docker system prune es el día de limpieza general. snap set refresh.retain=2 es decirles que no guarden más de dos mudas de ropa. Y unattended-upgrades es el técnico de mantenimiento que arregla las goteras solo, sin que tengas que llamar cada vez.
> SYSTEM_READY > NODE_ONLINE