Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
POC-CVE-2023-32233 — Use-After-Free en Netfilter nf_tables al procesar solicitudes por lotes CVE-2023-32233 | Kitploit
Herramientas/GitHubGitHub/oferchen/poc-cve-2023-32233
Escalada de PrivilegiosForensia de MemoriaAnálisis de VulnerabilidadesExplotaciónPruebas de PenetraciónDesarrollo de PayloadsExplotación de Binarios
GitHuboferchen/poc-cve-2023-32233

POC-CVE-2023-32233

Use-After-Free en Netfilter nf_tables al procesar solicitudes por lotes CVE-2023-32233

Ver Repositorio
5362hace 3 añosRevisado por Kitploit

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

Use-After-Free en Netfilter nf_tables al procesar solicitudes por lotes

Demo

Demo_CVE-2023-32233

Detalles de la Vulnerabilidad

El código afectado proviene del kernel oficial de Linux desde https://kernel.org/ y forma parte del componente Netfilter nf_tables (net/netfilter/nf_tables_api.c).

Netfilter nf_tables permite actualizar su configuración como una operación atómica. Al usar esta característica, los clientes en modo usuario envían solicitudes por lotes que contienen una lista de operaciones básicas. Netfilter nf_tables procesa entonces todas las operaciones dentro del lote como una única transacción. Al procesar el lote, Netfilter nf_tables comprueba las actualizaciones del estado de la configuración para garantizar que cada operación básica sucesiva sea válida, y esto también tiene en cuenta las actualizaciones de estado de todas las operaciones anteriores dentro del lote. Sin embargo, la comprobación implementada actualmente es insuficiente.

En nuestro escenario específico comenzamos con una configuración de Netfilter nf_tables que tiene una nft_rule con la expresión lookup en un nft_set anónimo, y donde el nft_set anónimo contiene algunos elementos. A continuación, enviamos una solicitud por lotes que contiene las siguientes dos operaciones básicas:

  1. Operación NFT_MSG_DELRULE para eliminar la nft_rule.
    Tenga en cuenta que esto también elimina implícitamente la expresión lookup y el nft_set anónimo.
  2. Operación NFT_MSG_DELSETELEM para eliminar cualquiera de los elementos del nft_set anónimo eliminado.

La versión actual de Netfilter nf_tables acepta la solicitud por lotes anterior. Luego llama a nf_tables_commit_release(), que añade los recursos liberados a nf_tables_destroy_list. nf_tables_destroy_list es procesada entonces por nf_tables_trans_destroy_work(), que primero desasigna los recursos relacionados con la operación NFT_MSG_DELRULE llamando a:

root@kitploit:~
nft_commit_release()
    nf_tables_rule_destroy()
        nf_tables_expr_destroy()
            expr->ops->destroy() that points to nft_lookup_destroy()
                nf_tables_destroy_set()
                    nft_set_destroy()
                        kvfree() that deallocates memory used by `nft_set`

antes de procesar la operación NFT_MSG_DELSETELEM, donde se accede a la referencia al nft_set desasignado mediante nft_trans_elem_set() durante las siguientes llamadas:

root@kitploit:~
nft_commit_release()
    nf_tables_set_elem_destroy()
        nft_set_elem_ext()

Dentro de nft_set_elem_ext() anterior, se accede a la ubicación de memoria del nft_set desasignado para determinar la ubicación de nft_set_ext:

root@kitploit:~
static inline struct nft_set_ext *nft_set_elem_ext(const struct nft_set *set,
                                                   void *elem)
{
        return elem + set->ops->elemsize;
}

para las operaciones que siguen. Por lo tanto, siempre que el valor de set->ops->elemsize se corrompa, cierta ubicación de memoria inesperada podría interpretarse como una lista de nft_expr a destruir:

