
¿Está tu host Linux expuesto a CVE-2026-98432?
Comprueba si un host Linux puede estar expuesto a CVE-2026-98432, el use-after-free en el handshake TCP IPv4 del kernel. Forma parte de shouldipanic.ch.
Un SYN que transporta opciones IPv4 (Record Route, Timestamp) llega a código que lee las opciones después de que estas ya puedan haber sido liberadas. Basta con cualquier puerto TCP IPv4 abierto, no se necesita iniciar sesión. Está presente en todos los kernels desde 4.4 (2016) hasta la versión corregida de cada rama: 5.10.272, 5.15.223, 6.1.190, 6.6.159, 6.12.113, 6.18.56, 7.2.10, 7.3. El CNA del kernel le asigna 9.8; se ha reportado un exploit funcional de root remoto contra servidores con listeners MPTCP (no publicado). La corrección es invisible en el tráfico de red: un kernel corregido responde exactamente igual que uno sin parchear.
Eso condiciona lo que puede afirmar cada modo:
--on-host lee el kernel en ejecución en la propia máquina: su versión,
el changelog del paquete y si hay un kernel más reciente esperando un reinicio.Server y muestra si esa distribución
ya ha publicado un kernel corregido. Es una pista sólida, no un veredicto: detrás de un
proxy inverso o dentro de un contenedor, el kernel pertenece a otra máquina.kernel98scan se presenta en dos formas:
kernel98scan.py: un único archivo Python sin dependencias. En un host,
--on-host da el veredicto real; apuntado a direcciones hace fingerprinting de distribuciones
sobre rangos de IP, con un escaneo de puertos y descubrimiento de vhosts.
curl -LO https://github.com/akaihigurashi/kernel98scan/releases/latest/download/kernel98scan.py
python3 kernel98scan.py --on-host # real verdict for THIS machine
Python 3.8 o superior, no hay nada que instalar. Ejemplos:
python3 kernel98scan.py --on-host # read the running kernel
python3 kernel98scan.py 203.0.113.10 # outside: distribution + fix status
python3 kernel98scan.py 203.0.113.0/24 # a whole range from outside
python3 kernel98scan.py 203.0.113.10 --ip-options # + do IP-options packets get through?
python3 kernel98scan.py 203.0.113.10 -o out.csv # CSV output
python3 kernel98scan.py -h # all options
En el host (--on-host, código de salida 2 / 1 / 0 / 3):
| Estado | Significado |
|---|---|
VULNERABLE | Nada indica que la corrección esté en el kernel en ejecución |
CHECK | Hay un kernel corregido instalado, pero el antiguo sigue en ejecución: reiniciar |
FIXED | El changelog del kernel menciona el CVE, o un kernel upstream sin parches está en la versión corregida |
Los kernels de proveedor (Ubuntu, RHEL, Proxmox) aplican las correcciones retroactivamente sobre números de versión antiguos, por lo que en su caso solo cuenta el changelog. También muestra si MPTCP está activado (el exploit reportado pasa por listeners MPTCP) y tcp_migrate_req.
Desde fuera:
| Estado | Significado |
|---|---|
POSSIBLY VULNERABLE | El banner muestra una distribución de Linux: o bien aún no tiene un kernel corregido (Ubuntu, RHEL y sus reconstrucciones a día de hoy) o bien lo tiene, pero hay que instalarlo y arrancarlo |
UNKNOWN | La distribución no es visible, o responde un proxy/CDN |
NOT LINUX | La cabecera Server apunta a un host que no es Linux |
VULNERABLE y PATCHED nunca se reportan desde fuera. El estado de corrección por distribución es una tabla en check.py (FIX_STATUS, FIXED_TABLE), actualizada con cada versión del proveedor.
| Opción | Qué hace |
|---|---|
--on-host | Lee el kernel en ejecución en esta máquina y da un veredicto real |
--ip-options | Una conexión cuyo SYN lleva una opción IP Record-Route (ver más abajo) |
-o FILE | Además, escribe todos los resultados en CSV |
-a / -v | Muestra los resultados ocultos / muestra la evidencia |
-d | Depuración: cada petición, cabecera y coincidencia |
-q | Sin banner (para scripts) |
-V | Imprime la versión |
-h | Todas las opciones |
--ip-options (opcional, solo línea de comandos)Abre una conexión normal y otra cuyo SYN lleva una opción IP Record-Route vacía, e informa si dichos paquetes llegan al host (reach), se descartan por el camino (filtered) o el puerto está cerrado (noport). Esa es la forma de paquete a través de la cual se alcanza el fallo, por lo que está desactivada por defecto y nunca se ejecuta en la página web: úsala en tus propios hosts, por ejemplo para confirmar que tu firewall descarta las opciones IP. Una sola conexión no puede ganar la carrera que necesita el fallo, y un resultado filtered puede deberse a cualquier red intermedia.
nft add table ip cve98432
nft add chain ip cve98432 pre '{ type filter hook prerouting priority -300; }'
nft add rule ip cve98432 pre meta l4proto tcp ip hdrlength != 5 drop
sysctl -w net.mptcp.enabled=0 # if you don't need MPTCP
Descarta paquetes TCP IPv4 que llevan cualquier opción IP. No es persistente entre reinicios y no sustituye al kernel corregido.
docker run -d --name kernel98scan --restart unless-stopped \
-p 127.0.0.1:8092:8092 \
-e PANIC_ORIGIN=https://kernel98scan.example.ch \
-v kernel98scan-data:/data \
ghcr.io/akaihigurashi/kernel98scan:latest
O con Compose: descarga docker-compose.yml, establece
PANIC_ORIGIN, luego docker compose up -d. El contenedor escucha solo en localhost;
coloca un proxy inverso con TLS delante de él
(ejemplo de nginx).
| Variable | Predeterminado | Propósito |
|---|---|---|
PANIC_ORIGIN | – | Dirección pública. Las solicitudes de otros orígenes se rechazan. Establécelo. |
PANIC_OPERATOR / PANIC_CONTACT | – | Sección de contacto opcional al pie de la página |
PANIC_IMPRINT_URL | – | Enlace opcional a un aviso legal existente |
PANIC_TRUSTED_PROXY | 127.0.0.1,::1 | Proxies cuyo encabezado X-Real-IP es de confianza |
PANIC_DENY_HOSTS / PANIC_DENY_IPS | – | Nunca comprobar estos dominios / direcciones |
PANIC_AUDIT_DIR | /data/audit | Archivos de registro de auditoría diarios |
PANIC_RETENTION_DAYS | 30 | Los archivos de auditoría más antiguos que esto se eliminan |