Aprende › Vulnerabilidades web

Persistencia en Linux: cron, systemd, SSH keys, rootkits, LD_PRELOAD

· 9 min de lectura

Qué mecanismos usa un atacante para sobrevivir en Linux: cron, systemd timers, servicios, claves SSH, rootkits LKM, LD_PRELOAD, PAM backdoors, archivos init. Cómo se detectan y se limpian.

La persistencia en Linux comparte filosofía con la de Windows (asegurar que el acceso sobrevive a reinicios, cambios de credenciales y limpieza) pero utiliza mecanismos muy distintos: cron, systemd, servicios tradicionales, claves SSH, archivos de inicialización del shell, rootkits a nivel de kernel, preloads de biblioteca y backdoors de PAM. Todas estas técnicas aparecen en los informes forenses de incidentes en servidores Linux y cualquier auditoría de hardening las revisa antes de dar por limpio un equipo.

Esta lección cubre el catálogo completo organizado por nivel de privilegio y por sigilo. Es el complemento de la lección anterior sobre Windows; juntas cubren el 95 % de lo que ves en respuesta a incidentes reales.

Practica solo en equipos propios o laboratorios autorizados. Dejar backdoors en servidores ajenos es intrusión y delito; en pentests legítimos solo se instalan con autorización explícita y se limpian al terminar.

El mapa#

Persistencia Linux: cron, systemd, servicios, SSH keys, shell init, rootkits LKM, LD_PRELOAD, PAM, inetd, /etc/profile.d
Persistencia Linux: cron, systemd, servicios, SSH keys, shell init, rootkits LKM, LD_PRELOAD, PAM, inetd, /etc/profile.d

Catalogada por nivel de privilegio típico requerido para instalarse:

