Saltar al contenido
Volver al Blog

tutoriales · 9 min de lectura

regreSSHion (CVE-2024-6387): pre-auth RCE en sshd, con asteriscos

Qualys publica el 1 de julio una race condition en el signal handler de OpenSSH que reintroduce un bug parcheado en 2006. Pre-auth RCE como root suena a apocalipsis. Las letras pequeñas (glibc, x86, 10.000 conexiones, 6–8 horas de race window) devuelven la noticia a su sitio.

· Manuel López Pérez · tutoriales

Qualys publica el 1 de julio una race condition en el signal handler de OpenSSH que reintroduce un bug parcheado en 2006. Pre-auth RCE como root suena a apocalipsis. Las letras pequeñas (glibc, x86, 10.000 conexiones, 6–8 horas de race window) devuelven la noticia a su sitio.

1 de julio de 2024. Qualys publica un advisory con un nombre tan llamativo como su titular: regreSSHion, race condition en el signal handler SIGALRM de sshd que abre un pre-auth RCE como root. CVSS 8.1, NVD lo publica el mismo día. OpenSSH 9.8p1 sale al servidor el 6 de julio con el parche. La noticia rebota en cada feed: “RCE pre-auth en SSH, sin credenciales, como root, en millones de servidores expuestos”.

El detalle que Qualys explica con calma en el advisory y que el ciclo de noticias amputa deja la historia en su sitio. La explotación requiere glibc, x86 i386 más manejable que amd64, ~10.000 conexiones y 6 a 8 horas de race window para acertar a la disposición de heap correcta. En OpenBSD no funciona. En servidores con MaxStartups bajo no funciona. Y en realidad, el bug que se explota es de 2006, parcheado entonces, reintroducido en 2020 sin que nadie lo notara durante cuatro años.

Lab: imagen Debian con OpenSSH 9.6p1 y glibc 2.36, PoC público de Qualys. Servidor sin contramedidas, control completo del cliente. No se envía contra hosts externos.

El bug en una frase

En sshd, cuando un cliente abre conexión y no completa autenticación en LoginGraceTime (120 segundos por defecto), el kernel entrega SIGALRM al proceso. El handler de la señal llama a sigdie(), que internamente llama a syslog() para registrar el incidente. syslog() no es async-signal-safe: en glibc llama a malloc() y free(), y dentro de un signal handler eso es Pandora.

Lista canónica de funciones async-signal-safe: man 7 signal-safety en Linux. _exit, signal, write, read, kill, pause, alarm y unas setenta más. syslog() no está. malloc()/free() tampoco. Cualquier handler que llame a algo fuera de esa lista es un bug latente esperando una señal en el momento equivocado.

Si la señal llega exactamente cuando el proceso sshd está en medio de un malloc() (por ejemplo, montando la estructura PAM tras recibir el username del cliente) el segundo malloc() desde el handler corrompe el heap arena de glibc. A partir de ahí, con paciencia, se manipula el heap para que el siguiente puntero apunte a una shellcode controlada por el atacante. El proceso es root (pre-fork, antes del privilege drop). RCE.

La regresión que tarda cuatro años

Esta es la parte interesante. En 2006 se reporta CVE-2006-5051, con la misma forma: signal handler llamando a funciones no async-signal-safe. La fix en su día consistió en envolver sigdie() en una guarda condicional, controlada por una macro DO_LOG_SAFE_IN_SIGHAND. Cuando estaba definida (por defecto en glibc), las llamadas problemáticas se sustituían por _exit(14): el proceso termina sin llamar a ningún destructor, sin tocar el heap, sin syslog.

En octubre de 2020, OpenSSH 8.5p1 introduce el commit 752250c que reestructura el código de logging. En ese commit, las protecciones #ifdef DO_LOG_SAFE_IN_SIGHAND desaparecen. No es un cambio malicioso ni controvertido: el refactor pasa code review, los tests siguen verdes (no había test para esta clase de race), y nadie nota que la guardia ha caído.