root@kitploit:~
static void nf_tables_set_elem_destroy(const struct nft_ctx *ctx,
                                       const struct nft_set *set, void *elem)
{
        struct nft_set_ext *ext = nft_set_elem_ext(set, elem);

        if (nft_set_ext_exists(ext, NFT_SET_EXT_EXPRESSIONS))
                nft_set_elem_expr_destroy(ctx, nft_set_ext_expr(ext));

Técnicas de Explotación

Explotar la vulnerabilidad anterior requiere ganar una carrera contra nf_tables_trans_destroy_work(), que se ejecuta desde el subproceso de trabajo en segundo plano del kernel de Linux. Esto parece complicar la explotación práctica incluso antes de considerar las mitigaciones existentes, como el endurecimiento del asignador de slabs del kernel, la aleatorización del diseño del espacio de direcciones del kernel (KASLR) y especialmente la integridad del flujo de control. Sin embargo, el PoC adjunto demuestra que todavía es posible lograr una explotación razonablemente fiable en la práctica.

Para explotar la vulnerabilidad, necesitamos modificar el contenido de la memoria del nft_set después de que se desasigne bajo nf_tables_rule_destroy(), pero antes de que se utilice bajo nf_tables_set_elem_destroy(). Tanto nf_tables_rule_destroy() como nf_tables_set_elem_destroy() se llaman dentro de una única invocación de nf_tables_trans_destroy_work(), que se ejecuta desde el subproceso de trabajo en segundo plano del kernel de Linux. Además, el bloque de memoria desasignado suele estar disponible para reutilizarse solo desde el mismo núcleo de la CPU.

Al competir con nf_tables_trans_destroy_work(), mejoramos nuestras posibilidades añadiendo un retraso controlado para el subproceso de trabajo en segundo plano entre las llamadas a nf_tables_rule_destroy() y nf_tables_set_elem_destroy(). Para ello, insertamos una operación adicional para destruir otro nft_set que contenga un gran número de elementos. Además, mantenemos ocupados todos los demás núcleos de la CPU, de modo que el subproceso de trabajo en segundo plano probablemente sea programado en un núcleo de CPU específico, para así intentar asignar una nueva estructura desde el mismo núcleo de CPU justo después de que desasigne nft_set bajo nf_tables_rule_destroy(). Nuestro objetivo es asignar un nuevo nft_set de tipo diferente para reutilizar la ubicación de memoria del nft_set desasignado bajo nf_tables_rule_destroy().

El nuevo tipo de nft_set se selecciona para usar un valor diferente para set->ops->elemsize. Así, cuando el subproceso de trabajo en segundo plano finalmente llama a nf_tables_set_elem_destroy() para procesar la operación NFT_MSG_DELSETELEM, interpreta su argumento elem incorrectamente, de modo que el nft_set_ext *ext corrupto se encuentra unos bytes después de la ubicación correcta. Esto significa que ciertos campos de datos controlados por el usuario del nft_set_ext original ahora se interpretan como cabeceras, lo que resulta en confusión de tipos.

Una forma de abusar de esta confusión de tipos es manipulando las cabeceras del nft_set_ext corrupto con valores de desplazamiento tales que nf_tables_set_elem_destroy() interprete el contenido de cualquier bloque de memoria adyacente como la lista de nft_expr a destruir mediante las siguientes llamadas:

root@kitploit:~
nft_set_elem_expr_destroy()
    __nft_set_elem_expr_destroy()
        nf_tables_expr_destroy()
            expr->ops->destroy()

En este punto de la explotación, todavía no tenemos detalles del diseño de memoria del kernel. Por lo tanto, no es posible fabricar direcciones absolutas de punteros. Sin embargo, al manipular las cabeceras del nft_set_ext corrupto, todavía podemos usar desplazamientos fuera de rango, de modo que expr->ops->destroy() se llame sobre ciertos nft_expr válidos en los bloques de memoria adyacentes.

Para ello, rociamos expresiones nft_log, con NFTA_LOG_PREFIX controlado. Ese nft_log->prefix es desasignado entonces por nft_log_destroy() una vez que se llama a expr->ops->destroy():

root@kitploit:~
static void nft_log_destroy(const struct nft_ctx *ctx,
                            const struct nft_expr *expr)
{
        struct nft_log *priv = nft_expr_priv(expr);
        struct nf_loginfo *li = &priv->loginfo;

        if (priv->prefix != nft_log_null_prefix)
                kfree(priv->prefix);

Tenga en cuenta que todavía podemos acceder e incluso desasignar nuevamente esta memoria a través de la otra referencia de la expresión nft_log rociada.

Además, también podemos controlar el tamaño de nft_log->prefix, de modo que pueda asignarse desde cualquiera de los slabs kmalloc-{8, ..., 192}. Finalmente, la memoria referida es interpretada por el kernel como una cadena de caracteres, por lo que no hay que preocuparse por corrupciones cuando superponemos diferentes objetos sobre ella. Esto es, esencialmente, el fin del juego.

Un inconveniente es que cualquier carácter NULL termina nft_log->prefix, por lo que no podemos leer más allá de los bytes NULL al filtrar el contenido de la memoria. Esto se aborda en el siguiente paso, donde asignamos nft_object->udata para reutilizar el bloque de memoria de nft_log->prefix y destruimos la expresión nft_log. Esto desasigna la memoria de nft_object->udata, pero ahora todavía podemos usar el puntero colgante nft_object->udata para filtrar el contenido de la memoria sin restricciones sobre bytes NULL.

Buscando estructuras adecuadas para los siguientes pasos, nos decidimos por nft_expr asignado mediante nft_dynset_new(). Estas viven en los mismos slabs que nft_log->prefix y nft_object->udata. Además, tenemos un control razonable sobre el tamaño de asignación, de modo que luego podríamos cambiar fácilmente entre slabs de diferente tamaño si fuera necesario.

Para usar estas estructuras, creamos un filtro de paquetes con la expresión nft_dynset. Y cuando enviamos cualquier paquete a través de la interfaz de loopback, la expresión nft_dynset llama a nft_dynset_new() para crear nuevos elementos para el nft_set asociado. Los elementos creados son expresiones con estado de los siguientes tipos:

  • nft_counter para obtener la ubicación de nf_tables.ko en la memoria del kernel.
    La estructura incluye un puntero a nft_counter_ops en el módulo del kernel nf_tables.ko. Filtramos este puntero leyendo nft_object->udata.

  • nft_quota para lectura y escritura arbitraria de memoria.
    Podemos desasignar y reasignar repetidamente nft_object->udata para modificar el puntero nft_quota->consumed. A continuación, realizamos la operación NFT_MSG_GETSETELEM que llama a nft_quota_do_dump() para leer el contenido de la memoria referenciada y pasa el resultado como atributo NFTA_QUOTA_CONSUMED en el resultado. En cuanto a las escrituras, simplemente enviamos paquetes a través de la interfaz de loopback, donde nft_quota_do_eval() llama a:

    root@kitploit:~
      static inline bool nft_overquota(struct nft_quota *priv,
                                       const struct sk_buff *skb)
      {
              return atomic64_add_return(skb->len, priv->consumed) >=
    

Usamos la lectura arbitraria de memoria anterior para obtener la dirección base del núcleo del kernel. Luego procedemos a modificar la subcadena "sbin" de la ruta "/sbin/modprobe", de modo que se reemplace por "/tmp". La ruta resultante "//tmp/modprobe" es utilizada entonces por el kernel para iniciar un proceso con privilegios de root, donde controlamos el contenido del archivo.

Tenga en cuenta que no hicimos ningún esfuerzo intencional por evadir la integridad del flujo de control. Sin embargo, para cada uno de los pasos de explotación, elegimos conscientemente las primitivas más flexibles y más robustas. Resulta que nuestra selección evitó de algún modo cualquier primitiva que pudiera potencialmente ser bloqueada por la integridad del flujo de control. Ahora tenemos curiosidad por confirmar con pruebas que el exploit resultante realmente funciona contra sistemas con mitigaciones de integridad del flujo de control.

Descargar herramienta

para modificar nft_quota->consumed.