
Um relatório sobre Dirty Frag, que é uma cadeia de vulnerabilidades de Escalação de Privilégio Local (LPE) do Linux que permite que um usuário não privilegiado obtenha acesso root.
Laboratório de Reprodução e Detecção de Exploit para uma cadeia de escalonamento local de privilégios no kernel Linux.
Status: VERIFICADO. Concluí a reprodução, a verificação sem arquivos e a detecção no nível de chamadas de sistema no laboratório (kernel 6.18.9+kali-amd64). Este documento é um registro de laboratório. Cada afirmação abaixo foi observada durante a execução da reprodução. As capturas de tela e artefatos são capturas reais da VM.
O Dirty Frag combina dois bugs lógicos determinísticos no kernel Linux. Esses bugs permitem que um usuário local sem privilégios sobrescreva o cache de páginas de arquivos somente leitura (por exemplo, /usr/bin/su) e obtenha um shell root:
Ambas as variantes usam o mesmo padrão de raiz do Dirty Pipe e do Copy Fail. A chamada de sistema splice(2) coloca uma referência a uma página do page-cache de um arquivo no slot frag de um sk_buff do lado do remetente. O atacante só pode ler este arquivo. O código do kernel no lado do receptor então executa um STORE criptográfico in-place sobre esse frag. Isso altera o page-cache na RAM. Nenhuma gravação em disco ocorre, portanto o monitoramento de integridade de arquivos (AIDE, Tripwire) não consegue detectar. O ataque é determinístico. Não possui janela de corrida nem pânico do kernel em caso de falha.
cac2661c53f3 (2017‑01) a f4c50a4034e6 (corrigido em 2026‑05‑05)2dc334f1a63a (2023‑06) a aa54b1d27fe0 (corrigido em 2026‑05‑10)CVE-2026-43284 = 8.8 (Alta), CVE-2026-43500 = 7.8 (Alta)O Dirty Frag é um LPE sem arquivos. Ele corrompe o page-cache na memória, não o arquivo no disco. O monitoramento tradicional de integridade de arquivos não consegue detectá-lo. A detecção deve ocorrer na camada de chamadas de sistema. A cadeia usa estes primitivos de chamadas de sistema: socket(AF_ALG)/socket(AF_RXRPC), splice e unshare(CLONE_NEWUSER|CLONE_NEWNET). O caminho ESP também cria sockets UDP AF_INET e netlink. Esta camada é o foco da engenharia de detecção neste repositório.
Ambas as variantes usam o mesmo sink: criptografia in-place que ARMAZENA bytes em uma página do page-cache que o atacante coloca com splice(2).
UDP_ENCAP_ESPINUDP.vmsplice e, em seguida, 16 bytes de /usr/bin/su no deslocamento do arquivo alvo com splice.splice empurra o pipe para o socket de envio. splice_to_socket() define MSG_SPLICE_PAGES. Isso coloca a página do page-cache de /usr/bin/su diretamente em skb->frags[0].xfrm4_udp_encap_rcv, depois xfrm_input, depois esp_input(). O ramo vulnerável skip_cow () ignora . Ele executa com a página do page-cache como origem e destino.O atacante controla tanto a localização (deslocamento do splice) quanto o valor (4 bytes). A verificação de autenticação é executada após o store, portanto a camada criptográfica nunca sinaliza a gravação. Esta variante requer CAP_NET_ADMIN e usa unshare(CLONE_NEWUSER|CLONE_NEWNET).
rxkad_verify_packet_1() executa uma descriptografia pcbc(fcrypt) de bloco único diretamente no frag do skb fixado por splice. Ele não copia os dados primeiro. O atacante escolhe uma chave de sessão (add_key("rxrpc", …)) para que decrypt(ciphertext) seja igual a desired_plaintext. Isso produz um STORE de 8 bytes. Esta variante tem como alvo /etc/passwd. Ela não requer namespace de usuário. Requer o módulo rxrpc.ko (carregado por padrão no Ubuntu).
O PoC público tem como alvo /usr/bin/su. Ele grava 48 stores ESP de 4 bytes cada (192 bytes no deslocamento 0 do arquivo). Ele substitui os primeiros bytes do page-cache por um ELF estático de shell root. O ponto de entrada do ELF executa setgid(0); setuid(0); setgroups(0,NULL); execve("/bin/sh", …). Um único execve("/usr/bin/su") então produz um shell root.
O patch ESP (mainline f4c50a4034e6) marca os frags de página que chegam por splice() com o sinalizador SKBFL_SHARED_FRAG. O ramo skip_cow em esp_input() agora também verifica esse sinalizador. Skbs com frags compartilhados passam por skb_cow_data() antes da descriptografia AEAD in-place.
O patch RxRPC (mainline aa54b1d27fe0) adiciona uma verificação de skb->data_len ao lado da verificação existente de skb_cloned(). O kernel copia um skb não linear com dados paginados antes da descriptografia pcbc(fcrypt) in-place.
Captura de tela da configuração do laboratório VirtualBox:

