🛡️ Seguridad

Hardening de Servidores Linux: Mejores Prácticas

← Volver al Blog

Introducción

Cada día se reportan miles de ataques a servidores Linux. Los atacantes buscan sistemas mal configurados, contraseñas débiles y servicios expuestos. En esta guía aprenderás a proteger tus servidores Linux como un profesional con años de experiencia en infraestructura IT. Abordaremos desde la configuración básica hasta técnicas avanzadas de hardening utilizadas en entornos de producción reales, incluyendo benchmarks CIS, auditoría con Lynis, monitoreo de integridad con AIDE y automatización completa con Ansible.

Esta guía está basada en Ubuntu 22.04 LTS, pero los conceptos y herramientas son aplicables a cualquier distribución Linux. He organizado el contenido en módulos independientes para que puedas implementarlos gradualmente según las necesidades de tu infraestructura.

¿Qué es el Hardening?

Hardening es el proceso de asegurar un sistema reduciendo su superficie de ataque. Cada servicio, puerto, usuario y configuración que no es estrictamente necesario representa un riesgo potencial. El objetivo no es solo aplicar parches de seguridad, sino construir una arquitectura defensiva por capas que haga económicamente inviable para un atacante comprometer el sistema.

Un servidor hardening sigue el principio de mínimo privilegio: cada componente tiene exactamente los permisos que necesita para funcionar y nada más. Esto aplica a usuarios, procesos, servicios de red y configuraciones del kernel.

CIS Benchmarks

Los CIS (Center for Internet Security) Benchmarks son guías de configuración segura desarrolladas por consenso global de expertos en ciberseguridad. Constituyen el estándar de facto para el hardening de sistemas operativos, bases de datos, aplicaciones y servicios cloud. Cada benchmark contiene cientos de recomendaciones categorizadas por nivel de impacto y riesgo.

Niveles CIS: Level 1 vs Level 2

CIS Level 1 agrupa las recomendaciones esenciales que todo servidor debería implementar. Son configuraciones de bajo impacto operativo que reducen significativamente la superficie de ataque sin afectar la funcionalidad del sistema. Por ejemplo, deshabilitar el login root por SSH, establecer permisos correctos en archivos críticos o configurar el tiempo de bloqueo de sesiones inactivas.

CIS Level 2 contiene medidas más agresivas diseñadas para entornos de alta seguridad como datos financieros, salud o infraestructura crítica. Estas recomendaciones pueden requerir cambios profundos en la configuración del sistema y en ocasiones romper compatibilidad con aplicaciones antiguas. Por ejemplo, deshabilitar por completo la compilación de módulos del kernel en tiempo de ejecución, restringir el uso de sysctl o activar auditd para todas las llamadas al sistema.

Cómo aplicar CIS Benchmarks

El proceso recomendado comienza con la descarga del benchmark oficial desde https://www.cisecurity.org/cis-benchmarks. Para Ubuntu 22.04, el benchmark contiene aproximadamente 250 recomendaciones. Se recomienda usar herramientas como OpenSCAP para automatizar la evaluación:

# Instalar OpenSCAP
sudo apt install -y libopenscap8 scap-security-guide

# Evaluar el sistema contra el perfil CIS Level 1
sudo oscap xccdf eval \
  --profile xccdf_org.ssgproject.content_profile_cis_level1_server \
  --results results.xml --report report.html \
  /usr/share/xml/scap/ssg/content/ssg-ubuntu2204-ds.xml

# Generar script de remediación
sudo oscap xccdf generate fix \
  --profile xccdf_org.ssgproject.content_profile_cis_level1_server \
  --fix-type ansible results.xml -o remediation.yml

El reporte HTML generado muestra en verde las configuraciones que cumplen, en rojo las que fallan y en gris las no evaluadas. La remediación puede aplicarse manualmente o mediante el playbook de Ansible generado.

Ejemplo de recomendaciones CIS Level 1

# 1.1.1.1 - Ensure mounting of cramfs filesystems is disabled
install cramfs /bin/true

# 5.1.1 - Ensure cron daemon is enabled and running
systemctl enable --now cron

# 5.2.1 - Ensure permissions on /etc/ssh/sshd_config are configured
chown root:root /etc/ssh/sshd_config
chmod 600 /etc/ssh/sshd_config

# 5.4.1.1 - Ensure password expiration is 365 days or less
sed -i 's/^PASS_MAX_DAYS.*/PASS_MAX_DAYS 90/' /etc/login.defs

Ejemplo de recomendaciones CIS Level 2

# 1.5.1 - Ensure core dumps are restricted
echo 'hard core 0' >> /etc/security/limits.conf
echo 'fs.suid_dumpable = 0' >> /etc/sysctl.d/99-cis.conf

