Introducción
Este artículo presenta un caso real de auditoría de seguridad en servidores Linux en producción, anonimizado para preservar la confidencialidad del cliente. El objetivo fue evaluar el grado de cumplimiento del Esquema Nacional de Seguridad (ENS) en categoría media.
“Lo que encontrarás en este artículo no es teoría — son hallazgos reales, comandos y salidas reales de sistemas en producción.”
Entorno técnico
Sistema operativo auditado: Ubuntu 24.04.4 LTS (Noble Numbat)
Arquitectura: x86_64
Categoría ENS: Media
Normativa de referencia:
- Real Decreto 311/2022 (ENS)
- CCN-STIC-619: Secure Deployment Guide Ubuntu 22.04 LTS
- CCN-STIC-807B: Bastionado de Servidores Linux
- CCN-STIC-810: Procedimientos de Empleo Seguro – OpenSSH
Controles ENS evaluados:
- op.acc.5
- op.acc.6
- mp.com.2
- mp.com.3
- mp.com.4
- mp.eq.3
- mp.sw.2
- mp.si.2
- op.exp.8
- op.exp.9
Contexto del proyecto
El cliente operaba varios servidores Ubuntu 22.04.4 LTS en producción con distintos roles y funciones. Todos ellos debían cumplir los requisitos del ENS categoría media.
La auditoría incluía:
- Información general del sistema y configuración básica
- Gestión de usuarios y políticas de autenticación (op.acc.5, op.acc.6)
- Hardening del sistema (mp.eq.3, mp.si.2)
- Servicios activos y software instalado (mp.sw.2)
- Puertos de comunicación y servicios de red (mp.com.2)
- Logs y monitorización (op.exp.8, op.exp.9)
- Actualizaciones y parches (mp.sw.2)
Todos los registros y análisis utilizan hora UTC como referencia, requisito del ENS (op.exp.8) para garantizar la trazabilidad y correlación de eventos entre sistemas.
Metodología: inventario previo al firewall
tes de modificar cualquier configuración, lo primero es conocer exactamente qué servicios están escuchando y qué conexiones activas no deben interrumpirse.
# Puertos en escucha con proceso asociado
ss -tulpen
# Conexiones activas establecidas (NO interrumpir)
ss -tpn state established
# Procesos con conexiones de red
lsof -i -P -n
# Servicios en ejecución
systemctl list-units --type=service --state=running
Este fue el salida real de ss -tulpen en uno de los servidores (datos sensibles anonimizados):
Netid State Local Address:Port Process
tcp LISTEN 127.0.0.1:5432 postgres (BD interna)
tcp LISTEN 127.0.0.1:6379 redis-server (caché interna)
tcp LISTEN 127.0.0.1:8001 gunicorn (app backend)
tcp LISTEN 0.0.0.0:22 sshd (acceso remoto)
tcp LISTEN *:80 nginx (web)
tcp LISTEN *:443 nginx (web seguro)
tcp LISTEN 172.16.x.x:10001 qualys-cloud-agent
udp UNCONN 0.0.0.0:5353 avahi-daemon
Observación positiva: PostgreSQL, Redis y el backend escuchan solo en 127.0.0.1, correctamente aislados. (cumple mp.com.2: protección de la confidencialidad mediante segregación).
Observación negativa: avahi-daemon activo en producción — servicio pensado para entornos de escritorio, sin utilidad en servidor y con superficie de ataque innecesaria (incumple mp.eq.3: minimización de servicios).
Hallazgo crítico 1: gestión de usuarios
Control ENS afectado: op.acc.5 (Control de acceso lógico), op.acc.6 (Mecanismo de autenticación)
Referencia normativa: CCN-STIC-810 apartados 3.1 y 3.2
Problema detectado:
# Usuarios con shell interactiva
grep -v '/nologin\|/false' /etc/passwd | awk -F: '{print $1, $7}'
# Usuarios con UID 0
awk -F: '$3 == 0 {print $1}' /etc/passwd
# Estado de contraseñas
sudo awk -F: '$2 !~ /^[!*]/ {print $1, "→ contraseña activa"}' /etc/shadow
Se detectó en varios servidores:
- Cuentas de servicio con shell interactiva (deberían usar /usr/sbin/nologin)
- Cuentas aparentemente abandonadas pero con acceso activo
- Login directo como root habilitado por SSH (PermitRootLogin yes)
- Autenticación SSH mediante contraseña en lugar de solo clave pública
Impacto:
El acceso directo como root junto con autenticación por contraseña es uno de los vectores más explotados en servidores expuestos a internet. Los logs mostraban intentos de fuerza bruta constantes sin mecanismos de bloqueo.
Incumplimiento ENS:
- op.acc.5: No se implementa control de acceso mediante autenticación robusta
- op.acc.6: No se emplea mecanismo de autenticación basado en certificados/claves
Corrección aplicada:
Referencia: CCN-STIC-810, apartado 3.2 (Autenticación basada en clave pública)
# Deshabilitar login root por SSH
sed -i 's/^PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
# Deshabilitar autenticación por contraseña
sed -i 's/^PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
# Verificar antes de reiniciar
sshd -t && systemctl restart sshd
# Deshabilitar shell en cuentas de servicio
usermod -s /usr/sbin/nologin nombre_cuenta
Cumplimiento ENS conseguido: op.acc.5, op.acc.6
Hallazgo crítico 2: firewall ausente o mal configurado
Control ENS afectado: mp.com.2 (Protección de la confidencialidad), mp.com.4 (Segregación de redes).
Referencia normativa: CCN-STIC-807B apartado 4.3, CCN-STIC-619 apartado 6.2
Problema detectado:
ufw status verbose
systemctl status ufw
En algunos servidores:
Status: inactive
Además, se detectó un caso crítico: MySQL expuesto en todas las interfaces (0.0.0.0:3306).
ss -tulpen | grep 3306
# tcp LISTEN 0.0.0.0:3306 ← CRÍTICO
Incumplimiento ENS:
- mp.com.2: No se protege la confidencialidad mediante control de flujos
- mp.com.4: No existe segregación de redes ni filtrado perimetral
Corrección aplicada (ENS nivel medio):
Referencia: CCN-STIC-807B apartado 4.3.1 (Configuración de firewall local).
El orden es clave para no perder acceso:
# PASO 1: Permitir SSH
ufw allow from IP_ADMINISTRACION to any port 22 proto tcp
# PASO 2: Permitir servicios necesarios
ufw allow 80/tcp
ufw allow 443/tcp
# PASO 3: Política por defecto
ufw default deny incoming
ufw default allow outgoing
# PASO 4: Activar firewall
ufw enable
# PASO 5: Verificar
ufw status verbose
Para MySQL:
# /etc/mysql/mysql.conf.d/mysqld.cnf
bind-address = 127.0.0.1
systemctl restart mysql
ss -tulpen | grep 3306
# tcp LISTEN 127.0.0.1:3306 ← correcto
Lección aprendida: mantener siempre dos sesiones SSH abiertas antes de activar el firewall. Cumplimiento ENS conseguido: mp.com.2, mp.com.4
Hallazgo medio 1: servicios innecesarios activos
Control ENS afectado: mp.eq.3 (Protección de equipos fijos), mp.si.2 (Gestión de la configuración de seguridad).
Referencia normativa: CCN-STIC-619 apartado 3.4 (Minimización de servicios)
Problema detectado:
Servicios activos sin justificación:
- avahi-daemon (descubrimiento de servicios – innecesario en servidor)
- ModemManager (gestión de módems – innecesario)
- Cups (impresión – innecesario en servidor)
- Upower (gestión de energía – innecesario)
- Multipathd (almacenamiento multipath – no configurado)
- Snapd (gestor de paquetes snap – no utilizado)
Cada servicio innecesario incrementa la superficie de ataque.
Incumplimiento ENS:
- mp.eq.3: No se minimiza la superficie de exposición
- mp.si.2: Configuración de seguridad no optimizada (servicios por defecto activos)
Corrección aplicada
Referencia: CCN-STIC-619 apartado 3.4
Corrección aplicada
for svc in avahi-daemon ModemManager cups upower snapd; do
systemctl disable --now $svc 2>/dev/null && echo "Desactivado: $svc"
done
systemctl list-units --type=service --state=failed
Cumplimiento ENS conseguido: mp.eq.3, mp.si.2
Hallazgo medio 2: hardening SSH incompleto
Control ENS afectado: mp.com.3 (Protección de la integridad y autenticidad), op.acc.5
Referencia normativa: CCN-STIC-810 apartados 3.3, 3.4 y 3.5
Estado inicial
grep -E "^(PermitRootLogin | PasswordAuthentication | X11Forwarding | MaxAuthTries | Protocol)" /etc/ssh/sshd_config
Configuración con valores inseguros o ausentes.
Incumplimiento ENS:
- mp.com.3: Configuración SSH permisiva permite ataques de fuerza bruta y sesiones prolongadas
- op.acc.5: Control de acceso insuficiente (múltiples intentos, sesiones ilimitadas)
Configuración aplicada
Referencia: CCN-STIC-810 tabla de parámetros recomendados
cat >> /etc/ssh/sshd_config << EOF
# Hardening ENS
PermitRootLogin no
PasswordAuthentication no
X11Forwarding no
MaxAuthTries 3
MaxSessions 4
ClientAliveInterval 300
ClientAliveCountMax 2
LoginGraceTime 30
AllowTcpForwarding no
EOF
sshd -t && systemctl restart sshd
Justificación de parámetros (según CCN-STIC-810):
- MaxAuthTries 3: Limita intentos de autenticación (previene fuerza bruta)
- ClientAliveInterval 300 + ClientAliveCountMax 2: Cierra sesiones inactivas tras 10 minutos
- LoginGraceTime 30: Limita tiempo de login a 30 segundos
- AllowTcpForwarding no: Previene túneles SSH no autorizados
Cumplimiento ENS conseguido: mp.com.3, op.acc.5
Hallazgo medio 3: logs sin monitorización activa
Control ENS afectado: op.exp.8 (Registro de la actividad), op.exp.9 (Registro de gestión de incidentes), mp.com.4 (Detección de intrusiones).
Referencia normativa: CCN-STIC-807B apartado 5.1 (Auditoría y logs), Real Decreto 311/2022 Anexo II
Problema detectado
grep "Failed password" /var/log/auth.log | tail -20
Cientos de intentos sin bloqueo.
systemctl status auditd
# → inactive (dead)
Incumplimiento ENS:
- op.exp.8: No se registra adecuadamente la actividad de usuarios y administradores
- op.exp.9: No hay registro sistemático de eventos de seguridad
- mp.com.4: No hay sistema de prevención/detección de intrusiones
Corrección aplicada
Referencia: CCN-STIC-807B apartado 5.1, Real Decreto 311/2022 Anexo II tabla mp.com.4
1. Fail2ban (Prevención de intrusiones)
apt install fail2ban -y
cat > /etc/fail2ban/jail.local << EOF
[sshd]
enabled = true
port = ssh
maxretry = 3
bantime = 3600
findtime = 600
EOF
systemctl enable --now fail2ban
Parámetros ENS:
- maxretry = 3: Alineado con MaxAuthTries de SSH
- bantime = 3600: Bloqueo de 1 hora tras detección
- findtime = 600: Ventana de observación de 10 minutos
2. Auditd (Registro de eventos de seguridad)
systemctl enable --now auditd
cat > /etc/audit/rules.d/hardening.rules << EOF
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/sudoers -p wa -k sudoers
-w /etc/ssh/sshd_config -p wa -k sshd_config
-a always,exit -F arch=b64 -S execve -F euid=0 -k root_commands
EOF
augenrules --load
Justificación de reglas:
Monitorización de ficheros de identidad: cumple op.exp.8 (trazabilidad de cambios)
Registro de comandos root: cumple op.exp.9 (investigación forense post-incidente)
Cumplimiento ENS conseguido: op.exp.8, op.exp.9, mp.com.4
Hallazgo bajo: actualizaciones pendientes
Control ENS afectado: mp.sw.2 (Aceptación y puesta en producción)
Referencia normativa: CCN-STIC-619 apartado 2.3 (Gestión de actualizaciones)
Problema detectado
apt list --upgradeable 2>/dev/null
Paquetes críticos sin actualizar:
coreutils
nftables
snapd
binutils
Kernel pendiente de reinicio:
needs-restarting -r
Incumplimiento ENS:
- mp.sw.2: No se garantiza la aplicación de actualizaciones de seguridad en tiempo razonable
Corrección aplicada
- Referencia: CCN-STIC-619 apartado 2.3.2
Error GPG en repositorio externo:
# Corregir clave GPG de repositorio
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CLAVE_GPG
# Aplicar actualizaciones de seguridad
apt update && apt upgrade -y
# Programar actualizaciones automáticas de seguridad
apt install unattended-upgrades -y
dpkg-reconfigure -plow unattended-upgrades
Buena práctica: Reinicio del servidor programado en ventana de mantenimiento para aplicar actualizaciones de kernel.
Cumplimiento ENS conseguido: mp.sw.2
Resumen de hallazgos
| Hallazgo | Riesgo | Prioridad | Control ENS | Normativa CCN-STIC |
| Login root SSH habilitado | Crítico | Inmediata | op.acc.5, op.acc.6 | CCN-STIC-810 (3.2) |
| Autenticación por contraseña SSH | Crítico | Inmediata | op.acc.6 | CCN-STIC-810 (3.2) |
| Firewall inactivo/permisivo | Crítico | Inmediata | mp.com.2, mp.com.4 | CCN-STIC-807B (4.3) |
| MySQL expuesto | Crítico | Inmediata | mp.com.2 | CCN-STIC-807B (4.3.1) |
| Servicios innecesarios | Medio | Corto plazo | mp.eq.3, mp.si.2 | CCN-STIC-619 (3.4) |
| Auditd inactivo | Medio | Corto plazo | op.exp.8, op.exp.9 | CCN-STIC-807B (5.1) |
| Fail2ban ausente | Medio | Corto plazo | mp.com.4 | CCN-STIC-807B (5.1) |
| Hardening SSH incompleto | Medio | Corto plazo | mp.com.3, op.acc.5 | CCN-STIC-810 (3.3-3.5) |
| Actualizaciones pendientes | Medio | Corto plazo | mp.sw.2 | CCN-STIC-619 (2.3) |
| Puerto SSH por defecto | Bajo | Medio plazo | mp.com.3 | CCN-STIC-810 (3.7) |
| Repositorio GPG sin verificar | Bajo | Medio plazo | mp.sw.2 | CCN-STIC-619 (2.3.2) |
Buenas prácticas ya implementadas
- HTTPS en todos los servicios web (mp.com.1)
- Bases de datos aisladas en 127.0.0.1 en la mayoría de casos (mp.com.2)
- Algoritmos de hash robustos (SHA-512 / yescrypt) (op.acc.4)
- Herramientas de monitorización desplegadas (op.exp.10)
- Uso correcto de proxy inverso (mp.s.2)
- Sistemas en versiones con soporte activo (mp.sw.2)
Conclusión práctica
El principal riesgo no residía en una vulnerabilidad concreta, sino en un patrón evidente: exceso de exposición, ausencia de restricciones y un control de accesos insuficiente. Ninguno de los problemas presentaba complejidad técnica; todos derivaban de configuraciones inadecuadas o de la falta de revisión tras la instalación inicial. La lección es clara: un servidor recién desplegado no puede considerarse seguro. El hardening no es opcional en producción, especialmente en entornos sujetos al ENS.
Checklist mínimo antes de producción
Control de acceso (op.acc):
- Deshabilitar login root por SSH (op.acc.5)
- Deshabilitar autenticación por contraseña (op.acc.6)
- Configurar autenticación basada en clave pública (op.acc.6)
Protección de comunicaciones (mp.com):
- Activar firewall con política deny-all de entrada (mp.com.2, mp.com.4)
- Aplicar hardening SSH según CCN-STIC-810 (mp.com.3)
- Activar fail2ban (mp.com.4)
Protección de equipos (mp.eq, mp.si):
- Desactivar servicios innecesarios (mp.eq.3)
- Revisar configuración de seguridad (mp.si.2)
Registro y auditoría (op.exp):
- Activar auditd con reglas de monitorización (op.exp.8, op.exp.9)
- Configurar sincronización horaria UTC (op.exp.8)
Gestión de software (mp.sw):
- Aplicar actualizaciones de seguridad (mp.sw.2)
- Configurar actualizaciones automáticas (mp.sw.2)
Verificar integridad de repositorios (GPG) (mp.sw.2)
Protección de servicios (mp.s):
- Verificar que ninguna base de datos escucha en interfaces externas (mp.com.2)
- Configurar HTTPS en servicios web (mp.com.1)