El cambio relevante en log.c, recortado del commit:

-void
-sigdie(const char *fmt,...)
-{
-#ifdef DO_LOG_SAFE_IN_SIGHAND
-	va_list args;
-
-	va_start(args, fmt);
-	do_log(SYSLOG_LEVEL_FATAL, fmt, args);
-	va_end(args);
-#endif
-	_exit(1);
-}
+void
+sshsigdie(const char *file, const char *func, int line, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	sshlogv(file, func, line, 0, SYSLOG_LEVEL_FATAL, fmt, args);
+	va_end(args);
+	_exit(1);
+}

DO_LOG_SAFE_IN_SIGHAND no está definido en sistemas glibc, así que antes del refactor el sigdie() original hacía _exit(1) directo y nunca tocaba syslog. Después, el nuevo sshsigdie() llama incondicionalmente a sshlogvdo_logsyslog. La guarda ya no está. En glibc, cada SIGALRM por timeout intenta loguear desde el handler.

Cuatro años después, Bharat Jogi y el equipo de Qualys auditan el código de signal handlers tras un proyecto interno sobre clases de bugs históricas y encuentran que la protección se ha perdido. Reproducen el ataque. Reportan. Damien Miller y Theo de Raadt lo confirman; sale el parche.

El timeline en una tabla:

FechaEvento
2006CVE-2006-5051 reportado. Fix con _exit(14) y macro DO_LOG_SAFE_IN_SIGHAND
2006OpenSSH 4.4p1 incluye el parche
Oct 2020Commit 752250c en OpenSSH 8.5p1 elimina la guardia. Regresión sin reportar
2020 – 2024Bug presente, no reportado
Mayo 2024Qualys identifica la regresión durante auditoría
1 jul 2024Qualys publica advisory; OpenSSH avisa de la fix inminente
6 jul 2024OpenSSH 9.8p1 con la fix

Las versiones afectadas son OpenSSH 8.5p1 a 9.7p1, en sistemas glibc. La 4.4p1 a 8.4p1 incluyen el parche original. Antes de la 4.4p1, vulnerables también.

La explotabilidad real, sin titulares

El advisory de Qualys es honesto sobre las condiciones:

  • glibc. El bug existe porque syslog() de glibc llama a malloc(). En musl, en bionic, en OpenBSD libc, el syslog() está implementado de otra manera y no toca el heap. Distros musl-based (Alpine sin glibc) no son explotables por esta vía.
  • i386 más fácil que amd64. En 64 bits, la entropía del ASLR es mucho mayor y la posición del heap es menos predecible. Qualys reporta exploit estable en 32 bits; en amd64 lo menciona como “harder” sin demos completos en el advisory inicial.
  • MaxStartups y LoginGraceTime. Para que la race se dispare, hay que abrir muchas conexiones y dejar que cada una llegue a los 120 segundos sin autenticar. Por defecto MaxStartups 10:30:100 permite 10 concurrentes antes de empezar a tirar; con esos parámetros y el modelo de Qualys, una explotación exitosa requiere unas 6 a 8 horas de tráfico sostenido contra el servidor. Reducir LoginGraceTime a 0 desactiva el handler vulnerable por completo —es la mitigación oficial para quien no puede parchear de inmediato.
  • Tasa de éxito. ~10.000 intentos por explotación exitosa según el modelo. Cada intento puede tardar segundos o minutos según la carga.

En la práctica, un servidor SSH público con logging activo y monitorizado detecta el ataque en la primera media hora. Diez mil intentos de auth fallidos contra un host es ruido al que cualquier SIEM razonable salta. Las explotaciones in-the-wild tras la publicación se han limitado, en lo que se ha hecho público, a redes internas con baja monitorización o servidores expuestos sin logging revisado.