# 3.1.2 - Ensure packet redirect sending is disabled
sysctl -w net.ipv4.conf.all.send_redirects=0
sysctl -w net.ipv4.conf.default.send_redirects=0

# 4.2.1.5 - Ensure rsyslog is configured to send logs to a remote host
echo '*.* @logs.example.com:514' >> /etc/rsyslog.conf

# 6.2.1 - Ensure accounts in /etc/passwd use shadowed passwords
pwck -r

Auditoría con Lynis

Lynis es un auditor de seguridad de código abierto desarrollado por CISOfy. Escanea el sistema completo y genera un informe detallado con recomendaciones priorizadas. Es la herramienta ideal para auditorías periódicas y para verificar el cumplimiento de benchmarks como CIS o PCI-DSS.

Instalación y ejecución

# Instalación desde repositorio oficial
sudo apt install -y lynis

# O descargar la última versión
wget -O - https://packages.cisofy.com/keys/cisofy-software-public.key | sudo apt-key add -
echo "deb https://packages.cisofy.com/community/lynis/deb/ stable main" | sudo tee /etc/apt/sources.list.d/cisofy-lynis.list
sudo apt update && sudo apt install -y lynis

# Ejecutar auditoría completa
sudo lynis audit system

# Ejecutar solo categorías específicas
sudo lynis audit system --tests-from-group malware,authentication,networking

Interpretar los resultados

Al finalizar, Lynis muestra un resumen con el Hardening Index, un valor numérico de 0 a 100 que indica el nivel de seguridad del sistema. También presenta una lista de sugerencias numeradas como Suggestion y Warning. Cada sugerencia incluye una explicación y el comando necesario para resolverla.

=============================================================
  Lynis 3.1.1
