Introduccion: Security by default
La mayoria de distribuciones Linux siguen un modelo de permiso por defecto: todo esta permitido hasta que el administrador lo restringe. Este post invierte ese modelo — construimos un sistema que nace restringido y solo se abre lo estrictamente necesario.
Este post es la evolucion de nuestra implementacion de Kernel Hardening con KSPP. Partimos de aquella base y la llevamos al siguiente nivel: un Debian 13 (Trixie) con seguridad por defecto a nivel de kernel, syscalls, red, filesystem y procesos. Un sistema operativo que es seguro antes de instalar nada.
| Principio | Implementacion |
|---|---|
| Deny-all syscalls | Seccomp-BPF per-service via systemd (sshd excluido) |
| Deny-all red | nftables con policy DROP en input, output y forward |
| Deny-all filesystem | Blacklist modulos + particiones noexec |
| Deny-all modulos | Blacklist agresivo + module.sig_enforce |
| Deny-all capacidades | CapabilityBoundingSet minimo por servicio |
| Kernel inmutable | lockdown=confidentiality + auditd inmutable (-e 2) |
| Telemetria irrevocable | Envio en tiempo real a SIEM externo (Wazuh/Elastic/Splunk) |
El resultado es un Debian con restricciones de nivel militar, manteniendo la flexibilidad del ecosistema Debian para entornos productivos. Toda la guia es aplicable tanto en bare-metal (portatil, servidor fisico) como en cualquier hipervisor empresarial (VMware, Proxmox, Hyper-V, Xen, KVM). Toda la configuracion ha sido validada en un entorno real (Debian 13, kernel 6.12.88).
Arquitectura de defensa en profundidad
┌─────────────────────────────────────────────────────┐
│ HARDWARE / HIPERVISOR │
│ Deshabilitar: clipboard, USB passthrough, audio │
│ Aplicable a: VMware, Proxmox, Hyper-V, KVM, bare │
├─────────────────────────────────────────────────────┤
│ CAPA 1: KERNEL (KSPP + lockdown) │
│ - lockdown=confidentiality │
│ - module.sig_enforce=1, audit=1 │
│ - init_on_alloc/free, slab_nomerge, pti=on │
│ - Spectre/MDS/TSX mitigations enforced │
├─────────────────────────────────────────────────────┤
│ CAPA 2: SYSCTL (60+ parametros) │
│ - ptrace_scope=3, kptr_restrict=2 │
│ - kexec_load_disabled=1, io_uring_disabled=2 │
│ - IPv6 deshabilitado, ICMP bloqueado │
├─────────────────────────────────────────────────────┤
│ CAPA 3: SECCOMP-BPF (per-service) │
│ - SystemCallFilter por servicio via systemd │
│ - sshd/auditd excluidos (privilege separation) │
│ - fail2ban, cron, timesyncd: filtrado completo │
├─────────────────────────────────────────────────────┤
│ CAPA 4: RED (nftables deny-all) │
│ - Input: solo SSH desde red gestion │
│ - Output: solo DNS + apt + NTP + SIEM │
│ - Forward: DROP total │
│ - Rate-limiting en todas las reglas │
├─────────────────────────────────────────────────────┤
│ CAPA 5: FILESYSTEM │
│ - /tmp noexec,nosuid,nodev │
│ - /proc hidepid=2 │
│ - Modulos kernel blacklisted (USB, BT, wireless) │
├─────────────────────────────────────────────────────┤
│ CAPA 6: AUDITORIA (auditd inmutable) │
│ - Reglas MITRE ATT&CK (-e 2 inmutable) │
│ - AIDE/Wazuh syscheck cada 15 min │
│ - Fail2ban + logging centralizado │
├─────────────────────────────────────────────────────┤
│ CAPA 7: TELEMETRIA (SIEM + EDR + DFIR) │
│ - Wazuh Agent (FIM + rootkit + log collection) │
│ - Velociraptor Client (threat hunting en vivo) │
│ - Envio a SIEM externo (irrevocable localmente) │
└─────────────────────────────────────────────────────┘Antes de hardening: Framework de Decision
Cada control tiene un coste: complejidad, rendimiento, operabilidad. Aplicar "maximo hardening" por defecto es tan peligroso como no aplicar ninguno. Antes de implementar cualquier restriccion, responde estas 4 preguntas:
1. Que estamos protegiendo (Asset Criticality)
| Nivel | Ejemplo | Implicacion |
|---|---|---|
| Critico | Bastion de produccion, sistemas de pago | Hardening maximo, incluso con impacto operacional |
| Alto | Servidores de aplicacion con datos sensibles | Hardening adaptado, priorizando deteccion |
| Medio | Entornos de staging, herramientas internas | Hardening base + monitoreo |
| Bajo | Laboratorios, VMs efimeras | Hardening minimo, foco en aislamiento de red |
2. Quien podria atacarnos (Threat Modeling)
| Actor | Motivacion | Controles prioritarios |
|---|---|---|
| Script kiddie / Botnet | Oportunismo, cryptomining | Firewall estricto, actualizaciones automaticas |
| Competidor / Espionaje | Robo de IP | Telemetria avanzada, FIM, auditoria inmutable |
| APT / Estado-nacion | Persistencia en infraestructura critica | Kernel hardening, remote attestation, Zero Trust |
| Insider malicioso | Sabotaje, exfiltracion | Segregacion de duties, logging de sesiones, MFA |
3. Que podemos permitirnos perder (Risk Tolerance)
- Es aceptable un downtime de 15 minutos para parchear el kernel?
- Podemos bloquear una syscall y romper una aplicacion?
- Tenemos capacidad de recuperacion si el hardening causa un incidente?
Regla de oro: Si no puedes responder estas preguntas, no estas listo para hardening avanzado. Empieza por los controles de deteccion.
4. Como sabremos si funciona (Success Metrics)
Define metricas antes de implementar:
- Tiempo medio de deteccion de anomalias (MTTD) — objetivo: < 5 min
- Falsos positivos por dia — objetivo: < 5 por bastion
- Impacto en rendimiento — objetivo: < 10% degradacion
- Tiempo de recuperacion ante incidente de configuracion (MTTR) — objetivo: < 15 min
Matriz de Decision: Que aplicar segun el servicio
No todos los servidores necesitan el mismo nivel de restriccion. Esta matriz guia la decision:
| Control | Bastion | Web App | Base de Datos | Container Host |
|---|---|---|---|---|
lockdown=confidentiality | Si | Integrity | Integrity | Integrity |
io_uring_disabled=2 | Si | Si | No (*) | Si |
user.max_user_namespaces=0 | Si | Si | Si | No (**) |
| Seccomp per-service | Agresivo | Adaptado | Minimo | Container-native |
| nftables output deny-all | Si | Allowlist FQDN | Allowlist DB peers | Allowlist registry |
ptrace_scope=3 | Si | Scope=2 | Scope=2 | Scope=2 |
| FIM cada 15 min | Si | Cada 1h | Solo configs | Solo binarios |
| MFA obligatorio | Si | Si | Si | Solo admin |
(*) PostgreSQL 14+ usa io_uring; deshabilitarlo = -30% throughput en escritura intensiva.
(**) Docker/Podman requieren user namespaces para aislamiento de contenedores.
Filosofia por tipo de servicio:
- Bastion: "Deny-all por defecto" — superficie minima, acceso humano directo
- Web/DB: "Detect-and-respond" — permitir funcionalidad, detectar anomalias
- Containers: "Isolate-by-design" — confiar en aislamiento de runtime, harden el host
Todo lo que sigue en este post aplica el perfil Bastion (maximo nivel de restriccion). Adapta segun tu matriz de riesgo.
Parametros especificos por perfil
| Control | Bastion | Servidor Web | Base de datos | Container host |
|---|---|---|---|---|
user.max_user_namespaces | 0 | 0 | 0 | 1024 (Docker necesita) |
lockdown | confidentiality | integrity | integrity | integrity |
slub_debug=FZP | Si | No (impacto rendimiento ~5-15%) | No | No |
init_on_free=1 | Si | Si | No (impacto I/O) | Si |
io_uring_disabled | 2 | 2 | 0 (PostgreSQL usa io_uring) | 2 |
module.sig_enforce | 1 | 1 | 0 (DKMS/drivers) | 0 (overlay fs) |
| nftables output | Solo DNS/APT/NTP/SIEM | +HTTP/HTTPS a backends | +puertos DB | +registry |
| Seccomp per-service | Si (agresivo) | Si (adaptado) | No (rendimiento) | Si (containerd) |
Impacto real en rendimiento:
slub_debug=FZP+init_on_free=1+page_poison=1pueden reducir throughput entre 5-15% en cargas intensivas de memoria. En un bastion es irrelevante. En un servidor de base de datos con miles de transacciones/segundo, es inaceptable. Medir siempre antes de desplegar.
Incompatibilidades conocidas con herramientas enterprise
| Herramienta | Control conflictivo | Solucion |
|---|---|---|
| Docker/Podman | user.max_user_namespaces=0 | Subir a 1024+ en container hosts |
| DKMS / drivers propietarios (NVIDIA, etc.) | module.sig_enforce=1 | Deshabilitar o firmar modulos con MOK |
| Herramientas eBPF (Cilium, Falco, bpftrace) | lockdown=confidentiality | Usar lockdown=integrity o excluir |
| Agentes de backup (Veeam, NetBackup) | Seccomp @system-service | Drop-in especifico sin SystemCallFilter |
| Monitoring con perf/flamegraph | kernel.yama.ptrace_scope=3 | Reducir a 2 en entornos de profiling |
| Antivirus enterprise (CrowdStrike, SentinelOne) | lockdown=confidentiality + module.sig | Requiere excepciones kernel o firma |
Fase 1: Instalacion base minimalista
Requisitos de hardware/VM
- CPU: 2 cores minimo
- RAM: 2GB minimo, 4GB recomendado
- Disco: 20GB minimo (con LUKS el cifrado consume overhead)
- Red: Interfaz dedicada para gestion (separada del trafico de produccion)
Instalacion de Debian 13 (Trixie)
Independientemente del hipervisor o hardware fisico, la instalacion debe ser minimalista. Usar la ISO netinst y en el paso de seleccion de software (tasksel) marcar unicamente:
- "Standard system utilities"
Desmarcar todo lo demas: escritorio, servidor web, servidor SSH (lo instalamos despues hardeneado), servidor de impresion, etc. El objetivo es partir de un sistema con la superficie de ataque mas reducida posible.
💡 Consideracion enterprise: En entornos con decenas de bastiones, esta instalacion se automatiza con Packer + Ansible. Ver seccion "Escalando el hardening".
Consideraciones por hipervisor
En entornos virtualizados, reducir la superficie de ataque del propio hipervisor:
| Hipervisor | Acciones recomendadas |
|---|---|
| VMware ESXi/Workstation | Deshabilitar: copy-paste, drag-drop, shared folders, 3D acceleration. Activar Secure Boot en la VM |
| Proxmox/KVM | Usar machine type q35, deshabilitar USB passthrough, tablet device y audio. Activar cpu host |
| Hyper-V | Deshabilitar Integration Services innecesarios, activar Secure Boot, usar Generation 2 |
| Xen | Usar modo HVM con IOMMU, deshabilitar PV drivers innecesarios |
| Bare-metal | BIOS/UEFI: deshabilitar USB boot, activar Secure Boot, password en BIOS, deshabilitar Thunderbolt/FireWire si no se usan |
Hardening post-instalacion inicial
Una vez con el sistema base instalado y acceso como root:
# Actualizar y limpiar
apt update && apt full-upgrade -y && apt autoremove --purge -y
# Instalar herramientas de hardening
apt install -y \
apparmor apparmor-utils apparmor-profiles apparmor-profiles-extra \
libpam-apparmor \
aide aide-common \
auditd audispd-plugins \
fail2ban \
nftables \
haveged rng-tools5 \
lynis \
unattended-upgrades \
needrestart \
debsums \
systemd-coredump \
cryptsetup-bin \
libseccomp-dev libseccomp2 seccomp \
openssh-server \
jq curl gnupg2
# Eliminar paquetes innecesarios (minimalismo)
apt purge -y \
telnet rsh-client xinetd nis tftp \
avahi-daemon cups 2>/dev/null || true
apt autoremove --purge -yCrear usuario de administracion
El acceso al bastion se hace exclusivamente por SSH con clave publica. Sin password login, sin acceso root directo:
# Crear grupo y usuario
groupadd bastion-ssh
useradd -m -s /bin/bash -G bastion-ssh,sudo bastion-admin
# Configurar SSH key
mkdir -p /home/bastion-admin/.ssh
chmod 700 /home/bastion-admin/.ssh
# Copiar tu clave publica (desde tu maquina local)
# echo "ssh-ed25519 AAAA..." > /home/bastion-admin/.ssh/authorized_keys
chmod 600 /home/bastion-admin/.ssh/authorized_keys
chown -R bastion-admin:bastion-admin /home/bastion-admin/.ssh
# Bloquear password de root
passwd -l rootPolitica de contraseñas (PAM + pwquality)
Aunque el acceso SSH es exclusivamente por clave publica, las contraseñas locales siguen siendo relevantes para sudo, consola fisica y escalada de privilegios. Una politica robusta previene contraseñas debiles que un atacante podria explotar tras comprometer una sesion:
# /etc/login.defs - politica de ciclo de vida
PASS_MAX_DAYS 90 # Rotacion cada 90 dias
PASS_MIN_DAYS 7 # Minimo 7 dias entre cambios (evitar bypass de historial)
PASS_WARN_AGE 14 # Aviso 14 dias antes de expiracion
PASS_MIN_LEN 14 # Longitud minima 14 caracteres# /etc/security/pwquality.conf - complejidad
minlen = 14 # Longitud minima
dcredit = -1 # Al menos 1 digito
ucredit = -1 # Al menos 1 mayuscula
ocredit = -1 # Al menos 1 caracter especial
lcredit = -1 # Al menos 1 minuscula
maxrepeat = 3 # Maximo 3 caracteres consecutivos iguales
maxclassrepeat = 4 # Maximo 4 consecutivos de misma clase
usercheck = 1 # No puede contener el username
dictcheck = 1 # Verificar contra diccionario
minclass = 4 # Minimo 4 clases de caracteres
difok = 8 # Minimo 8 caracteres diferentes al anterior
palindrome = 1 # Rechazar palindromos# PAM: historial + bloqueo de cuentas
# /etc/pam.d/common-password (añadir remember=12)
password requisite pam_pwquality.so retry=3
password [success=1 default=ignore] pam_unix.so obscure use_authtok try_first_pass yescrypt remember=12
# Bloqueo tras intentos fallidos (pam_faillock)
# /etc/pam.d/common-auth
auth required pam_faillock.so preauth silent audit deny=5 unlock_time=900 fail_interval=900
auth [default=die] pam_faillock.so authfail audit deny=5 unlock_time=900 fail_interval=900
auth sufficient pam_faillock.so authsucc audit deny=5 unlock_time=900 fail_interval=900Esto establece: contraseñas de minimo 14 caracteres con complejidad obligatoria, rotacion cada 90 dias, historial de 12 passwords (no reutilizables), y bloqueo de cuenta durante 15 minutos tras 5 intentos fallidos.
Sudo hardening
Sudo es el vector mas comun de escalada de privilegios. La configuracion por defecto es demasiado permisiva:
# /etc/sudoers.d/00-security
Defaults requiretty # Solo desde terminal real (no scripts remotos)
Defaults use_pty # Forzar pseudo-terminal
Defaults logfile="/var/log/sudo.log"
Defaults log_input, log_output # Grabar TODA la sesion sudo
Defaults iolog_dir="/var/log/sudo-io"
Defaults passwd_timeout=1 # 1 minuto para introducir password
Defaults timestamp_timeout=5 # Cache de 5 min (no indefinido)
Defaults passwd_tries=3 # Max 3 intentos
Defaults env_reset # Limpiar variables de entorno
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
Defaults !visiblepw # No mostrar password en pantalla
Defaults always_set_home # Siempre setear HOME
Defaults log_denied # Logear intentos denegados# /etc/sudoers.d/bastion - permisos granulares (principio de minimo privilegio)
bastion-admin ALL=(root) NOPASSWD: /usr/local/bin/security-test-suite.sh
bastion-admin ALL=(root) NOPASSWD: /usr/local/bin/bastion-emergency.sh audit-mode
bastion-admin ALL=(root) NOPASSWD: /usr/bin/journalctl
bastion-admin ALL=(root) NOPASSWD: /usr/bin/systemctl status *
bastion-admin ALL=(root) NOPASSWD: /usr/sbin/aide --check
bastion-admin ALL=(root) NOPASSWD: /usr/bin/lynis audit system *
bastion-admin ALL=(root) NOPASSWD: /usr/bin/aa-statusNota sobre
requiretty: Este parametro impide ejecutar sudo desde scripts no interactivos (como cron o pipelines CI/CD). En un bastion esto es deseable — todo acceso privilegiado debe ser interactivo y auditable. En otros tipos de servidor, puede requerir excepciones.
Autenticacion multifactor (2FA/MFA)
La clave publica SSH protege contra ataques de fuerza bruta, pero si un atacante roba la clave privada tiene acceso completo. El segundo factor (TOTP) mitiga este riesgo:
# Instalar
apt install -y libpam-google-authenticator
# Configurar para SSH: publickey + TOTP obligatorio
# /etc/ssh/sshd_config.d/hardening.conf (añadir)
AuthenticationMethods publickey,keyboard-interactive
# Configurar PAM para SSH
# /etc/pam.d/sshd (añadir al final)
auth required pam_google_authenticator.so nullok
# Habilitar challenge-response en SSH
# /etc/ssh/sshd_config.d/hardening.conf (añadir)
KbdInteractiveAuthentication yesCada usuario configura su TOTP:
# El usuario ejecuta (NO root):
google-authenticator -t -d -f -r 3 -R 30 -w 3 -Q UTF8
# Esto genera:
# - Un QR code para escanear con Google Authenticator/Authy/FreeOTP
# - Codigos de emergencia (guardar offline)
# - Fichero ~/.google_authenticator
# Opciones recomendadas:
# -t = Time-based (TOTP)
# -d = No permitir reutilizar token
# -f = Forzar escritura del fichero
# -r 3 -R 30 = Rate limiting: 3 intentos cada 30 seg
# -w 3 = Window de 3 tokens (90 seg de margen)
nulloken PAM: Permite login sin 2FA si el usuario aun no ha configurado~/.google_authenticator. Esto es util durante la transicion. Una vez todos los usuarios tienen 2FA, cambiar aauth required pam_google_authenticator.so(sin nullok) para hacerlo obligatorio.
Flujo de autenticacion resultante:
- Cliente presenta clave publica SSH → verificada contra
authorized_keys - Servidor solicita TOTP code via keyboard-interactive
- Usuario introduce codigo de 6 digitos de su app
- Acceso concedido solo si ambos factores son validos
Integracion con gestores de identidad empresarial
En entornos corporativos, las cuentas locales no escalan. La integracion con un IdP centralizado permite:
- Gestion centralizada de accesos (altas/bajas automaticas)
- Politicas de password unificadas
- MFA gestionado corporativamente
- Auditoria centralizada de accesos
FreeIPA / Red Hat IdM
# Instalar cliente FreeIPA
apt install -y freeipa-client
# Enrollar el bastion en el dominio
ipa-client-install \
--server=ipa.example.com \
--domain=example.com \
--realm=EXAMPLE.COM \
--mkhomedir \
--ssh-trust-dns \
--force-ntpd
# Configurar SSSD para controlar acceso por grupo
cat >> /etc/sssd/sssd.conf << 'EOF'
[domain/example.com]
access_provider = ipa
ipa_hbac_refresh = 60
# Solo permitir grupo especifico
simple_allow_groups = bastion-admins, security-team
EOF
# HBAC (Host-Based Access Control) en FreeIPA
# Crear regla que solo permita acceso al bastion desde el grupo autorizado
# ipa hbacrule-add bastion-access
# ipa hbacrule-add-host bastion-access --hosts=bastion.example.com
# ipa hbacrule-add-user bastion-access --groups=bastion-admins
# Sudo centralizado via FreeIPA
# ipa sudorule-add bastion-sudo
# ipa sudorule-add-host bastion-sudo --hosts=bastion.example.com
# ipa sudorule-add-user bastion-sudo --groups=bastion-admins
# ipa sudorule-add-runasuser bastion-sudo --users=root
# ipa sudorule-add-allow-command bastion-sudo --sudocmds="/usr/bin/journalctl"Microsoft Entra ID (Azure AD) + SSSD
# Para entornos Microsoft, integrar via SSSD + Kerberos
apt install -y sssd sssd-tools sssd-krb5 krb5-user realmd adcli
# Unir al dominio
realm join --user=admin EXAMPLE.ONMICROSOFT.COM
# /etc/sssd/sssd.conf
[sssd]
domains = example.onmicrosoft.com
services = nss, pam, ssh, sudo
[domain/example.onmicrosoft.com]
id_provider = ad
auth_provider = ad
access_provider = ad
ad_access_filter = memberOf=CN=Bastion-Admins,OU=Security,DC=example,DC=com
sudo_provider = ad
ldap_sudo_search_base = OU=Sudoers,DC=example,DC=com
# MFA via Entra ID Conditional Access
# Se gestiona desde Azure Portal:
# - Conditional Access Policy → Require MFA for SSH access
# - Device compliance required
# - Location-based restrictionsLDAP generico (OpenLDAP, 389ds)
# /etc/sssd/sssd.conf para LDAP generico
[domain/ldap.example.com]
id_provider = ldap
auth_provider = ldap
ldap_uri = ldaps://ldap.example.com
ldap_search_base = dc=example,dc=com
ldap_tls_reqcert = demand
ldap_tls_cacert = /etc/ssl/certs/ca-ldap.pem
access_provider = simple
simple_allow_groups = bastion-sshSSH Certificate Authority (sin passwords, sin keys estaticas)
La solucion mas segura elimina authorized_keys por completo. Un CA firma certificados SSH de corta duracion:
# En sshd_config:
TrustedUserCAKeys /etc/ssh/ca-user.pub # CA que firma certificados de usuario
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
# El usuario obtiene un certificado temporal (via Vault, Teleport, etc.)
# $ vault write ssh/sign/bastion public_key=@~/.ssh/id_ed25519.pub
# Genera un certificado valido por 8 horas con principals especificos
# Ventajas:
# - No hay authorized_keys que gestionar
# - Certificados expiran automaticamente
# - Revocacion inmediata via CRL
# - Auditoria de quien firmo que y cuandoFase 2: KSPP completo - Parametros de kernel
El bootloader se configura con todos los parametros KSPP mas extensiones adicionales que van mas alla de las recomendaciones estandar:
💡 Consideracion enterprise: Los parametros KSPP varian segun perfil de servicio (bastion vs webserver vs database). Ver Matriz de Decision.
GRUB_CMDLINE_LINUX_DEFAULT="quiet \
slab_nomerge \
slub_debug=FZP \
page_alloc.shuffle=1 \
page_poison=1 \
vsyscall=none \
init_on_alloc=1 \
init_on_free=1 \
pti=on \
randomize_kstack_offset=on \
spectre_v2=on \
spec_store_bypass_disable=seccomp \
l1tf=full,force \
mds=full,nosmt \
tsx=off \
tsx_async_abort=full,nosmt \
mmio_stale_data=full,nosmt \
retbleed=auto,nosmt \
kvm.nx_huge_pages=force \
lockdown=confidentiality \
module.sig_enforce=1 \
oops=panic \
debugfs=off \
lsm=landlock,lockdown,yama,integrity,apparmor,bpf \
extra_latent_entropy \
iommu=force \
intel_iommu=on \
efi=disable_early_pci_dma \
audit=1 \
audit_backlog_limit=8192"Explicacion de parametros clave avanzados
| Parametro | Proposito | Novedad |
|---|---|---|
slub_debug=FZP | Free/Zero/Poison checking en allocator | Detecta use-after-free |
randomize_kstack_offset=on | Aleatoriza offset del stack del kernel | Anti-ROP en kernel |
oops=panic | Panic inmediato en oops | Evita estado inconsistente |
lsm=landlock,lockdown,yama,integrity,apparmor,bpf | Stack LSM completo | Multiples capas MAC |
iommu=force | Forzar IOMMU para DMA | Anti-DMA attacks |
efi=disable_early_pci_dma | Bloquear DMA pre-boot | Evil-maid protection |
mmio_stale_data=full,nosmt | Mitigacion MMIO | Nuevas vulns CPU |
retbleed=auto,nosmt | Mitigacion Retbleed | AMD/Intel 2022+ |
Fase 3: Sysctl - 60+ parametros de hardening
El fichero /etc/sysctl.d/99-bastion-hardening.conf implementa restricciones que van significativamente mas alla de CIS Benchmark:
# Proteccion de informacion del kernel
kernel.kptr_restrict = 2 # Ocultar punteros kernel completamente
kernel.dmesg_restrict = 1 # Solo root puede leer dmesg
kernel.yama.ptrace_scope = 3 # No ptrace NUNCA (ni root)
kernel.kexec_load_disabled = 1 # Imposible cargar kernel alternativo
kernel.io_uring_disabled = 2 # Deshabilitar io_uring (superficie de ataque enorme)
kernel.sysrq = 0 # Sin magic sysrq
# ASLR maximo
vm.mmap_rnd_bits = 32 # 32 bits de entropia en mmap
vm.mmap_min_addr = 65536 # Prevenir null-pointer exploits
# Core dumps eliminados
fs.suid_dumpable = 0
kernel.core_pattern = |/bin/false
# Red: todo bloqueado por defecto
net.ipv4.icmp_echo_ignore_all = 1 # No responder pings
net.ipv4.tcp_timestamps = 0 # Sin timestamps (fingerprinting)
net.ipv4.tcp_sack = 0 # Deshabilitar SACK (CVE-2019-11477)
net.ipv6.conf.all.disable_ipv6 = 1 # IPv6 innecesario = deshabilitado
# TTY hardening
dev.tty.ldisc_autoload = 0 # No autocargar line disciplines
user.max_user_namespaces = 0 # Sin user namespaces (escape de containers)Diferencia con CIS Benchmark
CIS recomienda kernel.yama.ptrace_scope = 1. Nosotros usamos 3 (nadie puede usar ptrace, ni siquiera root). En un bastion no hay razon para debugging en produccion.
CIS no menciona kernel.io_uring_disabled. io_uring ha sido fuente de multiples CVEs criticos (CVE-2022-29582, CVE-2023-2598) y en un bastion no se necesita.
Fase 4: Seccomp-BPF System-Wide - El corazon del hardening
Concepto: Restriccion de syscalls por servicio
En Linux podemos restringir las syscalls que un proceso puede usar combinando:
- Seccomp-BPF → filtrado de llamadas al sistema
- Systemd sandboxing (ProtectHome, PrivateTmp) → aislamiento de filesystem
- AppArmor → refuerzo adicional de paths y capabilities
💡 Consideracion enterprise: El generador de perfiles Seccomp permite crear perfiles adaptados por servicio, exportables a Ansible para despliegue masivo.
Generador de perfiles Seccomp
Creamos un generador que produce perfiles Seccomp organizados por niveles de privilegio:
#!/bin/bash
# seccomp-profile-generator.sh <servicio> <nivel>
# Niveles: minimal | standard | network | permissive
# STDIO: operaciones basicas de I/O y memoria
STDIO_SYSCALLS=(
"brk" "mmap" "mprotect" "munmap" "mremap" "madvise"
"clone" "exit" "exit_group" "getpid" "gettid"
"rt_sigaction" "rt_sigprocmask" "rt_sigreturn"
"futex" "nanosleep" "clock_gettime"
"close" "read" "write" "lseek" "fstat"
"arch_prctl" "prctl" "getrandom"
)
# RPATH: lectura de filesystem
RPATH_SYSCALLS=(
"openat" "access" "faccessat2" "getcwd"
"readlink" "stat" "getdents64"
)
# INET: operaciones de red
INET_SYSCALLS=(
"socket" "bind" "connect" "listen" "accept4"
"sendto" "recvfrom" "epoll_create1" "epoll_ctl" "epoll_wait"
)
# SIEMPRE DENEGADAS (KILL inmediato)
ALWAYS_DENY=(
"kexec_load" "init_module" "finit_module" "delete_module"
"bpf" "perf_event_open" "userfaultfd" "ptrace"
"mount" "umount2" "pivot_root" "chroot"
"io_uring_setup" "io_uring_enter" "io_uring_register"
)El nivel minimal solo permite stdio + lectura de archivos. El nivel network anade sockets. Un servicio como auditd usa minimal, SSH usa network.
Aplicacion via systemd (por servicio)
Leccion aprendida en produccion: Un drop-in global en
/etc/systemd/system/service.d/parece atractivo pero rompe servicios criticos como sshd, que necesitasetuid/setgid/sys_chrootpara su privilege separation interna. ElSystemCallFilteragresivo causa SIGSYS (signal 31) y mata el proceso inmediatamente. Lo mismo ocurre conProtectSystem=strictque monta/como read-only e impide operaciones basicas.
La estrategia correcta es aplicar drop-ins individuales por servicio, excluyendo explicitamente sshd y auditd que requieren privilegios especiales:
# /etc/systemd/system/fail2ban.service.d/hardening.conf
# Aplicar tambien a: cron, systemd-timesyncd, y otros servicios no-privilegiados
[Service]
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallFilter=~@mount @swap @reboot @raw-io @debug @obsolete
SystemCallFilter=~io_uring_setup io_uring_enter io_uring_register
SystemCallFilter=~kexec_load kexec_file_load
SystemCallErrorNumber=EPERM
NoNewPrivileges=yes
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
MemoryDenyWriteExecute=true
RestrictNamespaces=true
LockPersonality=true
ProtectClock=true
ProtectHostname=truePara sshd usamos un drop-in mas conservador — sandboxing de filesystem sin filtrado de syscalls:
# /etc/systemd/system/ssh.service.d/hardening.conf
[Service]
ProtectHome=read-only
PrivateTmp=true
PrivateDevices=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
RestrictNamespaces=true
RestrictAddressFamilies=AF_INET AF_UNIX
NoNewPrivileges=no
LockPersonality=true
ProtectClock=true
ProtectHostname=true
SystemCallArchitectures=nativeNota critica: No incluir
SystemCallFilter,MemoryDenyWriteExecute=trueniProtectSystem=stricten el drop-in de sshd. OpenSSH 9.8+ separasshd(listener) desshd-session(sesion), y la sesion necesita syscalls privilegiadas para PAM, PTY allocation y privilege separation. Forzar Seccomp aqui causa core-dumps inmediatos.
Por que per-service y no global
Un drop-in global en service.d/ pareceria mas seguro (deny-all), pero en la practica:
- Rompe servicios criticos: sshd, auditd, y systemd internals necesitan
@privileged - Irrecuperable remotamente: Si sshd muere por SIGSYS, solo puedes recuperar via consola fisica
- Falsa seguridad: El drop-in global se hereda pero no se puede "restar" — solo añadir excepciones que a menudo anulan todo el filtro
El enfoque correcto es:
- Drop-ins individuales para servicios que no necesitan privilegios (fail2ban, cron, timesyncd)
- Sandboxing conservador para sshd (filesystem + namespaces, sin syscall filter)
- AppArmor como capa de refuerzo para paths y capabilities (ver nota sobre sshd abajo)
- Auditd sin sandboxing de syscalls — necesita acceso total al netlink audit socket
Fase 5: Firewall nftables - Deny-all bidireccional
A diferencia de configuraciones tipicas que solo filtran INPUT, nuestro bastion controla tambien el trafico de salida:
💡 Consideracion enterprise: En flotas grandes, las reglas nftables se generan desde templates Jinja2 con variables por entorno (IPs de gestion, rangos SIEM).
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
iif "lo" accept
ct state established,related accept
ct state invalid drop
# Solo SSH desde red de gestion, rate-limited
tcp dport 22 ip saddr 192.168.1.0/24 \
ct state new limit rate 10/minute burst 20 packets accept
# Log + drop todo lo demas
limit rate 10/minute log prefix "NFT-DROP: " level warn
counter drop
}
chain output {
type filter hook output priority 0; policy drop;
oif "lo" accept
ct state established,related accept
# Solo DNS, apt y NTP permitidos
tcp dport 53 accept
udp dport 53 accept
tcp dport { 80, 443 } accept
udp dport 123 accept
# Todo lo demas logueado y dropeado
limit rate 5/minute log prefix "NFT-OUT-DROP: " level warn
counter drop
}
chain forward {
type filter hook forward priority 0; policy drop;
counter drop
}
}Esto significa que si un atacante compromete un servicio, no puede establecer conexiones salientes (reverse shells, exfiltracion) excepto a puertos muy especificos.
Fase 6: Auditoria con mapeo MITRE ATT&CK
Requisito critico: El kernel Debian 13 tiene
CONFIG_AUDIT=ypero audit se inicializa como disabled por defecto. Es obligatorio añadiraudit=1 audit_backlog_limit=8192a los parametros de GRUB. Sin esto, auditctl no puede abrir el socket netlink y todas las reglas fallan silenciosamente.
Las reglas de auditd usan tags que mapean directamente a tecnicas MITRE ATT&CK para correlacion inmediata con SIEMs:
# /etc/audit/rules.d/bastion.rules
-D
-b 8192
-f 2
## CREDENTIAL ACCESS (T1003)
-w /etc/shadow -p wa -k T1003_credential_access
-w /etc/gshadow -p wa -k T1003_credential_access
-w /etc/passwd -p wa -k T1003_credential_access
## PERSISTENCE (T1547/T1053/T1543)
-a always,exit -F arch=b64 -S init_module -S finit_module -S delete_module -k T1547_kernel_module
-w /etc/crontab -p wa -k T1053_cron_persistence
-w /etc/cron.d/ -p wa -k T1053_cron_persistence
-w /var/spool/cron/ -p wa -k T1053_cron_persistence
-w /etc/systemd/system/ -p wa -k T1543_systemd_persistence
-w /usr/lib/systemd/system/ -p wa -k T1543_systemd_persistence
## PRIVILEGE ESCALATION (T1548)
-a always,exit -F arch=b64 -S setuid -S setgid -S setreuid -S setregid -k T1548_privilege_change
-w /etc/sudoers -p wa -k T1548_sudo_modify
-w /etc/sudoers.d/ -p wa -k T1548_sudo_modify
-a always,exit -F arch=b64 -S execve -F euid=0 -F auid!=0 -F auid!=-1 -k T1548_priv_exec
## DEFENSE EVASION (T1562/T1070)
-w /etc/apparmor.d/ -p wa -k T1562_modify_apparmor
-w /usr/sbin/auditctl -p x -k T1562_tamper_audit
-w /etc/audit/ -p wa -k T1562_tamper_audit
-w /var/log/ -p wa -k T1070_log_tampering
## LATERAL MOVEMENT (T1021)
-w /etc/ssh/sshd_config -p wa -k T1021_ssh_config
-w /etc/ssh/sshd_config.d/ -p wa -k T1021_ssh_config
## EXECUTION (T1059)
-a always,exit -F arch=b64 -S execve -k T1059_execution
## REGLAS INMUTABLES tras boot (requiere reinicio para modificar)
-e 2El flag -e 2 es critico: una vez cargadas las reglas, no se pueden modificar sin reiniciar. Un atacante que gane root no puede silenciar la auditoria.
Fase 7: AIDE con verificacion en tiempo real
AIDE se ejecuta cada 15 minutos via timer de systemd y alerta inmediatamente si detecta cambios:
# Timer: cada 15 min con jitter de 2 min
[Timer]
OnBootSec=10min
OnUnitActiveSec=15min
RandomizedDelaySec=2min
Persistent=trueLos atributos monitorizados incluyen SHA-256 + SHA-512 para archivos criticos de seguridad, haciendo extremadamente dificil crear colisiones:
SecurityFiles = p+u+g+s+b+m+c+sha256+sha512
/etc/apparmor.d SecurityFiles
/etc/seccomp SecurityFiles
/etc/ssh/sshd_config SecurityFiles
/boot BinlibFase 8: Telemetria centralizada - SIEM, EDR y Threat Hunting
Un bastion sin telemetria centralizada es un punto ciego. Las reglas de auditd y AIDE generan alertas locales, pero si el atacante tiene root puede manipular logs locales (a pesar de -e 2). La solucion es exfiltrar telemetria en tiempo real a un sistema externo.
💡 Consideracion enterprise: La eleccion de SIEM depende de tu stack existente. La seccion de telemetria incluye una matriz de comparacion para ayudar a decidir.
Arquitectura de telemetria
┌─────────────────────────┐
│ BASTION │
│ auditd → audisp-remote ─────┐
│ Wazuh Agent ─────────────────┼──→ SIEM (Wazuh/Elastic/Splunk)
│ Velociraptor Client ─────────┼──→ Velociraptor Server (DFIR)
│ journald → remote ───────────┘
└─────────────────────────┘Opcion 1: Wazuh Agent
Wazuh actua como EDR + SIEM agent en un solo componente. Monitoriza integridad de ficheros (reemplaza AIDE), recoge logs de auditd, detecta rootkits y envia todo al manager:
# Instalar Wazuh Agent
curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | gpg --dearmor -o /usr/share/keyrings/wazuh.gpg
echo "deb [signed-by=/usr/share/keyrings/wazuh.gpg] https://packages.wazuh.com/4.x/apt/ stable main" > /etc/apt/sources.list.d/wazuh.list
apt update && apt install -y wazuh-agent
# Configurar manager
cat > /var/ossec/etc/ossec.conf << 'EOF'
<ossec_config>
<client>
<server>
<address>WAZUH_MANAGER_IP</address>
<port>1514</port>
<protocol>tcp</protocol>
</server>
<enrollment>
<enabled>yes</enabled>
<agent_name>bastion-prod</agent_name>
<groups>linux,hardened,bastion</groups>
</enrollment>
</client>
<!-- Monitorizar logs de auditd -->
<localfile>
<log_format>audit</log_format>
<location>/var/log/audit/audit.log</location>
</localfile>
<!-- Integridad de ficheros criticos (syscheck) -->
<syscheck>
<frequency>900</frequency>
<directories check_all="yes" realtime="yes">/etc/ssh,/etc/apparmor.d,/etc/audit,/etc/nftables.conf,/etc/sysctl.d</directories>
<directories check_all="yes">/boot,/usr/sbin/sshd,/usr/sbin/auditd</directories>
</syscheck>
<!-- Deteccion de rootkits -->
<rootcheck>
<frequency>3600</frequency>
</rootcheck>
</ossec_config>
EOF
systemctl enable wazuh-agent
systemctl start wazuh-agentOpcion 2: Elastic Agent (Elastic SIEM)
# Descargar e instalar Elastic Agent
curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-8.x-linux-x86_64.tar.gz
tar xzf elastic-agent-*.tar.gz
cd elastic-agent-*
# Enrollar con Fleet Server
./elastic-agent install \
--url=https://FLEET_SERVER:8220 \
--enrollment-token=TOKEN \
--insecureConfigurar en Kibana la integracion Auditd Manager + System para recoger automaticamente los eventos de auditd con los tags MITRE ATT&CK.
Opcion 3: Splunk Universal Forwarder
# Instalar forwarder
dpkg -i splunkforwarder-*.deb
# Configurar outputs
cat > /opt/splunkforwarder/etc/system/local/outputs.conf << 'EOF'
[tcpout]
defaultGroup = splunk_indexers
[tcpout:splunk_indexers]
server = SPLUNK_INDEXER:9997
sslCertPath = /opt/splunkforwarder/etc/certs/server.pem
sslPassword = ********
sslVerifyServerCert = true
EOF
# Monitorizar audit log
cat > /opt/splunkforwarder/etc/system/local/inputs.conf << 'EOF'
[monitor:///var/log/audit/audit.log]
sourcetype = linux:audit
index = security
[monitor:///var/log/auth.log]
sourcetype = linux_secure
index = security
EOFOpcion 4: QRadar (envio via syslog/LEEF)
# audisp-remote para envio de audit logs via syslog
cat > /etc/audit/plugins.d/syslog.conf << 'EOF'
active = yes
direction = out
path = /sbin/audisp-syslog
type = always
args = LOG_LOCAL6
format = string
EOF
# rsyslog para enviar a QRadar
cat > /etc/rsyslog.d/qradar.conf << 'EOF'
local6.* @@QRADAR_IP:514;RSYSLOG_SyslogProtocol23Format
EOF
systemctl restart rsyslog
systemctl restart auditdVelociraptor: Threat Hunting y DFIR en vivo
Velociraptor no es un SIEM — es una herramienta de threat hunting y respuesta a incidentes que permite ejecutar queries forenses en endpoints en tiempo real. Complementa al SIEM:
# Instalar Velociraptor client
curl -L -o /usr/local/bin/velociraptor \
https://github.com/Velocidex/velociraptor/releases/latest/download/velociraptor-linux-amd64
chmod +x /usr/local/bin/velociraptor
# Generar config de cliente (desde el server)
# velociraptor config client > client.config.yaml
# Instalar como servicio
cat > /etc/systemd/system/velociraptor-client.service << 'EOF'
[Unit]
Description=Velociraptor Client
After=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/velociraptor client -config /etc/velociraptor/client.config.yaml
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
systemctl enable velociraptor-client
systemctl start velociraptor-clientCon Velociraptor puedes ejecutar VQL queries en vivo sobre el bastion:
-- Verificar que kernel params KSPP estan activos
SELECT * FROM proc_cmdline() WHERE Key =~ "lockdown|pti|slab"
-- Buscar procesos sin Seccomp
SELECT Pid, Name, Cmdline FROM pslist()
WHERE NOT SeccompMode
-- Verificar integridad de ficheros criticos
SELECT FullPath, hash(path=FullPath, hashselect="SHA256") as Hash
FROM glob(globs="/etc/ssh/**")
-- Detectar modulos kernel cargados (deberian ser minimos)
SELECT * FROM modules()Consideraciones de nftables para telemetria
El firewall deny-all de salida requiere reglas adicionales para permitir el envio de telemetria:
# Añadir al chain output para permitir envio a SIEM
# Wazuh
tcp dport 1514 ip daddr WAZUH_MANAGER_IP accept
# Elastic
tcp dport 8220 ip daddr FLEET_SERVER_IP accept
# Splunk
tcp dport 9997 ip daddr SPLUNK_INDEXER_IP accept
# Velociraptor
tcp dport 8000 ip daddr VELOCIRAPTOR_SERVER_IP accept
# Syslog (QRadar)
tcp dport 514 ip daddr QRADAR_IP acceptMatriz de comparacion
| Capacidad | Wazuh | Elastic SIEM | Splunk | QRadar | Velociraptor |
|---|---|---|---|---|---|
| Log collection | Si | Si | Si | Si | No (no es SIEM) |
| File integrity | Si (syscheck) | Con modulo | Con addon | No nativo | Si (VQL) |
| Rootkit detection | Si | No | No | No | Si (hunting) |
| MITRE mapping | Si | Si | Si (ES) | Si | Si |
| Threat hunting | Limitado | KQL | SPL | AQL | VQL (avanzado) |
| DFIR live | No | No | No | No | Si |
| Coste | Open source | Licencia | Licencia | Licencia | Open source |
| Recomendacion | Enterprise | Enterprise | Enterprise | Enterprise | Complemento |
Recomendacion para bastiones
La combinacion ideal para un bastion es:
- Wazuh Agent como SIEM agent + FIM + rootkit detection (reemplaza AIDE)
- Velociraptor Client como herramienta de hunting/DFIR bajo demanda
- auditd con reglas MITRE como fuente de eventos del kernel (inmutable)
Con esta combinacion tienes: deteccion en tiempo real (Wazuh), auditoria inmutable del kernel (auditd -e 2), verificacion de integridad (Wazuh syscheck), y capacidad de investigacion forense en vivo (Velociraptor).
Fase 9: Blacklisting de modulos de kernel
Un bastion no necesita USB, Bluetooth, wireless ni protocolos de red exoticos. Cada modulo innecesario es superficie de ataque:
# /etc/modprobe.d/bastion-blacklist.conf
# Almacenamiento USB (vector de exfiltracion)
blacklist usb-storage
install usb-storage /bin/false
# Protocolos con historico de vulnerabilidades
blacklist dccp # CVE-2017-6074
blacklist sctp # Multiples CVEs
blacklist rds # CVE-2010-3904
blacklist tipc # CVE-2022-0435
# DMA attacks
blacklist firewire-core
blacklist thunderbolt
install thunderbolt /bin/false
# Wireless/Bluetooth (innecesario en servidor)
blacklist cfg80211
blacklist bluetoothFase 10: SSH - Algoritmos post-cuanticos
La configuracion SSH usa los algoritmos mas fuertes disponibles, incluyendo intercambio de claves resistente a computacion cuantica:
# /etc/ssh/sshd_config.d/hardening.conf
# Acceso restringido
Port 22
AddressFamily inet
ListenAddress 192.168.1.200
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
MaxAuthTries 3
MaxSessions 2
LoginGraceTime 30
# Key exchange: sntrup761 es hibrido post-cuantico + X25519
KexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256
# Solo cifrados AEAD
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
# MACs Encrypt-then-MAC
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
# Host keys (mantener RSA para compatibilidad con clientes legacy)
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
# Zero forwarding
AllowTcpForwarding no
AllowAgentForwarding no
AllowStreamLocalForwarding no
X11Forwarding no
PermitTunnel no
GatewayPorts no
PermitUserEnvironment no
# Timeouts
ClientAliveInterval 300
ClientAliveCountMax 2
# Logging verbose para correlacion con auditd
LogLevel VERBOSE
Banner /etc/issue.netNota sobre AppArmor y sshd: En Debian 13, OpenSSH 9.8+ separa el proceso en
sshd(listener) ysshd-session(sesion de usuario). Crear un perfil AppArmor personalizado para sshd es extremadamente fragil — la transicion de perfil entre ambos procesos requiere paths exactos que varian entre versiones y el modo enforce causa "Connection reset" sin generar logs DENIED utiles. La recomendacion es proteger sshd via: (1) config hardened, (2) nftables rate-limiting, (3) fail2ban, (4) auditd monitoring. El sandboxing real lo proporciona la privilege separation interna de OpenSSH.
Validacion: Security Test Suite
El bastion incluye un script de validacion automatizada que verifica las 12 areas de hardening:
$ sudo /usr/local/bin/security-test-suite.sh
============================================================
BASTION SECURITY TEST SUITE
============================================================
[1/12] Parametros de kernel KSPP
✓ slab_nomerge
✓ init_on_alloc=1
✓ pti=on
✓ lockdown=confidentiality
...
[4/12] Seccomp-BPF en servicios
✓ ssh: SystemCallFilter activo
✓ auditd: drop-in de hardening presente
✓ Perfiles Seccomp generados: 3
[5/12] Firewall nftables
✓ nftables activo
✓ Politica deny-all (input + forward)
============================================================
RESULTADOS
Passed: 42 / 45
Warnings: 3 / 45
Failed: 0 / 45
ESTADO: BASTION OPERATIVO
============================================================Modo de emergencia
Para situaciones donde el hardening impide diagnosticar problemas, existe un modo auditoria que cambia bloqueos por logs:
# Modo auditoria: AppArmor complain + Seccomp log (sin bloquear)
sudo /usr/local/bin/bastion-emergency.sh audit-mode
# Monitorizar que se habria bloqueado
journalctl -f | grep -i 'apparmor\|seccomp\|denied'
# Restaurar hardening completo
sudo /usr/local/bin/bastion-emergency.sh restore-hardeningValidacion con Lynis
Resultado de lynis audit system en el servidor con todo el hardening aplicado:
Hardening index : 79 [############### ]
Tests performed : 256
Warnings : 1 (AIDE database no inicializada aun)
Suggestions : 26Score 79/100 es solido para un bastion con esta configuracion. Las sugerencias pendientes son menores:
GRUB password— no aplicable en VM con consola restringida via hipervisorTCPKeepAlive=NOen SSH — preferimos mantenerlo para detectar conexiones muertasumask 027— ya aplicado via PAM pero login.defs usa 022 como fallbackprocess accounting— cubierto por auditd con reglas MITREmalware scanner— Wazuh FIM + AIDE cubren esta funcion
Para subir el score a 85+ basta con: inicializar la base de datos AIDE (aideinit), instalar libpam-tmpdir, y configurar un GRUB password.
Metricas de exito operacional
Un score de Lynis no significa que estes seguro. Estas metricas si indican si el hardening esta funcionando:
Prevencion
| Metrica | Como medir | Objetivo | |
|---|---|---|---|
| Syscalls bloqueadas por Seccomp | `journalctl -k | grep -c seccomp` | 0 en operacion normal (bloqueos = ataque o misconfiguracion) |
| Conexiones bloqueadas por nftables | nft list counters | Tendencia estable; picos = escaneo externo | |
| Modulos kernel rechazados | regla auditd init_module | 0 intentos no autorizados |
Deteccion
| Metrica | Como medir | Objetivo |
|---|---|---|
| Tiempo medio de deteccion (MTTD) | Timestamp auditd → timestamp alerta SIEM | < 5 min para eventos criticos |
| Falsos positivos por dia | Alertas SIEM marcadas como FP | < 5/dia por bastion |
| Cobertura MITRE ATT&CK | Reglas auditd con tag T* / tecnicas aplicables | > 80% |
Resiliencia
| Metrica | Como medir | Objetivo |
|---|---|---|
| MTTR de configuracion | Simular drift → tiempo hasta restaurar baseline | < 15 min con Ansible |
| Impacto en rendimiento | Benchmark pre/post hardening | < 10% degradacion |
| Exito de recuperacion | Ejecutar runbook de emergencia trimestralmente | 100% |
Estas metricas deben alimentar un dashboard ejecutivo. La seguridad que no se mide, no se gestiona.
Conclusiones
Este bastion implementa 10 capas de seguridad que operan de forma independiente, validadas en un entorno real (Debian 13 Trixie, kernel 6.12.88). Un atacante necesitaria:
- Evadir el firewall nftables (deny-all bidireccional, rate-limited)
- Autenticarse via SSH con clave publica (sin password, algoritmos post-cuanticos)
- Escapar del sandbox Seccomp-BPF (syscalls criticas = KILL en servicios no-privilegiados)
- Evitar deteccion por auditd (reglas inmutables -e 2, 20 reglas MITRE ATT&CK)
- No disparar alertas del SIEM (telemetria exfiltrada en tiempo real, irrevocable localmente)
- No disparar AIDE/Wazuh syscheck (verificacion cada 15 min, hash SHA-512)
- Todo esto en un kernel con KSPP completo (lockdown=confidentiality, pti, ASLR 32-bit)
Lecciones aprendidas en produccion
Durante la implementacion real encontramos que:
ProtectSystem=stricten drop-ins globales rompe el sistema: Monta/como read-only para todos los servicios incluyendo los de systemd-core. Solo usar per-service.SystemCallFilteres incompatible con sshd: OpenSSH necesita@privileged(setuid, setgid, chroot) para su privilege separation. Forzar Seccomp aqui causa SIGSYS (signal 31) y core-dump inmediato.- AppArmor enforce para sshd es fragil en Debian 13: La separacion sshd/sshd-session (OpenSSH 9.8+) hace que los perfiles requieran mantenimiento constante entre versiones.
audit=1es obligatorio en GRUB: Sin este parametro, el subsistema audit se inicializa como disabled aunqueCONFIG_AUDIT=yen el kernel.- Rate-limit agresivo en nftables causa lockouts: 3/minute con burst 5 es demasiado restrictivo durante testing. Usar 10/minute burst 20 y reducir en produccion.
- tcp_wrappers (libwrap) sigue activo en Debian 13: sshd-session intenta leer
/etc/hosts.allowy/etc/hosts.deny. Si AppArmor o sandboxing impide el acceso, la conexion se rechaza silenciosamente.
La filosofia es clara: no hay confianza implicita. Cada capa asume que las anteriores han sido comprometidas. Pero la implementacion debe ser pragmatica — un sistema que no puedes administrar remotamente no es seguro, es inutil.
Toda la configuracion es aplicable en cualquier entorno — desde un portatil con Debian hasta un cluster VMware empresarial. Los ficheros de configuracion son los mismos independientemente de donde se despliegue.
Mapeo a frameworks regulatorios
Las reglas de auditd mapean a MITRE ATT&CK, pero en entornos regulados se necesita trazabilidad a frameworks de cumplimiento:
| Control tecnico | CIS Benchmark | PCI DSS 4.0 | NIST 800-53 | ISO 27001 | SOC 2 |
|---|---|---|---|---|---|
kptr_restrict=2 | 1.5.2 | Req 2.2.1 | SC-39 | A.12.6.1 | CC6.1 |
ptrace_scope=3 | 1.5.4 | Req 2.2.1 | AC-3 | A.9.4.1 | CC6.1 |
| nftables deny-all | 3.5.x | Req 1.2-1.5 | SC-7 | A.13.1.1 | CC6.6 |
| SSH key-only | 5.3.x | Req 8.3.1 | IA-2 | A.9.2.1 | CC6.1 |
| auditd -e 2 | 4.1.x | Req 10.2-10.3 | AU-9 | A.12.4.2 | CC7.2 |
| Seccomp per-service | - | Req 2.2.1 | SC-39 | A.12.6.1 | CC6.1 |
| AIDE/FIM | 1.3.x | Req 11.5 | SI-7 | A.12.4.3 | CC7.1 |
| lockdown=confidentiality | - | Req 2.2.1 | SC-34 | A.12.5.1 | CC6.1 |
| Telemetria a SIEM | 4.1.x | Req 10.6 | AU-6 | A.12.4.1 | CC7.2 |
Limitaciones y vectores de ataque residuales
Ningun hardening es absoluto. Esta seccion documenta honestamente lo que esta guia NO resuelve y los vectores que requieren capas adicionales:
Lo que lockdown + KSPP no cubre
| Vector | Por que persiste | Mitigacion adicional |
|---|---|---|
| Use-after-free en codigo kernel valido | lockdown bloquea acceso directo a memoria, no bugs logicos | Intel TDX / AMD SEV-SNP con memoria cifrada por hardware |
| Side-channels (Spectre variantes nuevas) | Mitigaciones cubren variantes conocidas, no futuras | Microcode updates + isolation por CPU pinning |
| Firmware/UEFI comprometido | lockdown opera a nivel kernel, no firmware | Coreboot + Heads, measured boot con TPM 2.0 |
| Intel ME / AMD PSP | Coprocesadores con acceso total a memoria | Hardware sin ME (System76, Purism) o me_cleaner |
| Modulos firmados con clave robada | module.sig_enforce valida firma, no integridad de la CA | Claves de firma en HSM (YubiHSM), rotacion periodica |
Lo que Seccomp-BPF no puede bloquear
- Vulnerabilidades dentro de syscalls permitidas: un buffer overflow en
read()no requiere syscalls extra - Exfiltracion via syscalls legitimas:
sendto()a un servidor permitido por nftables - Nuevas syscalls en kernels futuros: si no se actualiza la lista, quedan permitidas por defecto
Mitigacion: combinar Seccomp con Landlock (restriccion de filesystem paths), eBPF LSM para politicas dinamicas, o micro-VMs (Firecracker/gVisor) para aislamiento completo.
Red: vectores residuales con deny-all
- Conexiones establecidas:
ct state established acceptpermite tunelizar trafico sobre conexiones legitimas - DNS tunneling: si se permite UDP/53 saliente, herramientas como
iodine/dnscat2pueden exfiltrar datos - Compromiso de destinos permitidos: si el servidor apt o NTP es comprometido, el trafico es "legitimo"
Mitigacion: DNS proxy local con allowlist de dominios, mTLS para todas las conexiones salientes, egress filtering a nivel de FQDN (no solo IP).
Autenticacion: el eslabon humano
- TOTP es phishable: un atacante puede obtener el codigo en tiempo real via sitio falso
- Certificados SSH robados: validos hasta expiracion aunque se detecte compromiso
- Administrador comprometido: si el endpoint del admin tiene keylogger, todo el hardening es irrelevante
Mitigacion: FIDO2/WebAuthn (resistente a phishing), certificados con TTL < 1 hora, Privileged Access Workstations (PAW) dedicadas.
Auditoria: lo que auditd no ve
- Atacante con root puede saturar el buffer de audit antes de ejecutar acciones maliciosas
- Si el SIEM receptor es comprometido, los logs pueden ser manipulados o ignorados
- AIDE puede ser evadido si el atacante actualiza la base de datos antes del siguiente check
Mitigacion: WORM storage para logs (S3 Object Lock), multi-party logging a SIEMs independientes, fs-verity para integridad a nivel de bloque.
Supply chain: la confianza transitiva
Cada paquete instalado via apt depende de una cadena de confianza:
- Mirror de Debian no comprometido
- Claves GPG de firma no robadas
- Dependencias transitivas (libssl, libc6) sin backdoors
Mitigacion: reproducible builds, binary transparency logs, IMA/EVM para verificacion de binarios en runtime, version pinning estricto.
Conclusion sobre limitaciones
Esta guia cubre solidamente las capas software de hardening (kernel, userspace, red, autenticacion, auditoria). Para entornos de maxima sensibilidad (defensa, infraestructura critica) se requieren ademas:
- Hardware trust: TPM 2.0, measured boot, remote attestation
- Aislamiento fisico: air-gapped SIEMs, HSMs para claves
- Procesos organizativos: Zero Trust para administradores, just-in-time access, PAWs
La seguridad es un proceso continuo, no un estado final. Este bastion es un punto de partida solido — no un destino.
Referencias
- Kernel Self-Protection Project (KSPP)
- Systemd Security Sandboxing
- MITRE ATT&CK Linux Matrix
- CIS Debian Linux Benchmark
- Madaidan's Linux Hardening Guide
- Wazuh Documentation
- Velociraptor - Digital Forensics and Incident Response
- OpenSSH 9.8 Release Notes (sshd-session separation)
- NIST SP 800-53 Rev. 5 - Security Controls
- PCI DSS v4.0 - Requirement 2 (Secure Configurations)
Comentarios