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
TEST-CVE-2023-32233 — CVE-2023-32233 | Kitploit
Ferramentas/GitHubGitHub/rogeliopumajulca/test-cve-2023-32233
Forensia de MemóriaAnálise de VulnerabilidadesExploraçãoAprendizado e EducaçãoExploração de Binários
GitHubrogeliopumajulca/test-cve-2023-32233

TEST-CVE-2023-32233

CVE-2023-32233

Ver Repositório
4há 2 anosAinda não revisado

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 requisições em lote

Detalhes da Vulnerabilidade

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

O Netfilter nf_tables permite atualizar sua configuração como uma operação atômica. Ao usar esse recurso, os clientes em modo de usuário enviam requisições em lote contendo uma lista de operações básicas. O Netfilter nf_tables então processa todas as operações dentro 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 leva em conta as atualizações de estado de todas as operações anteriores dentro do 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 um nft_set anônimo, e onde o nft_set anônimo contém alguns elementos. Em seguida, enviamos uma requisiçã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 requisição em lote acima. Em seguida, ela chama nf_tables_commit_release() que anexa recursos liberados à nf_tables_destroy_list. A nf_tables_destroy_list é então processada 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() que aponta para nft_lookup_destroy()
                nf_tables_destroy_set()
                    nft_set_destroy()
                        kvfree() que desaloca a memória usada por `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 seguintes. Portanto, sempre que o valor de set->ops->elemsize for corrompido, uma localização de memória inesperada pode ser interpretada como uma 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 condição de corrida com nf_tables_trans_destroy_work() que executa em uma thread de trabalho em segundo plano do kernel Linux. Isso parece complicar a exploração prática mesmo antes de considerarmos mitigações existentes, como o endurecimento do alocador de slabs do kernel, Randomização de Espaço de Endereçamento do Kernel (KASLR) e especialmente Integridade de Fluxo de Controle. No entanto, o PoC anexado prova que ainda é possível alcançar 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 de ser usado em nf_tables_set_elem_destroy(). Ambas nf_tables_rule_destroy() e nf_tables_set_elem_destroy() são chamadas dentro de uma única invocação de nf_tables_trans_destroy_work() que executa em uma thread de trabalho em segundo plano do kernel Linux. Além disso, o bloco de memória desalocado geralmente está disponível para reutilização apenas a partir do mesmo núcleo de CPU.

Ao competir com nf_tables_trans_destroy_work(), melhoramos nossas chances adicionando um atraso controlado para a thread de trabalho em segundo plano entre suas chamadas 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. Adicionalmente, mantemos todos os outros núcleos de CPU ocupados, de modo que a thread de trabalho em segundo plano provavelmente será escalonada em um núcleo de CPU específico, para que possamos tentar alocar uma nova estrutura a partir do mesmo núcleo de CPU logo após ele desalocar 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. Então, 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 está alguns bytes após a localização correta. Isso significa que determinados campos de dados controlados pelo usuário do nft_set_ext original são agora interpretados como cabeçalhos, resultando em confusão de tipo.

Uma maneira de abusar dessa confusão de tipo é crafting os cabeçalhos do nft_set_ext corrompido com valores de deslocamento 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 ser destruída através 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 criar endereços de ponteiro absolutos. No entanto, ao criar os cabeçalhos do nft_set_ext corrompido, ainda podemos usar deslocamentos 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, espalhamos 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 através da outra referência da expressão nft_log espalhada.

Além disso, também podemos controlar o tamanho de nft_log->prefix, de modo que ele possa ser alocado a partir de qualquer um dos slabs kmalloc-{8, ..., 192}. Finalmente, a memória referida é interpretada como uma string de caracteres pelo kernel, então não há necessidade de se preocupar com corrupções ao sobrepor diferentes objetos sobre ela. Isso é essencialmente game over.

Um inconveniente é que quaisquer caracteres NULL terminam nft_log->prefix, então não podemos ler além de bytes NULL ao vazar conteúdo de memória. Isso é resolvido na próxima etapa, onde alocamos nft_object->udata para reutilizar o bloco 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 de nft_object->udata para vazar conteúdo de memória sem restrições de bytes NULL.

Procurando estruturas adequadas para as etapas seguintes, decidimos por nft_expr alocado a partir de nft_dynset_new(). Estes vivem nos mesmos slabs que nft_log->prefix e nft_object->udata. E também, temos controle razoável sobre o tamanho da alocação, de modo que posteriormente possamos alternar facilmente entre slabs de tamanhos diferentes, se necessário.

Para usar essas estruturas, criamos um filtro de pacotes com expressão nft_dynset. E quando enviamos quaisquer pacotes pela interface 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 com estado 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 repetidamente desalocar e realocar nft_object->udata para modificar o ponteiro nft_quota->consumed. Em seguida, realizamos uma 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 a escritas, simplesmente enviamos pacotes pela interface 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", para 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 de Fluxo de Controle. No entanto, para cada uma das etapas de exploração, escolhemos conscientemente os primitivos mais flexíveis e robustos. Acontece que nossa seleção de alguma forma evitou qualquer um dos primitivos que poderiam potencialmente ser bloqueados pela Integridade de Fluxo de Controle. Agora estamos curiosos para confirmar com testes se o exploit resultante realmente funciona contra sistemas com mitigações de Integridade de Fluxo de Controle.

Baixar ferramenta

para modificar nft_quota->consumed.