
PoC sin privilegios para CVE-2026-74586, un use-after-free en SCTP ASCONF del kernel de Linux. Proporciona un disparador de paquetes sin procesar, métricas de fiabilidad y un análisis detallado de armamento, incluidos desplazamientos de estructuras y primitivas de spray.
Un PoC autocontenido y sin privilegios para CVE-2026-74586, un use-after-free en
el manejo de ASCONF (reconfiguración dinámica de direcciones) de SCTP en el kernel de Linux.
El crédito por el fallo en sí es para Qing Ming, quien lo reportó upstream; la
corrección es el commit beb33f8ee1ca ("sctp: clear new_transport when removing a
peer"), fusionado el 2026-08-12 con Cc: stable — Red Hat lo clasifica como importante,
los rastreadores de terceros lo sitúan en CVSS 9.8.
Lo que este repositorio añade sobre el aviso:
sctp_process_asconf_param() almacena el transport creado por un parámetro ADD-IP
en asoc->new_transport. Un DEL-IP para la misma dirección en el
mismo chunk ASCONF libera ese transport mediante sctp_assoc_rm_peer(),
que (antes de la corrección) no limpia new_transport. Cuando
sctp_process_asconf() retorna, sctp_sf_do_asconf() actúa sobre el puntero
obsoleto — en un kernel estándar el efecto visible es un HEARTBEAT emitido
hacia la dirección del transport recién liberado; bajo KASAN es un
informe de slab-use-after-free.
Un solo chunk ASCONF es suficiente:
[Address Parameter L] [ADD-IP G] [DEL-IP G]
gcc -O2 -Wall -o poc poc.c
./poc
Sin root. El PoC hace unshare de un espacio de nombres de usuario + red, establece los sysctls de SCTP en él, levanta una asociación multi-homed sobre loopback y luego inyecta el ASCONF manipulado con un socket raw.
Salida esperada en un kernel vulnerable:
[+] Association established (no AUTH)
[+] server_vtag=0x... captured_tsn=0x...
[*] serial=0x... (= initial_tsn)
[+] Injected 60 bytes
[9222->9111 vt=...] ASCONF-ACK(0x80) len=8
[+] ASCONF-ACK -> server processed ADD-IP + DEL-IP
[9222->9111 vt=...] HB(0x04) len=60 <- kernel using freed transport
[9111->9222 vt=...] ABORT(0x06) len=8
[+] GhostTransport UAF TRIGGERED (ASCONF-ACK received)
[+] HEARTBEAT to ghost address observed (dangling transport USED)
El HEARTBEAT después del ACK es la parte interesante: ese paquete solo
existe porque el kernel recorrió el new_transport colgante. El cliente
ABORTa justo después porque el heartbeat llegó de la nada.
Nada de esto es investigación novedosa, simplemente está lo suficientemente poco documentado como para costar una tarde, así que aquí está para el siguiente:
sctp_rcv() verifica el checksum y descarta si
no coincide. Calcula CRC32C (polinomio 0x82F63B78, reflejado) sobre todo
el paquete SCTP con el campo checksum a cero.sctp_auth_recv_cid() se ejecuta en
la ruta de entrada antes de la máquina de estados y descarta ASCONF no
autenticado incluso cuando addip_noauth_enable=1 — ese sysctl solo
relaja la comprobación dentro de sctp_sf_do_asconf(). La salida es no
negociar AUTH a nivel de socket en primer lugar (sin setsockopt de
SCTP_AUTH_SUPPORTED), de modo que peer.auth_capable permanece en false
y ambas comprobaciones pasan.La etiqueta Fixes: del commit upstream es 6af29ccc223b ("sctp: Bundle
HEARTBEAT into ASCONF_ACK"), así que el código es antiguo. Corregido en
mainline y backportado a la serie estable (Debian tracker: corregido desde
7.1.9-1 en sid, 6.12.107 en trixie-security). Kali rolling a fecha de
2026-09-09 incluye 7.1.5-1kali1 en todas las suites, lo cual es anterior a
todo ello — esa es la principal relevancia práctica de este PoC.
Para cualquiera que lo lleve más lejos — medido contra 7.1.5 en QEMU,
offsets de objdump -d del sctp.ko distribuido:
| campo | offset |
|---|---|
| flowi (88 bytes) | 0x30 |
| ipaddr | 0x88 |
| af_specific | 0xa8 |
| asoc | 0xb0 |
| dst | 0xe0 |
| state | 0x15c |
| rcu | 0x2b0 |
sctp_association->new_transport se encuentra en 0x6b0. La tabla proviene de
pahole -C sctp_transport /sys/kernel/btf/vmlinux en el kernel en ejecución —
una revisión anterior de esta tabla llevaba un offset de ipaddr incorrecto
(0x68, por un error al contar flowi); BTF es la fuente de verdad.
sctp_transport_destroy_rcu), no
hay doble liberación en esta ruta. Si esperabas cerrar un socket y
obtener dos liberaciones — sctp_association_free() solo recorre la
lista de transportes, y rm_peer() ya había desvinculado el fantasma. No
pierdas una noche con eso.sctp_outq_select_transport() lee state en +0x15c desde
chunk->transport en +0xe8 del chunk. Un objeto reclamado rociado
con state=1 (ACTIVE) cambia el comportamiento del kernel tras la
liberación — el objeto está siendo consultado.sctp_outq_select_transport() leyendo state=0xFFFF (basura del slab)
desde un puntero de transporte obsoleto justo en el momento de la inyección. Consecuencia: el
rociado debe preacondicionar la caché antes del disparador, no después —
el objeto se lee antes de que el período de gracia expire en esta ruta.*(p+0x288) con p+0x288. Eso es un autopuntero. No
puedes rociar para superarlo sin conocer la dirección del slab, es decir,
este fallo necesita una fuga de información antes de que el control de af_specific se convierta en
control del puntero de instrucción. af_specific en sí es un sitio de llamada
indirecta (*(af_specific+0x18) con rdi = transport), así que ese es el premio
una vez que exista una fuga.setsockopt(SCTP_AUTH_KEY) con una clave
de 800 bytes aterriza en la misma caché kmalloc-1k. Recuerda que la cabecera es
struct sctp_authkey { assoc_id; keynumber; keylength; key[] } y
AUTH debe habilitarse por socket primero mediante SCTP_AUTH_SUPPORTED con un
sctp_assoc_value — un int simple devuelve EINVAL.Esto es una PoC de disparo/fallo. No es una escalada de privilegios y este repositorio no reclama ninguna. Ejecútala contra tus propios kernels en una VM.
Probado en: 7.1.5+kali-amd64 (Kali rolling) y 7.0.12+kali-amd64.
GPL-2.0 para las porciones de la lógica derivadas del kernel; haz lo que quieras con el resto. Solo para pruebas y investigación de seguridad autorizadas.