
Prova de conceito demonstrando escape de contêiner no Amazon EKS ao explorar a corrupção do page-cache do kernel Dirty Frag (CVE-2026-43284) por meio de camadas de imagem compartilhadas e DaemonSets privilegiados.
Uma prova de conceito demonstrando como um Pod Kubernetes padrão, sem privilégios pode alcançar execução de código em nível de nó no Amazon EKS ao explorar a vulnerabilidade de corrupção do page-cache do kernel Linux Dirty Frag por meio de camadas de imagem de contêiner compartilhadas.
O primitivo de ataque central é: qualquer DaemonSet privilegiado que compartilhe camadas de imagem com um contêiner controlado pelo atacante pode ser transformado em arma para escape de contêiner. Este PoC usa o kube-proxy como um exemplo concreto, mas a técnica se generaliza para qualquer workload privilegiado no cluster.
Validado no Amazon EKS (kernel 6.12.80) — um pod sem privilégios escreve [*] success no sistema de arquivos do host por meio do DaemonSet privilegiado kube-proxy:

Aviso: Este repositório é publicado apenas para fins educacionais e defensivos. Use-o exclusivamente em sistemas que você possui ou para os quais tenha autorização explícita para testar.
Dirty Frag (CVE-2026-43284) é uma vulnerabilidade de corrupção do page-cache do kernel Linux no caminho de recepção xfrm/ESP. No caminho afetado, esp_input() pode pular skb_cow_data() para um skb não linear sem frag_list, permitindo que crypto_authenc_esn_decrypt() armazene 4 bytes de dados controlados pelo atacante em uma página do page-cache alcançada por meio de splice().
O arquivo no disco não é modificado. Os bytes corrompidos residem no page-cache do kernel e são observados por leitores posteriores da mesma página de arquivo em cache.
Para detalhes completos sobre a vulnerabilidade original, consulte V4bel/dirtyfrag.
O ataque explora três propriedades que comumente coexistem em clusters Kubernetes:
privileged: true, hostNetwork: true, capabilities amplas, etc.) que executam periodicamente binários de suas imagens.Quando essas condições se alinham, um pod sem privilégios pode corromper um binário em uma camada de imagem compartilhada, e um DaemonSet privilegiado no mesmo nó executará sem saber o binário corrompido com seus privilégios elevados — alcançando execução completa de código em nível de nó.
O alvo da vulnerabilidade NÃO está limitado ao kube-proxy. Qualquer DaemonSet privilegiado (agentes de monitoramento, plugins CNI, coletores de logs, agentes de segurança, etc.) cuja imagem de contêiner compartilhe camadas com uma imagem controlada pelo atacante é um alvo viável.
Este projeto é inspirado no modelo de exploração Kubernetes documentado no Copy Fail Kubernetes PoC, mas usa um primitivo de kernel diferente.
A cadeia de ataque tem três estágios: corrupção do page-cache, propagação entre contêineres e execução privilegiada.
O binário do PoC executa a seguinte sequência a partir de um contêiner sem privilégios:
unshare(CLONE_NEWUSER | CLONE_NEWNET).splice() e entrada ESP elaborada para acionar o caminho vulnerável do kernel.Nenhuma permissão de escrita no arquivo alvo é necessária. O arquivo no disco permanece inalterado — apenas o page-cache na memória é corrompido.
Os runtimes de contêiner atendem leituras das camadas inferiores do overlay por meio do page-cache do kernel. Se o contêiner do PoC e o kube-proxy compartilham o mesmo arquivo de camada inferior, ambos observam as mesmas páginas em cache.
A imagem EKS neste repositório é construída a partir de:
public.ecr.aws/eks-distro-build-tooling/eks-distro-minimal-base-iptables:2026-03-11-1773190710.2023
Essa base é escolhida para corresponder à camada da toolchain userspace do kube-proxy EKS usada no ambiente validado.
Quando o kube-proxy executa em seguida um binário da família iptables com patch, o kernel carrega as páginas em cache corrompidas. O payload do PoC monta o dispositivo raiz do host e escreve um arquivo marcador em /root/res.
O conteúdo esperado do marcador é:
[*] success
┌──────────────────────────────┐ ┌────────────────────────┐ ┌──────────────────────────┐
│ Pod PoC │ │ Page Cache do Kernel │ │ DaemonSet kube-proxy │
│ contêiner sem privilégios │ │ │ │ contêiner privilegiado │
│ │ │ │ │ │
│ 1. unshare user+net ns │ │ │ │ │
│ 2. instalar SAs xfrm │ │ │ │ │
│ 3. splice do binário alvo │────▶│ binário de camada │────▶│ executa binário com │
│ pelo caminho ESP │ │ compartilhada com │ │ patch; payload roda │
│ │ │ page cache alterado │ │ com privilégios de nó │
└──────────────────────────────┘ └────────────────────────┘ └──────────────────────────┘
Testei em clusters GKE e ACK. Todos falharam.
O primitivo Dirty Frag requer criação de user namespace (CLONE_NEWUSER) para obter CAP_NET_ADMIN dentro de um novo network namespace. Tanto ACK quanto GKE bloqueiam isso no nível do nó por meio de mecanismos diferentes:
user.max_user_namespaces=0) impede completamente a criação de user namespaces sem privilégios.--seccomp-default do kubelet) bloqueia a syscall unshare independentemente do limite de namespaces.Esta é uma diferença fundamental do Copy Fail (CVE-2026-31431), que não requer user namespaces e explora com sucesso todas as três plataformas.
A variante EKS fornecida aplica patch nos seguintes binários quando presentes:
/usr/sbin/xtables-legacy-multi
/usr/sbin/xtables-nft-multi
Esses binários são invocados pela toolchain iptables usada pelo kube-proxy. O momento exato do acionamento depende da reconciliação de nós e serviços. No ambiente validado, o payload foi acionado pela reconciliação normal do kube-proxy.
Avisos importantes:
ipset quando configurado no modo ipvs. O modo padrão (iptables) não usa ipset.xtables-legacy-multi, xtables-nft-multi) para cobrir diferentes modos de proxy, mas se eles serão invocados depende da configuração do cluster.Se o kube-proxy não for privilegiado no seu cluster, o princípio do ataque ainda se mantém — você só precisa identificar um DaemonSet privilegiado diferente que compartilhe camadas de imagem com uma imagem base a partir da qual você possa construir.
.
├── exploit/
│ └── dirtyfrag.c # Escritor do page-cache xfrm/ESP
├── payload/
│ ├── payload-eks.c # Payload nolibc que escreve /root/res no host
│ └── nolibc/ # Cabeçalhos Linux nolibc
├── deploy/
│ └── poc-eks.yaml # Manifesto de Deployment EKS sem privilégios
├── scripts/
│ ├── setup-eks.sh # copia, compila e importa imagem em um nó EKS
│ ├── run-poc.sh # implanta e verifica o marcador
│ └── cleanup.sh # remove pod, marcador, páginas em cache e imagem local
├── Dockerfile.eks # Imagem EKS baseada em eks-distro-minimal-base-iptables
├── Makefile # alvos de build para payload, exploit, Docker e nerdctl
└── .github/workflows/
└── docker-publish.yml # Workflow de publicação GHCR
# Compilar payload + binário do exploit
make build-eks CC=x86_64-linux-gnu-gcc
# Compilar imagem Docker
make docker-build-eks
# Implantar (pod sem privilégios)
kubectl apply -f deploy/poc-eks.yaml
# Verificar logs
kubectl logs deployment/dirtyfrag-poc-eks
# Verificar escape no nó
ssh ec2-user@<node-ip> "sudo cat /root/res"
# Esperado: [*] success
O workflow do GitHub Actions (.github/workflows/docker-publish.yml) publica a imagem no GHCR ao fazer push para main ou criação de tag. Substitua <owner> em deploy/poc-eks.yaml pelo usuário ou organização do GitHub que possui o fork.
kubectl delete -f deploy/poc-eks.yaml --ignore-not-found
ssh ec2-user@<node-ip> "sudo rm -f /root/res"
kubectl delete pod -n kube-system -l k8s-app=kube-proxy --force
f4c50a4034e6).f4c50a4034e6 ou o backport do fornecedor.esp4 e esp6 se IPsec ESP não for necessário nos nós de trabalho.user.max_user_namespaces=0 impede que este PoC obtenha CAP_NET_ADMIN em um novo network namespace (isso já é o padrão no ACK).privileged: true e acesso amplo ao host, a menos que estritamente necessário.Exemplo de bloco de módulo:
printf 'install esp4 /bin/false\ninstall esp6 /bin/false\n' | sudo tee /etc/modprobe.d/dirtyfrag.conf
sudo rmmod esp4 esp6 2>/dev/null || true
O código do exploit é adaptado de V4bel/dirtyfrag sob a licença MIT.
O código do payload é derivado de tgies/copy-fail-c e é dual-licenciado sob LGPL-2.1-or-later OU MIT.
Os cabeçalhos nolibc são da infraestrutura de self-test do kernel Linux.
| Propriedade | Copy Fail | Dirty Frag |
|---|
| CVE | CVE-2026-31431 | CVE-2026-43284 |
| Caminho do kernel | AF_ALG + splice() | xfrm/ESP + splice() |
| Requisito de namespace | Não necessário | Requer user namespaces |
| Principal capability usada | Nenhuma no contêiner inicial | CAP_NET_ADMIN dentro do novo net namespace |
| Módulo relevante | algif_aead | esp4 |
| Distinção prática | Falha se o vetor AF_ALG for bloqueado | Ainda relevante quando AF_ALG está indisponível, mas ESP/user namespaces estão habilitados |
| Propriedade | Valor |
|---|
| Plataforma | Amazon Elastic Kubernetes Service (EKS) |
| Kernel do Nó | 6.12.80-106.156.amzn2023.x86_64 |
| Estado do Patch | Kernel pré-correção, sem f4c50a4034e6 |
Módulo esp4 | Carregado |
| User Namespaces | Habilitados (user.max_user_namespaces=15030) |
| SELinux | Permissivo |
| Seccomp | Sem restrições no contexto do pod testado |
| DaemonSet Alvo | kube-proxy |
| Privilégios do Alvo | privileged: true, hostNetwork: true |
| Modo de Proxy | iptables |
| Caminho do Marcador | /root/res |
| Plataforma | Resultado | Motivo |
|---|
| Alibaba Cloud ACK | Falhou | user.max_user_namespaces está definido como 0 nas imagens de nó padrão, então usuários sem privilégios não podem usar CLONE_NEWUSER unshare. |
| Google GKE | Falhou | user.max_user_namespaces é 15426, mas o kubelet habilita --seccomp-default. A política seccomp padrão desabilita a syscall unshare. |