Esto no es para minimizar: es pre-auth RCE como root en sshd y eso es serio. Pero el pánico del 1 de julio (“todo el mundo va a caer hoy”) no se materializa. Lo que se materializa es la oleada de patch fast legítima y el redescubrimiento de que LoginGraceTime 0 existía.

Lab: reproducir el setup sin reproducir el exploit

Para confirmar la versión afectada y el comportamiento del signal handler sin armar el exploit completo, basta un Docker:

# Dockerfile — sshd vulnerable, sin exploit
FROM debian:bookworm-slim
RUN apt-get update && \
    apt-get install -y openssh-server=1:9.6p1-* && \
    mkdir /run/sshd
RUN useradd -m -s /bin/bash lab && echo 'lab:lab' | chpasswd
RUN sed -i 's/^#LoginGraceTime.*/LoginGraceTime 30/' /etc/ssh/sshd_config && \
    sed -i 's/^#LogLevel.*/LogLevel DEBUG2/' /etc/ssh/sshd_config
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D", "-e"]

LoginGraceTime 30 acelera la prueba. Lanzar el contenedor:

docker build -t sshd-vuln . && docker run -d --name sshd-vuln -p 2222:22 sshd-vuln
docker exec sshd-vuln sshd -V 2>&1
# OpenSSH_9.6p1 Debian-...

Conectar y dejar la sesión a medias (sin enviar username) durante 30 segundos. En la salida de docker logs aparece:

sshd[24]: Timeout, client not responding from user-not-yet-authenticated
sshd[24]: fatal: Timeout before authentication for ::ffff:172.17.0.1 port ...

Ese fatal: se imprime desde el sigdie() que llama a syslog() desde el handler. Ahí está el bug. La race entre ese momento y un malloc() concurrente es lo que el exploit aprovecha.

Aplicar la mitigación oficial sin parchear:

docker exec sshd-vuln sed -i 's/^LoginGraceTime.*/LoginGraceTime 0/' /etc/ssh/sshd_config
docker exec sshd-vuln pkill -HUP sshd

Con LoginGraceTime 0 no se entrega SIGALRM, sigdie() no se invoca por timeout, race cerrada.

Lo que enseña el bug

  1. Una _exit(14) perdida en un refactor tarda cuatro años en explotar. El code review que dejó pasar el commit de 2020 era razonable a nivel humano: el refactor es legítimo, los tests pasaban. Lo que faltaba era un test específico para signal-handler-safety. Las clases de bug que se reintroducen son las que ningún test cubre porque “ya se arregló”.
  2. man 7 signal-safety existe y nadie lo lee. La lista de funciones async-signal-safe es corta y conocida; syslog() no está en ella, malloc() tampoco. Cualquier auditoría de signal handlers que hubiera mirado la lista habría notado la regresión en 2020.
  3. CVSS sin contexto induce a error. “Pre-auth RCE como root” en abstracto es CVSS 9.8+. Con las restricciones de glibc + x86 + 10.000 intentos + horas, el NVD le pone 8.1 (AC:H, attack complexity high). Esa H importa para priorizar.
  4. OpenBSD se beneficia de su propia paranoia. El bug no se reproduce en OpenBSD porque su syslog() no toca el heap. La diferencia es que OpenBSD invierte en audit lockdown del entorno C estándar; la regresión seguramente se habría detectado allí en su propio CI antes de llegar a un release.