.
├── README.md # este registro de laboratório
├── detection/
│ ├── dirtyfrag.rules # regras de detecção no nível de chamadas de sistema do auditd
│ ├── ausearch_dirtyfrag_observed.txt # saída real de detecção do exploit
│ ├── sigma/
│ │ └── dirty_frag_exploit.yml # regra Sigma para detecção em SIEM
│ └── yara/
│ └── dirty_frag_exploit.yar # regra YARA para código PoC em disco/memória
├── mitigation/
│ └── dirtyfrag_mitigation.sh # bloqueio de módulos + limpeza do page cache
├── poc/
│ └── check_vulnerable.py # verificador pré-voo não destrutivo
├── reports/
│ └── incident-dirtyfrag.md # playbook de resposta a incidentes
└── screenshots/ # capturas reais da VM do laboratório
poc/check_vulnerable.py e confirmar kernel/módulos/usernstestuser sem privilégios/usr/bin/sudrop_caches ou reinicialização)detection/dirtyfrag.rules e validar os alertas do auditdcat /etc/os-release | head -3
uname -r
Captura de tela da verificação da versão do kernel:

O kernel deve ser mais antigo que as correções de maio de 2026 (f4c50a4034e6 / aa54b1d27fe0). Se você executou apt upgrade após essa data, o exploit falhará. Consulte Solução de Problemas.
Um verificador seguro informa se a VM é um alvo plausível. Ele verifica o kernel em execução, a presença dos módulos esp4/esp6/rxrpc e se namespaces de usuário sem privilégios estão disponíveis. A variante ESP requer esses namespaces.
python3 poc/check_vulnerable.py
Veredito esperado: [*] potencialmente vulnerável -- prossiga apenas em uma VM descartável.
Um testuser sem privilégios simula um atacante sem direitos especiais.
sudo useradd -m testuser
sudo passwd testuser
su - testuser
id
Captura de tela: id mostra UID 1001. Isso confirma acesso não root.

A partir da conta sem privilégios:
git clone https://github.com/V4bel/dirtyfrag.git
cd dirtyfrag
gcc -O0 -Wall -o exp exp.c -lutil
./exp
Em caso de sucesso, o exploit corrige o page-cache de /usr/bin/su. Ele grava 48 stores ESP de 4 bytes cada (um ELF de shell root de 192 bytes no deslocamento 0 do arquivo). Em seguida, ele abre um shell root interativo com forkpty.
Captura de tela da disponibilidade do módulo, revisão do código-fonte e compilação limpa:

id
whoami
Captura de tela: id mostra uid=0(root) após ./exp.

sha256sum lê através do page-cache. Enquanto a gravação do exploit está ativa, o hash é diferente do original. Após uma limpeza, o hash retorna ao original. Esse par antes/depois prova que o binário em disco nunca foi tocado.
sha256sum /usr/bin/su # 1) enquanto o page cache está contaminado -> hash DIFERENTE
Captura de tela: o hash é diferente do hash do pacote (RAM envenenada, disco intacto).

Hash corrompido observado (page-cache): 3fc29078bd77150b5d6fbb368f632bf02c1a3704816f03fb694ec2e81e77bde4
Após a exploração, o page-cache contém os dados corrompidos. Sempre limpe-o:
echo 3 | sudo tee /proc/sys/vm/drop_caches
# ou reinicie a VM
Em seguida, verifique a restauração (como testuser):
sha256sum /usr/bin/su # agora corresponde ao hash ORIGINAL do pacote
su - # solicita uma senha novamente — sem root automático
Captura de tela: hash restaurado (original em disco).