=============================================================
  Hardening index : 68 [############       ]
  Tests performed : 267
  Suggestions    : 34
  Warnings       : 2

  How to increase this number:
  - Follow suggestions and warnings from the scan
  - Check https://cisofy.com/lynis/ for additional guidance

  💡 Tip: Schedule Lynis scans via cron for continuous auditing

Automatizar auditorías con Lynis

Para mantener un monitoreo continuo, programa Lynis vía cron y envía los reportes por correo o a un SIEM:

#!/bin/bash
# /usr/local/bin/lynis-audit.sh
LOGDIR="/var/log/lynis"
DATE=$(date +%Y%m%d-%H%M)
mkdir -p "$LOGDIR"

lynis audit system --quiet --logfile "$LOGDIR/lynis-$DATE.log"

# Extraer hardening index
INDEX=$(grep "Hardening index" "$LOGDIR/lynis-$DATE.log" | awk '{print $NF}')
echo "[$DATE] Hardening Index: $INDEX" >> "$LOGDIR/history.txt"

# Si el índice baja de 60, enviar alerta
if [ "$INDEX" -lt 60 ]; then
    mail -s "ALERTA: Hardening Index bajó a $INDEX en $(hostname)" \
         admin@example.com < "$LOGDIR/lynis-$DATE.log"
fi
# Programar en cron: ejecución semanal
0 6 * * 1 root /usr/local/bin/lynis-audit.sh

Configuración de auditd

auditd es el subsistema de auditoría del kernel de Linux. Permite registrar eventos de seguridad a nivel de sistema operativo, incluyendo accesos a archivos, ejecución de comandos, cambios de permisos y conexiones de red. Es el componente que genera la evidencia forense cuando ocurre un incidente.

Instalación y reglas básicas

sudo apt install -y auditd audispd-plugins
sudo systemctl enable --now auditd

Las reglas se definen en /etc/audit/rules.d/audit.rules. Aquí tienes un conjunto completo de reglas para un servidor de producción:

# /etc/audit/rules.d/audit.rules

# Eliminar reglas existentes
-D

# Buffer size (ajustar según RAM disponible)
-b 8192

# Frecuencia de fail y acción
-f 1
--backlog_wait_time 60000

# Monitoreo de archivos críticos del sistema
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/gshadow -p wa -k identity
-w /etc/sudoers -p wa -k sudoers
-w /etc/sudoers.d/ -p wa -k sudoers

# Monitoreo de configuración SSH
-w /etc/ssh/sshd_config -p wa -k sshd_config

# Monitoreo de binarios críticos
-w /usr/bin/passwd -p x -k passwd_modification
-w /usr/bin/su -p x -k su_exec
-w /usr/bin/sudo -p x -k sudo_exec
-w /usr/bin/ssh -p x -k ssh_exec

# Monitoreo del directorio de log
-w /var/log/auth.log -p wa -k authlog
-w /var/log/syslog -p wa -k syslog

# Monitoreo de cambios en la configuración de red
-w /etc/network/ -p wa -k network
-w /etc/hosts -p wa -k hosts
-w /etc/hostname -p wa -k hostname

# Monitoreo del directorio de cron
-w /etc/cron.d/ -p wa -k cron
-w /etc/crontab -p wa -k cron
-w /etc/cron.daily/ -p wa -k cron
-w /etc/cron.hourly/ -p wa -k cron

# Registrar todas las llamadas al sistema de mount
-a always,exit -F arch=b64 -S mount -S umount2 -k mounts
-a always,exit -F arch=b32 -S mount -S umount2 -k mounts

# Registrar cambios de tiempo
-a always,exit -F arch=b64 -S adjtimex -S settimeofday -S clock_settime -k time-change
-a always,exit -F arch=b32 -S adjtimex -S settimeofday -S clock_settime -k time-change

Gestión de logs de auditoría

auditd genera logs en /var/log/audit/audit.log. Para consultarlos de forma eficiente:

# Buscar eventos de autenticación
sudo ausearch -m USER_LOGIN --success yes -i

# Buscar accesos a /etc/shadow
sudo ausearch -k identity -i

# Buscar ejecuciones de sudo
sudo ausearch -k sudo_exec -i

# Reporte resumido por tipo de evento
sudo aureport -m

# Reporte de inicios de sesión
sudo aureport -l -i

# Monitoreo en tiempo real
sudo tail -f /var/log/audit/audit.log | audit2why

Para evitar que los logs de auditoría saturen el disco, configura la rotación en /etc/audit/auditd.conf:

max_log_file = 100
max_log_file_perms = 'go-rwx'
num_logs = 5
space_left_action = SYSLOG
action_mail_acct = root
admin_space_left_action = HALT
disk_full_action = SUSPEND

Automount y eliminación de servicios innecesarios

Cada servicio corriendo en un servidor es una potencial puerta de entrada. La regla de oro es: si no lo necesitas, desinstálalo. Si no puedes desinstalarlo, al menos deshabilítalo y elimínalo del inicio automático.

Identificar servicios activos

# Listar todos los servicios y su estado
systemctl list-units --type=service --state=running

# Ver servicios habilitados al arranque
systemctl list-unit-files --type=service --state=enabled

# Escuchar puertos abiertos y servicios asociados
ss -tulpn
netstat -tulpn

# Identificar procesos que escuchan en red
lsof -i -P -n | grep LISTEN

Deshabilitar servicios innecesarios

# Detener y deshabilitar un servicio
sudo systemctl stop whoopsie
sudo systemctl disable whoopsie

# Enmascarar para evitar que otro proceso lo reactive
sudo systemctl mask whoopsie

# Desinstalar por completo
sudo apt purge -y whoopsie avahi-daemon cups bluetooth

Servicios comunes que deberías revisar

Automount y dispositivos extraíbles

Deshabilitar el automontaje de dispositivos USB y medios extraíbles es una medida CIS Level 1 que previene ataques de tipo BadUSB:

# Deshabilitar el módulo USB storage
echo "blacklist usb-storage" | sudo tee /etc/modprobe.d/disable-usb-storage.conf

# Para un control más fino con udev
echo 'SUBSYSTEM=="usb", ATTR{authorized}="0"' | sudo tee /etc/udev/rules.d/10-disable-usb.rules

# Deshabilitar automontaje en GNOME/KDE (si aplica)
sudo apt purge -y udisks2

# Alternativa: deshabilitar servicio de automontaje
sudo systemctl mask autofs

Nota importante: no deshabilites usb-storage si tu servidor usa llaves de hardware YubiKey o dispositivos de almacenamiento USB para backups. En ese caso, audita qué dispositivos USB específicos autorizar mediante reglas udev personalizadas.

Protección contra malware en Linux

Aunque Linux es inherentemente más resistente al malware que otros sistemas operativos, no es inmune. Rootkits, mineros de criptomonedas y webshells son amenazas reales. Aquí tienes las herramientas esenciales para la detección.

ClamAV: Antivirus para servidores

ClamAV es un antivirus de código abierto fundamental para servidores de archivos, correo y web. Detecta malware, worms y virus diseñados originalmente para Windows que podrían afectar a los usuarios que descargan archivos de tu servidor.

# Instalación
sudo apt install -y clamav clamav-daemon

# Actualizar base de firmas
sudo systemctl stop clamav-freshclam
sudo freshclam
sudo systemctl start clamav-freshclam

# Escaneo de directorios críticos
sudo clamscan -r --bell --infected /etc /var/www /home
sudo clamscan -r --remove --quiet /var/www/uploads

# Escaneo completo (programar en horario de baja actividad)
sudo clamscan -r / --exclude-dir=/proc --exclude-dir=/sys \
  --exclude-dir=/dev --max-filesize=100M --max-recursion=20 \
  -l /var/log/clamav/scan-$(date +%Y%m%d).log

# Análisis con ClamAV daemon para mejor rendimiento
clamdscan --multiscan --fdpass /var/www

rkhunter: Rootkit Hunter

rkhunter analiza el sistema en busca de rootkits conocidos, binarios modificados y módulos del kernel sospechosos:

# Instalación
sudo apt install -y rkhunter

# Actualizar bases de datos
sudo rkhunter --update
sudo rkhunter --propupd

# Ejecutar chequeo completo
sudo rkhunter --check --skip-keypress --report-warnings-only

# Generar reporte detallado
sudo rkhunter --check --rwo --summary-only
cat /var/log/rkhunter.log | grep -i warning

Configuración recomendada en /etc/rkhunter.conf:

ALLOW_SSH_ROOT_USER=no
ALLOW_SYSLOG_REMOTE_LOGGING=yes
SUSPECT_DIRS="/dev /tmp /var/tmp /var/www /home"
PHALANX2_DIRTEST=1
ALLOWHIDDENDIR=""
DISABLE_UNHIDE=no
SCRIPTWHITELIST="/usr/sbin/groupadd /usr/sbin/useradd /usr/sbin/usermod"

chkrootkit

chkrootkit es una herramienta complementaria que verifica señales específicas de rootkits conocidos mediante la inspección de binarios del sistema y sus firmas:

# Instalación y ejecución
sudo apt install -y chkrootkit
sudo chkrootkit

# Solo verificar rootkits conocidos (más rápido)
sudo chkrootkit -q -n

# Si encuentras algo sospechoso, investiga más a fondo
# chkrootkit -x (modo experto)

Mi recomendación es usar ambas herramientas: rkhunter para monitoreo continuo programado (diario) y chkrootkit como segunda opinión cuando se detectan anomalías.

Script de escaneo automatizado

#!/bin/bash
# /usr/local/bin/malware-scan.sh
LOG="/var/log/malware-scan.log"
DATE=$(date)

echo "=== Escaneo de malware: $DATE ===" >> "$LOG"

# rkhunter
rkhunter --check --skip-keypress --rwo >> "$LOG" 2>&1

# chkrootkit
chkrootkit -q -n >> "$LOG" 2>&1

# ClamAV
clamscan -r /var/www /home --quiet --infected >> "$LOG" 2>&1

# Alertar si hay detecciones
if grep -qi "infected\|warning\|found" "$LOG"; then
    mail -s "ALERTA: Malware detectado en $(hostname)" \
         admin@example.com < "$LOG"
fi

echo "=== Fin del escaneo ===" >> "$LOG"

Automatización del hardening con Ansible

El hardening manual es propenso a errores y difícil de mantener en múltiples servidores. Ansible permite definir la configuración de seguridad como código, garantizando que todos los servidores cumplan con las mismas políticas y facilitando auditorías.

A continuación presento un playbook completo de hardening que implementa las medidas cubiertas en esta guía. Estructúralo en roles para mantenerlo modular y reutilizable.

Estructura de directorios recomendada

ansible-hardening/
├── playbook.yml
├── inventory/
│   └── production.yml
├── group_vars/
│   └── all.yml
└── roles/
    ├── ssh/
    │   └── tasks/main.yml
    ├── ufw/
    │   └── tasks/main.yml
    ├── fail2ban/
    │   └── tasks/main.yml
    ├── auditd/
    │   └── tasks/main.yml
    ├── lynis/
    │   └── tasks/main.yml
    └── sysctl/
        └── tasks/main.yml

Playbook principal

---
# playbook.yml - Hardening completo de servidores Linux
- name: Hardening de servidor Linux
  hosts: all
  become: yes
  vars_files:
    - group_vars/all.yml

  roles:
    - sysctl
    - ssh
    - ufw
    - fail2ban
    - auditd
    - lynis

  post_tasks:
    - name: Reiniciar servicios para aplicar cambios
      systemd:
        name: "{{ item }}"
        state: restarted
        daemon_reload: yes
      loop:
        - sshd
        - ufw
        - fail2ban
        - auditd

Rol: Configuración de SSH

---
# roles/ssh/tasks/main.yml
- name: Asegurar permisos del directorio SSH
  file:
    path: /etc/ssh
    owner: root
    group: root
    mode: '0755'

- name: Configurar sshd hardening
  template:
    src: sshd_config.j2
    dest: /etc/ssh/sshd_config
    owner: root
    group: root
    mode: '0600'
  notify: restart sshd

- name: Asegurar que existan llaves de host válidas
  command: ssh-keygen -A
  args:
    creates: /etc/ssh/ssh_host_rsa_key

- name: Deshabilitar versiones antiguas de protocolo SSH
  lineinfile:
    path: /etc/ssh/sshd_config
    regexp: '^#?Protocol'
    line: 'Protocol 2'

- name: Configurar límite de sesiones por usuario
  lineinfile:
    path: /etc/security/limits.conf
    line: '*               hard    maxlogins     3'
    create: yes

Template SSH roles/ssh/templates/sshd_config.j2:

Port {{ ssh_port }}
Protocol 2
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ed25519_key

LoginGraceTime 60
PermitRootLogin no
MaxAuthTries 3
MaxSessions 5

PubkeyAuthentication yes
PasswordAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
UsePAM yes

X11Forwarding no
PrintMotd no
AcceptEnv LANG LC_*
ClientAliveInterval 300
ClientAliveCountMax 2

AllowUsers {{ ssh_allowed_users | join(' ') }}

# CIS Level 2
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
KexAlgorithms curve25519-sha256,diffie-hellman-group16-sha512

Rol: Firewall UFW

---
# roles/ufw/tasks/main.yml
- name: Establecer políticas por defecto
  ufw:
    direction: "{{ item.direction }}"
    policy: "{{ item.policy }}"
  loop:
    - { direction: incoming, policy: deny }
    - { direction: outgoing, policy: allow }

- name: Permitir puertos específicos
  ufw:
    rule: "{{ item.rule }}"
    port: "{{ item.port }}"
    proto: "{{ item.proto }}"
  loop: "{{ ufw_allowed_ports }}"

- name: Habilitar UFW
  ufw:
    state: enabled
    logging: on

- name: Verificar reglas aplicadas
  command: ufw status numbered
  register: ufw_status
  changed_when: false

- name: Mostrar estado del firewall
  debug:
    msg: "{{ ufw_status.stdout_lines }}"

Rol: fail2ban

---
# roles/fail2ban/tasks/main.yml
- name: Instalar fail2ban
  apt:
    name: fail2ban
    state: present

- name: Configurar fail2ban local
  template:
    src: jail.local.j2
    dest: /etc/fail2ban/jail.local
    owner: root
    group: root
    mode: '0644'
  notify: restart fail2ban

- name: Configurar filtro SSH personalizado
  copy:
    content: |
      [Definition]
      failregex = ^%(__prefix_line)s(?:error: )?\(port \d+\): Authentication failure for .* from (?: port \d*)?\s*$
      ignoreregex =
    dest: /etc/fail2ban/filter.d/sshd-custom.conf
    owner: root
    group: root
    mode: '0644'

- name: Habilitar fail2ban al inicio
  systemd:
    name: fail2ban
    enabled: yes
    state: started

Template roles/fail2ban/templates/jail.local.j2:

[DEFAULT]
bantime = {{ fail2ban_bantime }}
findtime = {{ fail2ban_findtime }}
maxretry = {{ fail2ban_maxretry }}
banaction = ufw

[sshd]
enabled = true
port = {{ ssh_port }}
logpath = %(sshd_log)s

[sshd-ddos]
enabled = true
port = {{ ssh_port }}
logpath = %(sshd_log)s

[ufw]
enabled = true
logpath = /var/log/ufw.log

[recidive]
enabled = true
logpath = /var/log/fail2ban.log
bantime = 86400
findtime = 86400
maxretry = 5

Rol: auditd

---
# roles/auditd/tasks/main.yml
- name: Instalar auditd
  apt:
    name: auditd
    state: present

- name: Desplegar reglas de auditoría
  copy:
    src: audit.rules
    dest: /etc/audit/rules.d/audit.rules
    owner: root
    group: root
    mode: '0640'
  notify: restart auditd

- name: Configurar auditd.conf
  lineinfile:
    path: /etc/audit/auditd.conf
    regexp: "^{{ item.key }}"
    line: "{{ item.key }} = {{ item.value }}"
  loop:
    - { key: max_log_file, value: '100' }
    - { key: num_logs, value: '5' }
    - { key: space_left_action, value: SYSLOG }
    - { key: admin_space_left_action, value: HALT }
    - { key: disk_full_action, value: SUSPEND }

- name: Asegurar rotación de logs de auditoría
  copy:
    content: |
      /var/log/audit/*.log {
        weekly
        rotate 12
        compress
        delaycompress
        missingok
        notifempty
        postrotate
          /sbin/service auditd restart >/dev/null 2>&1 || true
        endscript
      }
    dest: /etc/logrotate.d/auditd
    owner: root
    group: root
    mode: '0644'

Variables del playbook

---
# group_vars/all.yml
ssh_port: 2222
ssh_allowed_users:
  - admin
  - deploy

ufw_allowed_ports:
  - { rule: allow, port: '2222', proto: tcp }
  - { rule: allow, port: '80', proto: tcp }
  - { rule: allow, port: '443', proto: tcp }

fail2ban_bantime: 3600
fail2ban_findtime: 600
fail2ban_maxretry: 3

Rol: sysctl (kernel hardening)

---
# roles/sysctl/tasks/main.yml
- name: Aplicar parámetros de hardening del kernel
  sysctl:
    name: "{{ item.name }}"
    value: "{{ item.value }}"
    state: present
    reload: yes
    sysctl_set: yes
  loop:
    # Deshabilitar forwarding de IP
    - { name: net.ipv4.ip_forward, value: '0' }
    - { name: net.ipv6.conf.all.forwarding, value: '0' }
    # Deshabilitar redirects ICMP
    - { name: net.ipv4.conf.all.send_redirects, value: '0' }
    - { name: net.ipv4.conf.default.send_redirects, value: '0' }
    # Deshabilitar aceptación de redirects
    - { name: net.ipv4.conf.all.accept_redirects, value: '0' }
    - { name: net.ipv4.conf.default.accept_redirects, value: '0' }
    - { name: net.ipv6.conf.all.accept_redirects, value: '0' }
    # Protección contra spoofing
    - { name: net.ipv4.conf.all.rp_filter, value: '1' }
    - { name: net.ipv4.conf.default.rp_filter, value: '1' }
    # Ignorar pings broadcast
    - { name: net.ipv4.icmp_echo_ignore_broadcasts, value: '1' }
    # Deshabilitar source routing
    - { name: net.ipv4.conf.all.accept_source_route, value: '0' }
    - { name: net.ipv6.conf.all.accept_source_route, value: '0' }
    # Protección contra SYN flood
    - { name: net.ipv4.tcp_syncookies, value: '1' }
    # Rango de puertos efímeros
    - { name: net.ipv4.ip_local_port_range, value: '32768 65535' }
    # Deshabilitar magic SysRq
    - { name: kernel.sysrq, value: '0' }
    # Restringir ptrace
    - { name: kernel.yama.ptrace_scope, value: '1' }
    # Deshabilitar core dumps
    - { name: fs.suid_dumpable, value: '0' }

Monitoreo de integridad con AIDE

AIDE (Advanced Intrusion Detection Environment) es un sistema de detección de cambios en archivos basado en una base de datos de checksums. Cuando un archivo es modificado sin autorización —por ejemplo, por un atacante que reemplaza un binario del sistema— AIDE lo detecta y alerta.

Instalación y configuración inicial

# Instalación
sudo apt install -y aide

# Configuración en /etc/aide/aide.conf
sudo cp /etc/aide/aide.conf /etc/aide/aide.conf.default

Configuración típica de AIDE para un servidor de producción:

# /etc/aide/aide.conf
# Definir macros de reglas
Binarios = p+i+n+u+g+s+b+m+c+md5+sha256
Config   = p+i+n+u+g+s+b+m+c+sha512
Logs     = p+i+n+u+g+size
Directorios = p+i+n+u+g

# Directorios del sistema
/bin    Binarios
/sbin   Binarios
/usr/bin Binarios
/usr/sbin Binarios
/lib    Binarios
/lib64  Binarios

# Configuración crítica
/etc Binarios
!/etc/ssl/certs
!/etc/fstab

# Archivos de log
/var/log Config
!/var/log/*.gz
!/var/log/*.bz2

# Directorios home
/home Binarios

# Web root
/var/www Binarios
!/var/www/cache

# Ignorar sistemas de archivos virtuales
!/proc
!/sys
!/dev
!/run
!/mnt
!/media

# Base de datos
!/var/lib/aide

# Reglas personalizadas para directorios específicos
/etc/ssh Binarios
/etc/audit Config
/etc/cron.d Config

Inicializar la base de datos

# Crear la base de datos inicial
sudo aideinit

# La base de datos se genera en /var/lib/aide/aide.db.new
# Renombrar como base operativa
sudo mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db

# Verificar integridad por primera vez
sudo aide --check

Monitoreo continuo con cron

#!/bin/bash
# /usr/local/bin/aide-check.sh
DB="/var/lib/aide/aide.db"
OUTPUT="/var/log/aide-report-$(date +%Y%m%d).txt"

if [ ! -f "$DB" ]; then
    echo "ERROR: Base de datos AIDE no encontrada" | mail -s "AIDE Error" admin@example.com
    exit 1
fi

# Ejecutar verificación
aide --check > "$OUTPUT" 2>&1

# Verificar resultados
if grep -q "changed entries" "$OUTPUT"; then
    CHANGED=$(grep "changed entries" "$OUTPUT" | awk '{print $NF}')
    if [ "$CHANGED" -gt 0 ]; then
        mail -s "ALERTA: $CHANGED cambios detectados por AIDE en $(hostname)" \
             admin@example.com < "$OUTPUT"
    fi
fi

# Actualizar base de datos si los cambios fueron autorizados
# sudo aide --update && sudo mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db
# Programar verificación diaria
0 4 * * * root /usr/local/bin/aide-check.sh

Es importante establecer un proceso para manejar las alertas de AIDE. Los cambios legítimos (actualizaciones, despliegues) deben registrar el cambio en la base de datos. Cualquier cambio no autorizado debe investigarse inmediatamente.

Configuración de Grub y arranque seguro

Proteger el cargador de arranque evita que un atacante con acceso físico al servidor pueda bootear con un kernel modificado, montar el sistema de archivos en modo recovery o modificar parámetros de arranque para deshabilitar la seguridad.

Proteger Grub con contraseña

# Generar hash de la contraseña
grub-mkpasswd-pbkdf2
# Ingresa la contraseña cuando se solicite
# Copia el hash generado (comienza con "grub.pbkdf2.sha512...")

Agrega la siguiente configuración en /etc/grub.d/40_custom:

#!/bin/sh
exec tail -n +3 $0

set superusers="admin"
password_pbkdf2 admin grub.pbkdf2.sha512.10000.HEXAQUI...COPIADO

# Restringir edición en arranque
set unrestricted_menu="y"

Protege el archivo de configuración y actualiza Grub:

# Asegurar permisos del archivo
sudo chmod 700 /etc/grub.d/40_custom
sudo chown root:root /etc/grub.d/40_custom

# Actualizar configuración de Grub
sudo update-grub

Parámetros de hardening del kernel en Grub

Modifica /etc/default/grub para agregar parámetros de seguridad al kernel:

# /etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
GRUB_CMDLINE_LINUX="ipv6.disable=1 audit=1 audit_backlog_limit=8192 \
  slab_nomerge init_on_alloc=1 init_on_free=1 page_alloc_shuffle=1 \
  pti=on randomize_kstack_offset=on spec_store_bypass_disable=on \
  spectre_v2=on tsx=off tsx_async_abort=full,nosmt mds=full,nosmt \
  l1tf=full,force nosmt=force kvm.nx_huge_pages=force \
  module.sig_enforce=1 lockdown=confidentiality"

# Asegurar permisos del archivo
sudo chmod 600 /etc/default/grub

# Actualizar Grub
sudo update-grub

Explicación de los parámetros clave:

Protección adicional del arranque

# Establecer contraseña de BIOS/EFI (fuera del SO)
# Configurar orden de arranque para que solo bootee desde disco
# Deshabilitar boot desde USB/CD en BIOS

# Bloquear acceso a la consola de recovery en Grub
# Por defecto Ubuntu muestra opciones de recovery. Deshabilitarlas:
sudo sed -i 's/GRUB_DISABLE_RECOVERY=false/GRUB_DISABLE_RECOVERY=true/' /etc/default/grub
sudo update-grub

# En sistemas UEFI, proteger con Secure Boot
sudo apt install -y shim-signed
sudo mokutil --enable-validation

Separación de servicios con contenedores

Una de las estrategias de hardening más efectivas es el principio de aislamiento: cada servicio debe ejecutarse en su propio contenedor, con su propio sistema de archivos, red y recursos. Esto limita el alcance de un compromiso: si un atacante vulnera una aplicación web, no tiene acceso automático al resto del sistema ni a otros servicios.

Docker vs Podman

Docker es la plataforma de contenedores más extendida, pero requiere un daemon ejecutándose como root, lo que históricamente ha sido vector de ataques de escalación de privilegios. Podman es una alternativa sin daemon (daemonless) que ejecuta contenedores como el usuario actual, no requiere permisos de root, y es compatible con las imágenes Docker.

Para entornos de producción donde la seguridad es crítica, mi recomendación es Podman en combinación con systemd para la gestión del ciclo de vida de los contenedores.

Ejemplo: Aislar una aplicación web con Docker

# Usar una red dedicada para el stack
docker network create --internal --subnet 172.20.0.0/16 app-net

# Contenedor de base de datos (sin acceso a internet)
docker run -d \
  --name db \
  --network app-net \
  --restart always \
  --read-only \
  --tmpfs /tmp:noexec,nosuid,size=100M \
  --tmpfs /var/run/mysqld:noexec,nosuid,size=50M \
  -v db-data:/var/lib/mysql:rw,nosuid,noexec \
  -e MYSQL_ROOT_PASSWORD_FILE=/run/secrets/db_root_pw \
  --secret db_root_pw \
  --security-opt=no-new-privileges:true \
  --security-opt=seccomp=/path/to/seccomp-profile.json \
  --cap-drop=ALL \
  --cap-add=SETUID \
  --cap-add=SETGID \
  mysql:8.0

# Contenedor de aplicación (solo conectado a db)
docker run -d \
  --name app \
  --network app-net \
  --restart always \
  --read-only \
  --tmpfs /tmp:noexec,nosuid,size=100M \
  -v app-code:/var/www/html:ro \
  -v app-logs:/var/log/apache2:rw,nosuid,noexec \
  --security-opt=no-new-privileges:true \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  -p 443:443 \
  php:8.2-apache

# Proxy inverso (único con exposición a internet)
docker run -d \
  --name proxy \
  --network app-net \
  --restart always \
  --read-only \
  --tmpfs /tmp:noexec,nosuid,size=100M \
  -v nginx-config:/etc/nginx:ro \
  -v nginx-logs:/var/log/nginx:rw \
  --security-opt=no-new-privileges:true \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  --cap-add=NET_RAW \
  -p 80:80 \
  -p 443:443 \
  nginx:alpine

Buenas prácticas de seguridad en contenedores

Podman con systemd: Gestión nativa

# Ejecutar contenedor como usuario no root
podman run -d \
  --name web-app \
  --restart always \
  --read-only \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  --memory=512m \
  --cpus=0.5 \
  --health-cmd="curl -f http://localhost/health || exit 1" \
  --health-interval=30s \
  --health-retries=3 \
  -p 8080:8080 \
  myapp:latest

# Generar unidad systemd para el contenedor
podman generate systemd --name web-app --files --new
# Esto genera ~/.config/systemd/user/container-web-app.service

# Habilitar el servicio como usuario
systemctl --user enable container-web-app.service
systemctl --user start container-web-app.service

# Para servicios del sistema (root)
sudo mv container-web-app.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now container-web-app

Docker Compose seguro

# docker-compose.yml
version: '3.9'

services:
  app:
    image: myapp:latest
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    read_only: true
    tmpfs:
      - /tmp:noexec,nosuid,size=100M
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
    security_opt:
      - no-new-privileges:true
    mem_limit: 512m
    mem_reservation: 256m
    cpus: 0.5
    pids_limit: 100
    networks:
      - internal
    volumes:
      - app-data:/var/www/html:ro
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  db:
    image: mysql:8.0
    restart: unless-stopped
    read_only: true
    tmpfs:
      - /tmp:noexec,nosuid,size=100M
    cap_drop:
      - ALL
    cap_add:
      - SETUID
      - SETGID
      - CHOWN
    security_opt:
      - no-new-privileges:true
    mem_limit: 1g
    cpus: 1.0
    networks:
      - internal
    volumes:
      - mysql-data:/var/lib/mysql:rw,nosuid,noexec
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_pw
    secrets:
      - db_root_pw

  proxy:
    image: nginx:alpine
    restart: unless-stopped
    read_only: true
    tmpfs:
      - /tmp:noexec,nosuid,size=50M
      - /var/run:noexec,nosuid,size=50M
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
      - NET_RAW
    security_opt:
      - no-new-privileges:true
    mem_limit: 256m
    cpus: 0.3
    networks:
      - internal
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - nginx-config:/etc/nginx:ro
      - nginx-logs:/var/log/nginx:rw

networks:
  internal:
    driver: bridge
    internal: true
    ipam:
      config:
        - subnet: 172.20.0.0/16

volumes:
  app-data:
  mysql-data:
  nginx-config:
  nginx-logs:

secrets:
  db_root_pw:
    file: ./secrets/db_root_pw.txt

Checklist de Hardening

Conclusión

El hardening no es un evento único sino un proceso continuo. Las amenazas evolucionan, aparecen nuevas vulnerabilidades y los equipos de seguridad deben adaptarse constantemente. Lo que hoy es una configuración segura, mañana puede ser un vector de ataque.

Implementa las medidas de esta guía de forma progresiva: empieza con el checklist básico (SSH, firewall, fail2ban), luego agrega auditoría con Lynis y auditd, después automatiza con Ansible, y finalmente implementa el monitoreo de integridad con AIDE y la segregación de servicios con contenedores. Programa auditorías mensuales, mantén un registro de cambios y, sobre todo, documenta cada decisión de seguridad.

Recuerda: la seguridad absoluta no existe. El objetivo es hacer que el costo de comprometer tu sistema sea mayor que el beneficio que un atacante obtendría. Con las prácticas descritas en esta guía, estarás muy por delante del 99% de los servidores en internet.