Mitigaciones, en orden

  1. Parchear a OpenSSH 9.8p1+. Distros publican backports el mismo 1 de julio:

    PlataformaAdvisoryEstado
    Debian 12 (bookworm)DSA-5724-1parcheado
    Ubuntu 22.04 / 24.04USN-6859-1parcheado
    RHEL 9RHSA-2024:4312parcheado
    RHEL 7 / 8no afectado (OpenSSH < 8.5p1)
    SUSE / openSUSE Leap 15.6SUSE-SU-2024:2304-1parcheado
    FreeBSD 13.x / 14.xFreeBSD-SA-24:11.opensshparcheado
    Alpineno afectado (musl libc)
    OpenBSDno afectado
  2. LoginGraceTime 0 como bridge mientras no se pueda parchear. Desactiva el handler vulnerable. Tiene la contrapartida de que conexiones abiertas sin autenticar quedan abiertas indefinidamente —combínalo con un firewall que limite conexiones concurrentes por IP.

  3. Auditar logs. El ataque es ruidoso. Timeout before authentication repetido masivamente desde una IP es la firma.

  4. Si la distro usa musl (Alpine sin glibc, por ejemplo), el bug no aplica. Pero parchear igual no cuesta nada y es buena higiene.

Detección rápida

Versión local, comparada con el rango vulnerable (8.5p1 a 9.7p1):

ssh -V
# OpenSSH_8.5p1 a OpenSSH_9.7p1, en glibc  → vulnerable
# OpenSSH_8.4p1 o anterior                 → no afectado por esta CVE
# OpenSSH_9.8p1 o superior                 → parcheado

Top 20 IPs con timeouts de pre-auth en SSH en las últimas 24 h (sistema con systemd):

journalctl -u ssh --since "1 day ago" \
  | grep "Timeout before authentication" \
  | grep -oE 'from [0-9.]+' | awk '{print $2}' \
  | sort | uniq -c | sort -rn | head -20

Patrón de detección, boceto en Sigma:

title: Posible regreSSHion (CVE-2024-6387) probing
logsource:
  service: sshd
detection:
  selection:
    Message|contains: 'Timeout before authentication'
  condition: selection | count(src_ip) > 20 within 10m
falsepositives:
  - clientes con red inestable
  - escáneres autenticados con MaxStartups bajo
level: medium

Distinto de un brute-force normal: aquí no aparece Failed password ni Failed publickey correlacionados, solo timeouts repetidos. Es la firma de un exploit que abre sesiones y las deja caducar hasta que acierta el race.

Referencias

Volver al Blog

Posts Relacionados

Ver Todos los Posts »
Cisco ASA: ArcaneDoor vuelve con CVE-2025-20333 y bootkit en la ROM

tutoriales · 16 min

Cisco ASA: ArcaneDoor vuelve con CVE-2025-20333 y bootkit en la ROM

CVE-2025-20362 (auth bypass por path traversal, variante de un bug de 2018) + CVE-2025-20333 (buffer overflow en un script Lua de la WebVPN). Encadenadas, RCE pre-auth como root en cualquier ASA/FTD expuesta a internet. UAT4356 lleva explotándolas desde mayo de 2025 y deja persistencia en ROMMON con un bootkit GRUB (RayInitiator) que sobrevive a reboot y a upgrade.

· Manuel López Pérez

Cleo MFT CVE-2024-50623: Cl0p cierra el año con el tercer managed file transfer

tutoriales · 11 min

Cleo MFT CVE-2024-50623: Cl0p cierra el año con el tercer managed file transfer

Huntress detecta el 3 de diciembre explotación as zero-day de un bug en Cleo Harmony, VLTrader y LexiCom. El patch inicial 5.8.0.21 no mitiga; sale CVE-2024-55956 y un segundo patch 5.8.0.24. Cl0p reivindica el 14 de diciembre. Tercer MFT del grupo en dos años.

· Manuel López Pérez

Ivanti Connect Secure: la chain pre-auth RCE que abrió 2024

tutoriales · 11 min

Ivanti Connect Secure: la chain pre-auth RCE que abrió 2024

CVE-2023-46805 (auth bypass por path traversal) + CVE-2024-21887 (command injection en /api/v1/license/keys-status). Encadenadas, RCE pre-auth como root. Volexity las publica el 10 de enero tras detectar explotación as zero-day por UTA0178 desde diciembre. El parche oficial llega el 31 de enero, tres semanas después.

· Manuel López Pérez