MecanismoDisparadorNivel
Cron jobsTiempoUsuario / root
Systemd user unitsLogon + tiempoUsuario
Systemd system unitsBoot + tiempoRoot
SSH authorized_keysConexión SSHUsuario / root
Shell init files (.bashrc, .profile)LogonUsuario
/etc/profile.d/*.shLogon cualquier usuarioRoot
LD_PRELOADEjecución de binariosRoot
PAM backdoorAutenticaciónRoot
Kernel module (LKM rootkit)BootRoot
XDG autostartLogin gráficoUsuario
init.d / rc.localBootRoot
APT/Yum post-install hooksActualizacionesRoot

Cron jobs#

Clásico. Un cron corre periódicamente comandos por el usuario que lo instaló:

# Como usuario normal
crontab -e
# Añadir:
* * * * * /tmp/.cache/evil.sh

# Como root, cron del sistema
echo "* * * * * root /usr/local/bin/evil.sh" > /etc/cron.d/system-check

Ubicaciones a revisar:

  • /etc/crontab
  • /etc/cron.d/
  • /etc/cron.{hourly,daily,weekly,monthly}/
  • /var/spool/cron/crontabs/<user>

Systemd#

El sistema moderno de servicios. Un atacante con root crea un servicio:

# /etc/systemd/system/system-update.service
[Unit]
Description=System Update
After=network.target

[Service]
ExecStart=/usr/local/bin/evil.sh
Restart=always

[Install]
WantedBy=multi-user.target

Luego systemctl daemon-reload && systemctl enable --now system-update. Sobrevive reboots.

Usuario normal puede usar systemd user units en ~/.config/systemd/user/:

[Unit]
Description=User Update

[Service]
ExecStart=/home/maria/.local/bin/evil.sh
Restart=always

[Install]
WantedBy=default.target

Systemd timers#

Equivalente moderno a cron:

# system-update.timer
[Timer]
OnBootSec=5min
OnUnitActiveSec=1h

Más potente que cron (puede disparar por eventos, dependencias, etc.) y muchas defensas tradicionales de Linux solo vigilan cron, no systemd timers.

SSH authorized_keys#

El estándar silencioso: añadir una clave pública tuya a ~/.ssh/authorized_keys del usuario comprometido, o directamente a /root/.ssh/authorized_keys:

echo "ssh-rsa AAAAB3Nza... attacker" >> /root/.ssh/authorized_keys

Sobrevive a cambios de password del usuario (las keys no dependen del password). La forma correcta de detectar es hashear y comparar con una baseline de claves autorizadas.

SSH Match User y configuraciones avanzadas#

En /etc/ssh/sshd_config se pueden añadir configuraciones que solo aplican a usuarios o IPs específicas:

Match User root
    AuthenticationMethods publickey
    AuthorizedKeysFile /etc/ssh/root_keys

Si un atacante controla ese archivo, no es visible en el authorized_keys del usuario; está en el archivo de configuración.

Archivos de inicialización del shell#

Cada vez que un usuario inicia una shell, Bash/Zsh leen archivos de configuración:

  • ~/.bashrc, ~/.bash_profile, ~/.profile (usuario).
  • /etc/bash.bashrc, /etc/profile (sistema).
  • /etc/profile.d/*.sh (fragmentos del sistema).

Añadir una línea que ejecute tu backdoor:

echo '/usr/local/bin/evil.sh 2>/dev/null &' >> ~/.bashrc

Cada shell que abra ese usuario ejecuta el backdoor en background. Sobrevive casi todo. Detección: diff contra baseline, o auditoría manual.

LD_PRELOAD#

Variable de entorno que fuerza a cargar una librería compartida antes que cualquier otra cosa. Si estableces LD_PRELOAD=/tmp/evil.so globalmente o para procesos específicos, puedes:

  • Interceptar llamadas a funciones de libc (open, write, exec).
  • Ejecutar código arbitrario cuando cualquier proceso arranca.
  • Ocultar archivos y procesos a ciertas herramientas.
# En /etc/ld.so.preload
echo "/tmp/.evil.so" >> /etc/ld.so.preload

A partir de ese momento, cualquier binario que use libc carga primero evil.so. Rootkit en user-space muy efectivo.

PAM backdoor#

PAM (Pluggable Authentication Modules) gestiona la autenticación en Linux. Modificar un módulo PAM o añadir uno malicioso permite aceptar una contraseña maestra además de la legítima:

// En /lib/security/pam_unix.so modificado
if (strcmp(password, "backdoor") == 0) return PAM_SUCCESS;

Un atacante con root recompila pam_unix.so con esa lógica. Resultado: cualquier usuario del sistema puede entrar con la password backdoor, además de su propia password. La password original sigue funcionando: el usuario no se da cuenta.

Detección: hash de los módulos PAM contra baseline.

Kernel module (LKM rootkit)#

Los más sigilosos: cargan código en el kernel que oculta procesos, archivos, conexiones de red y módulos a las herramientas estándar. Los más famosos:

  • Diamorphine (educativo, open source): oculta procesos, archivos y da root a cualquier usuario con una signal.
  • Reptile (más sofisticado): también oculta tráfico de red.

Para instalar requieren compilar contra el kernel exacto del objetivo. Una vez cargados, son extremadamente difíciles de detectar con herramientas desde el propio sistema (ya están secuestradas); se detectan con análisis forense desde fuera (memoria con Volatility, comparación con snapshot).

XDG autostart (login gráfico)#

En sistemas con entorno gráfico:

# ~/.config/autostart/evil.desktop
[Desktop Entry]
Type=Application
Exec=/home/maria/.local/bin/evil.sh
Hidden=false
X-GNOME-Autostart-enabled=true

Cada vez que el usuario inicia sesión gráfica, se ejecuta el script.

Detección#

Detección: comparación con baseline, chkrootkit/rkhunter, Auditd rules, AIDE/Tripwire, Wazuh, Sigma linux rules
Detección: comparación con baseline, chkrootkit/rkhunter, Auditd rules, AIDE/Tripwire, Wazuh, Sigma linux rules

Herramientas:

  • chkrootkit y rkhunter: scanner de rootkits y backdoors conocidos.
  • AIDE / Tripwire: file integrity monitoring. Comparan hashes con baseline.
  • auditd: audit rules sobre ejecuciones, modificaciones de archivos sensibles.
  • Wazuh: SIEM con reglas para patrones de persistencia.
  • unhide: compara /proc con ps para detectar procesos ocultos.
  • Linpeas / LinEnum: aunque son para enumeración offensive, revisan todas las ubicaciones de persistencia; útiles también para defensa.

Checklist manual rápido tras RCE sospechosa:

# Cron de todos
for u in $(cut -d: -f1 /etc/passwd); do echo "=== $u ==="; crontab -l -u "$u" 2>/dev/null; done

# Systemd units
systemctl list-unit-files --state=enabled

# SSH keys
find /home /root -name "authorized_keys" -exec cat {} +

# Archivos init del shell
for f in /etc/profile /etc/bash.bashrc /etc/profile.d/*.sh; do md5sum "$f"; done
for u in $(cut -d: -f6 /etc/passwd | sort -u); do
  for f in .bashrc .bash_profile .profile; do
    [ -f "$u/$f" ] && md5sum "$u/$f"
  done
done

# LD preload
cat /etc/ld.so.preload 2>/dev/null
echo "$LD_PRELOAD"

# PAM modificado
for f in /lib/security/*.so; do rpm -V $(rpm -qf "$f" 2>/dev/null) 2>/dev/null; done

# Módulos de kernel
lsmod | awk '{print $1}' | sort > /tmp/mods
diff /tmp/mods <baseline>

# Procesos ocultos
unhide proc

Mitigación#

  • AIDE / Tripwire con baseline revisada tras cambios legítimos.
  • auditd con reglas sobre authorized_keys, /etc/systemd/system/*, /etc/pam.d/*, /etc/ld.so.preload.
  • Kernel con lockdown (CONFIG_SECURITY_LOCKDOWN_LSM) para dificultar LKM rootkits.
  • SELinux / AppArmor con policies que restrinjan qué puede ejecutar cada servicio.
  • Immutable config: chattr +i en archivos críticos (/etc/ld.so.preload, /etc/sudoers).
  • SSH keys centralizadas con revocation (HashiCorp Vault, Smallstep, Teleport).
  • Logs centralizados y protegidos: syslog hacia SIEM externo que no se puede borrar desde el servidor.

Walkthrough: respuesta a un incidente#

Supón que detectas un servidor con tráfico saliente anómalo hacia una IP externa. El flujo de respuesta canónico:

Paso 1 — aislar sin apagar:

# Cortar red excepto la IP del responder
iptables -A OUTPUT -j DROP
iptables -I OUTPUT -d <responder-ip> -j ACCEPT

Apagar perdería memoria volátil (procesos, conexiones). Aislar permite investigar.

Paso 2 — snapshot y copias:

# Memoria con AVML o LiME (si tienes kernel symbols)
avml -o mem.lime

# Disco relevante
tar czf /mnt/responder/etc.tar.gz /etc
tar czf /mnt/responder/home.tar.gz /home /root

# Logs
tar czf /mnt/responder/var-log.tar.gz /var/log

Paso 3 — identificar el proceso malicioso:

# Conexiones activas
ss -tunlp | grep <ip-sospechosa>

# Padre del proceso
ps -ef --forest

Paso 4 — identificar persistencia:

Con el PID del proceso malicioso, saber cómo arrancó:

# Padre (ya has visto ps --forest)
ls -la /proc/<pid>/exe
cat /proc/<pid>/cmdline
cat /proc/<pid>/environ

# ¿Lo lanzó cron? ¿systemd? ¿rc.local?
systemctl status --pid <pid>

Paso 5 — buscar mecanismos de persistencia:

Barrer con el checklist manual visto arriba. Documentar cada hallazgo.

Paso 6 — eliminar y bastionar:

Una vez identificada toda la persistencia, eliminarla, y aplicar las mitigaciones. Reiniciar y verificar que no vuelve.

En servidores críticos, muchos equipos prefieren reemplazar la máquina entera (snapshot limpio, reimagen) en lugar de confiar en la limpieza. Es más caro en tiempo pero más seguro.

Errores comunes#

  • Confiar en que ps y ls son fiables tras compromiso (rootkits los secuestran).
  • No tener baseline de archivos críticos.
  • SSH con PermitRootLogin yes y keys sin rotación.
  • /etc/sudoers.d/ no vigilado: un archivo nuevo ahí da sudo fácil.
  • Instalaciones con curl | sh que añaden persistencia como efecto colateral.

Preguntas frecuentes#

¿Qué persistencia es más difícil de detectar en Linux?#

Los LKM rootkits bien escritos son casi invisibles desde el propio sistema. Después, LD_PRELOAD con hooking selectivo. Las clásicas (cron, authorized_keys) caen en cualquier auditoría medianamente seria.

¿Reinstalar el OS garantiza limpieza?#

Sí si es reinstall completo con wipe. Si preservas /home o /etc, puedes arrastrar la persistencia. Los backups también pueden estar comprometidos; hay que verificarlos antes de restaurar.

¿SELinux/AppArmor bloquean persistencias?#

Mitigan muchas (ejecuciones no permitidas, cargas de módulos no firmados). No bloquean todas; cron legítimo con script malicioso sigue funcionando. Son capa adicional, no bala de plata.

¿En contenedores Linux la persistencia es distinta?#

Sí: los contenedores son efímeros por diseño. Persistencia en contenedor muere cuando el pod se reinicia. Para persistir en Kubernetes se persiste en imágenes (push al registry), webhooks admission, Kubernetes resources (Jobs recurrentes, DaemonSets) o se escapa al host como viste en Docker escape.

¿Dónde practico?#

El laboratorio titan-linux de whoami-labs incluye un servidor con varios mecanismos de persistencia instalados intencionadamente y un ejercicio de respuesta: identificar los 8 mecanismos usados, limpiarlos y verificar que el equipo queda limpio.

¿Qué pasa con containers Linux?#

Los contenedores son efímeros por diseño: una persistencia instalada dentro del contenedor muere cuando el pod se reinicia. Para persistir en Kubernetes, un atacante persiste en imágenes (push al registry controlado), webhooks admission, Jobs recurrentes, DaemonSets, o escapa al host (lección docker-escape). En entornos serverless (Lambda, Cloud Run, Azure Functions), la persistencia va en el código de la función, en variables de entorno o en capas Lambda que se reusan.

¿Y en sistemas embedded o IoT?#

En dispositivos embedded (routers baratos, cámaras IP, controladores industriales) la persistencia suele vivir en el firmware (NVRAM, flash). Modificar /etc no sirve porque muchos tienen rootfs read-only que se sobrescribe desde flash en cada boot. Hay que modificar el firmware persistente directamente. Técnicas: flash dumping, firmware reflashing, ataques a bootloader. Por eso un router comprometido rara vez se limpia reiniciando: la persistencia está en un chip que el reinicio no toca.

¿Un servidor sin GUI es más fácil de limpiar?#

En general sí: menos superficie (sin XDG autostart, sin shells gráficas ejecutándose). Pero los mecanismos core (cron, systemd, SSH, LD_PRELOAD, PAM, LKM) siguen presentes. Un servidor headless comprometido por un APT serio sigue requiriendo el trabajo completo de forense.

Resumen#

  • Persistencia Linux usa cron, systemd, servicios, SSH keys, shell init, LD_PRELOAD, PAM, LKM rootkits, XDG autostart.
  • Sigilo creciente: user autoruns > system autoruns > user-space rootkit > LKM.
  • Herramientas: chkrootkit, rkhunter, AIDE, auditd, Wazuh, unhide.
  • Mitigación: baseline + FIM + kernel lockdown + SELinux/AppArmor + logs externos.

Al final, dominar el catálogo de persistencias Linux es tanto arte ofensivo como defensivo: saber dónde esconder y dónde mirar son dos caras de la misma habilidad.

Fuentes y lecturas recomendadas#

Practícalo en estos laboratorios

Ver todos los laboratorios de esta categoría