Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
POC-CVE-2023-32233 — Uso após liberação no Netfilter nf_tables ao processar solicitações em lote CVE-2023-32233 | Kitploit
Ferramentas/GitHubGitHub/oferchen/poc-cve-2023-32233
Escalada de PrivilégiosForensia de MemóriaAnálise de VulnerabilidadesExploraçãoTestes de PenetraçãoDesenvolvimento de PayloadsExploração de Binários
GitHuboferchen/poc-cve-2023-32233

POC-CVE-2023-32233

Uso após liberação no Netfilter nf_tables ao processar solicitações em lote CVE-2023-32233

Ver Repositório
5362há 3 anosRevisado pelo Kitploit

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

Use-After-Free no Netfilter nf_tables ao processar solicitações em lote

Demonstração

Demo_CVE-2023-32233

Detalhes da Vulnerabilidade

O código afetado é originário do kernel Linux oficial de https://kernel.org/ e faz parte do componente Netfilter nf_tables (net/netfilter/nf_tables_api.c).

Netfilter nf_tables permite atualizar sua configuração como uma operação atômica. Ao usar este recurso, os clientes em modo de usuário enviam solicitações em lote contendo uma lista de operações básicas. O Netfilter nf_tables processa então todas as operações do lote como uma única transação. Ao processar o lote, o Netfilter nf_tables verifica as atualizações de estado da configuração para garantir que cada operação básica sucessiva seja válida, e isso também considera as atualizações de estado de todas as operações anteriores no lote. No entanto, a verificação atualmente implementada é insuficiente.

Em nosso cenário específico, começamos com uma configuração do Netfilter nf_tables que possui um nft_rule com expressão lookup em nft_set anônimo, e onde o nft_set anônimo contém alguns elementos. Em seguida, enviamos uma solicitação em lote contendo as seguintes duas operações básicas:

  1. Operação NFT_MSG_DELRULE para excluir o nft_rule.
    Observe que isso também exclui implicitamente a expressão lookup e o nft_set anônimo.
  2. Operação NFT_MSG_DELSETELEM para excluir qualquer um dos elementos do nft_set anônimo excluído.

A versão atual do Netfilter nf_tables aceita a solicitação em lote acima. Em seguida, ela chama nf_tables_commit_release(), que anexa os recursos liberados a nf_tables_destroy_list. O nf_tables_destroy_list é então processado por nf_tables_trans_destroy_work(), que primeiro desaloca recursos relacionados à operação NFT_MSG_DELRULE chamando:

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 processar a operação NFT_MSG_DELSETELEM, onde a referência ao nft_set desalocado é acessada via nft_trans_elem_set() durante as seguintes chamadas:

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

Dentro de nft_set_elem_ext() acima, a localização de memória do nft_set desalocado é acessada para determinar a localização 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 as operações que se seguem. Portanto, sempre que o valor de set->ops->elemsize é corrompido, uma localização de memória inesperada pode ser interpretada como lista de nft_expr a ser destruída:

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 Exploração

Explorar a vulnerabilidade acima requer vencer uma corrida com nf_tables_trans_destroy_work(), que é executada a partir da thread de trabalho em segundo plano do kernel Linux. Isso parece complicar a exploração prática antes mesmo de considerarmos as mitigações existentes, como o endurecimento do alocador de slabs do kernel, a Randomização do Layout do Espaço de Endereço do Kernel (KASLR) e especialmente a Integridade do Fluxo de Controle. No entanto, o PoC anexado prova que ainda é possível obter uma exploração razoavelmente confiável na prática.

Para explorar a vulnerabilidade, precisamos modificar o conteúdo da memória do nft_set depois que ele é desalocado em nf_tables_rule_destroy(), mas antes que seja usado em nf_tables_set_elem_destroy(). Ambas as funções nf_tables_rule_destroy() e nf_tables_set_elem_destroy() são chamadas em uma única invocação de nf_tables_trans_destroy_work(), que é executada a partir da thread de trabalho em segundo plano do kernel Linux. Além disso, a região de memória desalocada geralmente está disponível para reutilização apenas no mesmo núcleo da CPU.

Ao competir com nf_tables_trans_destroy_work(), aumentamos nossas chances adicionando um atraso controlado para a thread de trabalho em segundo plano entre suas chamadas a nf_tables_rule_destroy() e nf_tables_set_elem_destroy(). Para isso, inserimos uma operação adicional para destruir outro nft_set contendo um grande número de elementos. Além disso, mantemos todos os outros núcleos da CPU ocupados, de modo que a thread de trabalho em segundo plano provavelmente seja escalonada em um núcleo de CPU específico, permitindo-nos tentar alocar uma nova estrutura do mesmo núcleo de CPU logo após ela desalocar o nft_set em nf_tables_rule_destroy(). Nosso objetivo é alocar um novo nft_set de tipo diferente para reutilizar a localização de memória do nft_set desalocado em nf_tables_rule_destroy().