Hash original observado (em disco): 2b4f8770bd35bba5cdc5cfe292bc1d988e92ec1786bf91cf83e0e86fac056eb6
Após uma reinicialização, dpkg -V util-linux retornou sem saída. O /usr/bin/su em disco corresponde exatamente ao pacote, portanto o hash corrompido 3fc29078… existia apenas no page-cache.
drop_cachespode não remover a página envenenada. Isso acontece se um processo em execução ainda mantém a página fixada. Nesse caso,sha256sumedpkg -Vcontinuam mostrando o conteúdo corrompido. A correção confiável é uma reinicialização. Observe quedpkg -Vtambém lê através do page-cache. Enquanto a página está envenenada, ele relata??5??????(o MD5 é diferente; tamanho, modo, proprietário e mtime correspondem). Ele fica silencioso novamente após a reinicialização. Isso prova que o arquivo em disco nunca foi modificado.
A limpeza não desativa o exploit. Ela apenas limpa o page-cache envenenado. Você deve desativar os módulos separadamente (consulte Mitigação). Remover módulos já carregados em um host explorado requer uma reinicialização.
O Dirty Frag é invisível para o monitoramento de integridade de arquivos. A detecção se concentra nos primitivos de chamadas de sistema que a cadeia deve usar.
detection/dirtyfrag.rules)# /etc/audit/rules.d/dirtyfrag.rules
-a always,exit -F arch=b64 -S socket -F a0=38 -F uid!=0 -k dirtyfrag_af_alg
-a always,exit -F arch=b64 -S socket -F a0=33 -F uid!=0 -k dirtyfrag_rxrpc
-a always,exit -F arch=b64 -S splice -F uid!=0 -k dirtyfrag_splice
-a always,exit -F arch=b64 -S unshare -F uid!=0 -k dirtyfrag_namespace
-w /usr/bin/su -p r -k dirtyfrag_suid_read
Observação:
AF_ALG = 38eAF_RXRPC = 33no Linux (consulte/usr/include/bits/socket.h). Rascunhos anteriores usavama0=21. Esse valor está incorreto. AF_RXRPC é 33, não 21.
Implantar e verificar:
sudo cp detection/dirtyfrag.rules /etc/audit/rules.d/
sudo systemctl restart auditd # ou: sudo auditctl -D && sudo auditctl -R /etc/audit/rules.d/dirtyfrag.rules
sudo auditctl -l
Alertas esperados após reexecutar o exploit (correlacione por PID):
ausearch -k dirtyfrag_af_alg
ausearch -k dirtyfrag_rxrpc
ausearch -k dirtyfrag_splice
ausearch -k dirtyfrag_namespace
Todas as cinco regras foram carregadas (auditctl -R, res=1 para cada CONFIG_CHANGE). As regras capturaram a configuração de namespace do exploit:
time->Qua Ago 5 08:26:37 2026
type=PROCTITLE msg=audit(1785932797.328:592): proctitle="./exp"
type=SYSCALL msg=audit(1785932797.328:592): arch=c000003e syscall=272 success=yes exit=0 a0=50000000 a1=0 a2=0 a3=0 items=0 ppid=5198 pid=5199 auid=1000 uid=1001 gid=1001 euid=1001 suid=1001 fsuid=1001 egid=1001 sgid=1001 fsgid=1001 tty=pts2 ses=2 comm="exp" exe="/home/testuser/dirtyfrag/exp" subj=unconfined key="dirtyfrag_namespace"
Decodificado: syscall=272 (unshare), a0=50000000 é igual a CLONE_NEWUSER | CLONE_NEWNET, uid=1001 (testuser sem privilégios), comm="exp". O log completo está em detection/ausearch_dirtyfrag_observed.txt.
Captura de tela: a saída do ausearch mostra o carregamento das regras e o evento do exploit.