O novo tipo de nft_set é selecionado para usar um valor diferente para set->ops->elemsize. Assim, quando a thread de trabalho em segundo plano finalmente chama nf_tables_set_elem_destroy() para processar a operação NFT_MSG_DELSETELEM, ela interpreta seu argumento elem incorretamente, de modo que o nft_set_ext *ext corrompido fica alguns bytes após a localização correta. Isso significa que certos campos de dados controlados pelo usuário do nft_set_ext original são agora interpretados como cabeçalhos, resultando em confusão de tipos.

Uma maneira de abusar dessa confusão de tipos é elaborar os cabeçalhos corrompidos do nft_set_ext com valores de offsets tais que nf_tables_set_elem_destroy() interprete o conteúdo de quaisquer blocos de memória adjacentes como a lista de nft_expr a destruir por meio das seguintes chamadas:

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

Neste ponto da exploração, ainda não temos detalhes do layout da memória do kernel. Portanto, não é possível elaborar endereços absolutos de ponteiros. No entanto, ao elaborar os cabeçalhos corrompidos do nft_set_ext, ainda podemos usar offsets fora do intervalo, de modo que expr->ops->destroy() seja chamado em certos nft_expr válidos nos blocos de memória adjacentes.

Para isso, pulverizamos expressões nft_log, com NFTA_LOG_PREFIX controlado. Esse nft_log->prefix é então desalocado por nft_log_destroy() quando expr->ops->destroy() é chamado:

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);

Observe que ainda podemos acessar e até mesmo desalocar novamente essa memória por meio da outra referência da expressão nft_log pulverizada.

Além disso, também podemos controlar o tamanho de nft_log->prefix, de modo que ele possa ser alocado de qualquer um dos slabs kmalloc-{8, ..., 192}. Por fim, a memória referenciada é interpretada pelo kernel como uma string de caracteres, então não precisamos nos preocupar com corrupções ao sobrepor objetos diferentes sobre ela. Isso é essencialmente o fim de jogo.

Um inconveniente é que qualquer caractere NULL termina nft_log->prefix, portanto não podemos ler além dos bytes NULL ao vazar conteúdo de memória. Isso é resolvido na próxima etapa, em que alocamos nft_object->udata para reutilizar a região de memória de nft_log->prefix e destruímos a expressão nft_log. Isso desaloca a memória de nft_object->udata, mas agora ainda podemos usar o ponteiro pendente nft_object->udata para vazar conteúdo de memória sem restrições quanto a bytes NULL.

Procurando estruturas adequadas para as próximas etapas, decidimos por nft_expr alocado por nft_dynset_new(). Elas vivem nos mesmos slabs que nft_log->prefix e nft_object->udata. Além disso, temos controle razoável sobre o tamanho da alocação, de modo que poderíamos facilmente alternar entre slabs de tamanhos diferentes, se necessário.

Para usar essas estruturas, criamos um filtro de pacotes com a expressão nft_dynset. E quando enviamos quaisquer pacotes pela interface de loopback, a expressão nft_dynset chama nft_dynset_new() para criar novos elementos para o nft_set associado. Os elementos criados são expressões stateful dos seguintes tipos:

  • nft_counter para obter a localização de nf_tables.ko na memória do kernel.
    A estrutura inclui um ponteiro para nft_counter_ops no módulo do kernel nf_tables.ko. Vazamos esse ponteiro lendo nft_object->udata.

  • nft_quota para leitura e escrita arbitrária de memória.
    Podemos desalocar e realocar repetidamente nft_object->udata para modificar o ponteiro nft_quota->consumed. Em seguida, executamos a operação NFT_MSG_GETSETELEM, que chama nft_quota_do_dump() para ler o conteúdo da memória referenciada e passa o resultado como atributo NFTA_QUOTA_CONSUMED no resultado. Quanto às escritas, simplesmente enviamos pacotes pela interface de loopback, onde nft_quota_do_eval() chama:

    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 a leitura arbitrária de memória acima para obter o endereço base do núcleo do kernel. E então prosseguimos para modificar a substring "sbin" do caminho "/sbin/modprobe", de modo que seja substituída por "/tmp". O caminho resultante "//tmp/modprobe" é então usado pelo kernel para iniciar um processo com privilégios de root, onde controlamos o conteúdo do arquivo.

Observe que não fizemos nenhum esforço intencional para contornar a Integridade do Fluxo de Controle. No entanto, para cada uma das etapas de exploração, escolhemos conscientemente as primitivas mais flexíveis e mais robustas. Acontece que nossa seleção, de alguma forma, evitou qualquer uma das primitivas que poderiam potencialmente ser bloqueadas pela Integridade do Fluxo de Controle. Agora estamos curiosos para confirmar com testes que o exploit resultante realmente funciona contra sistemas com mitigações de Integridade do Fluxo de Controle.

Baixar ferramenta

para modificar nft_quota->consumed.