O playbook completo está em reports/incident-dirtyfrag.md: resumo executivo, linha do tempo, IoCs, mapeamento MITRE ATT&CK, contenção, erradicação, recuperação e lições aprendidas.
Mitigação imediata em tempo de execução. Ela não sobrevive a uma reinicialização para módulos já carregados (consulte a observação abaixo):
sudo mitigation/dirtyfrag_mitigation.sh
O que ela faz:
/etc/modprobe.d/dirtyfrag.conf para bloquear esp4, esp6 e rxrpc. Inclui as linhas blacklist e alias … off. Um one-liner simples perde essas linhas (o carregamento automático por alias ainda funcionava caso contrário).Impacto: desativar esses módulos faz com que as funcionalidades de VPN IPsec (ESP) e do sistema de arquivos AFS (RxRPC) parem de funcionar.
Correção permanente: atualize para um kernel que contenha os patches upstream. Ou bloqueie os módulos na inicialização (initcall_blacklist=esp4,esp6,rxrpc).
Este repositório é apenas para pesquisa e treinamento autorizados de segurança defensiva.
Consulte LICENSE. Este laboratório é apenas para uso educacional em seu próprio ambiente isolado. Não execute o PoC em sistemas que você não está autorizado a testar.
| Variante | CVE | Sink | Caminho de gatilho | Requer userns sem privilégios |
|---|
| Gravação no Page-Cache via xfrm‑ESP | CVE‑2026‑43284 | crypto_authenc_esn_decrypt() em esp_input() | socket(AF_INET) com UDP‑encap, depois xfrm_input() | Sim (CAP_NET_ADMIN) |
| Gravação no Page-Cache via RxRPC | CVE‑2026‑43500 | rxkad_verify_packet_1() (pcbc(fcrypt)) | socket(AF_RXRPC) | Não |
!skb_cloned() && !skb_has_frag_list()skb_cow_data()crypto_authenc_esn_decrypt() emite um STORE dos 32 bits de ordem superior do ESN. Esse valor é replay_esn->seq_hi. O atacante escolhe esse valor no registro da SA com o atributo netlink XFRMA_REPLAY_ESN_VAL.| Componente | Detalhes |
|---|
| Hipervisor | VirtualBox |
| VM alvo | Kali Linux 2026.1 (snapshot restaurado para um estado vulnerável) |
| Kernel | 6.18.9+kali‑amd64 (mais antigo que as correções de maio de 2026) |
| PoC do exploit | V4bel/dirtyfrag (arquivo C único) |
| Detecção | auditd (regras em detection/dirtyfrag.rules) |
| Camada | O que ela vê | Status |
|---|
| auditd | Sockets AF_ALG e AF_RXRPC, splice, unshare, leituras SUID | Sim. Implantado e validado (arquivo de regras detection/dirtyfrag.rules) |
| Sigma | Padrões de chamadas de sistema para SIEM | Sim. Regra pronta (detection/sigma/dirty_frag_exploit.yml) |
| YARA | Código PoC em disco ou memória | Sim. Regra pronta (detection/yara/dirty_frag_exploit.yar) |
| FIM (AIDE/Tripwire) | Alterações de arquivos | Não. Cego, porque nenhuma gravação em disco ocorre |
| Sintoma | Causa provável | Correção |
|---|
./exp imprime failed / post-write verify failed | Kernel corrigido (de maio de 2026 ou posterior) | Inicialize um kernel mais antigo ou restaure um snapshot anterior à atualização. Verifique novamente com poc/check_vulnerable.py |
unshare(CLONE_NEWUSER) retorna EPERM | Namespaces de usuário sem privilégios desativados (AppArmor ou sysctl) | Verifique sysctl kernel.unprivileged_userns_clone. No Ubuntu, verifique o AppArmor. O Kali permite por padrão |
esp4/esp6/rxrpc não carregados | Módulos não disponíveis | sudo modprobe esp4 esp6 rxrpc (RxRPC carrega automaticamente com socket(AF_RXRPC)) |
Binário su "corrigido" mas sem shell root | drop_caches já foi executado, ou o page-cache está fixado por processos | Reexecute o exploit. Se travar, reinicie a VM |
| Exploit executado e ainda é possível obter root após a mitigação | Page-cache não limpo, ou módulos já carregados antes do bloqueio | echo 3 > /proc/sys/vm/drop_caches. A interrupção total requer reinicialização |