
PoC: escape de contêiner totalmente não privilegiado para execução de código no nível do nó no Kubernetes via CVE-2026-31431 corrupção de cache de página + camadas de imagem compartilhadas. Validado no Alibaba Cloud ACK, Amazon EKS e Google GKE.
Uma prova de conceito demonstrando como um contêiner totalmente não privilegiado pode alcançar execução de código no nível do nó no Kubernetes explorando o bug de corrupção do cache de páginas do kernel Linux CVE-2026-31431 através de camadas de imagem de contêiner compartilhadas.
A primitiva 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. Esta PoC usa kube-proxy como um exemplo concreto, mas a técnica se generaliza para qualquer carga de trabalho privilegiada no cluster.
Validado em Alibaba Cloud ACK, Amazon EKS e Google GKE — um pod não privilegiado escreve [*] success no sistema de arquivos do host através do DaemonSet privilegiado kube-proxy:
| Alibaba Cloud ACK (kernel 6.6.88) | Amazon EKS (kernel 6.12.79) | Google GKE (kernel 6.12.68) |
|---|---|---|
![]() | ![]() | ![]() |
Aviso: Este repositório é publicado apenas para fins educacionais e de defesa. Use-o exclusivamente em sistemas que você possui ou possui autorização explícita para testar.
CVE-2026-31431 ("Copy Fail") é uma vulnerabilidade do kernel Linux no caminho de Copy-on-Write (CoW) do cache de páginas. Uma condição de corrida com AF_ALG splice permite que um processo não privilegiado corrompa as páginas do cache de páginas de um arquivo somente leitura. A corrupção persiste no cache de páginas do kernel e é visível para todos os processos que posteriormente leem ou executam o arquivo — incluindo processos em outros contêineres ou no host.
Para detalhes completos sobre a vulnerabilidade original, veja copy.fail.
O ataque explora três propriedades que comumente coexistem em clusters Kubernetes:
privileged: true, hostNetwork: true, capacidades amplas, etc.) que executam periodicamente binários de sua imagem.Quando essas condições se alinham, um pod não privilegiado pode corromper um binário em uma camada de imagem compartilhada, e um DaemonSets privilegiado no mesmo nó executará inconscientemente o binário corrompido com seus privilégios elevados — alcançando execução de código no nível do nó.
O alvo da vulnerabilidade NÃO se limita ao kube-proxy. Qualquer DaemonSets privilegiado (agentes de monitoramento, plugins CNI, coletores de log, agentes de segurança, etc.) cuja imagem de contêiner compartilhe camadas com uma imagem controlada pelo atacante é um alvo viável.
A cadeia de ataque tem três estágios: corrupção do cache de páginas, propagação entre contêineres e execução privilegiada.
O subsistema AF_ALG (criptografia) do kernel expõe uma interface baseada em socket para operações criptográficas do espaço do usuário. O exploit abusa de uma condição de corrida em como o kernel lida com splice() de um arquivo em um socket AF_ALG:
authencesn(hmac(sha256),cbc(aes)).MSG_MORE, informando ao kernel que mais dados virão.splice() para transferir o conteúdo do arquivo alvo de um fd → pipe → socket AF_ALG.O exploit repete isso para cada janela de 4 bytes até que todas as páginas em cache do binário alvo sejam sobrescritas com uma carga útil personalizada.
Nenhuma permissão de escrita no arquivo é necessária. O arquivo no disco não é alterado — apenas o cache de páginas na memória é corrompido.
Os runtimes de contêiner usam sistemas de arquivos overlay. Quando dois contêineres compartilham a mesma camada de imagem, o kernel serve as leituras de arquivos a partir das mesmas páginas do cache de páginas.
O atacante constrói sua imagem PoC FROM a mesma imagem base do DaemonSets privilegiado alvo. Como ambos os contêineres compartilham o mesmo diretório inferior overlay, binários na camada compartilhada mapeiam para páginas do cache de páginas idênticas.
Quando o contêiner PoC não privilegiado corrompe o cache de páginas de um binário, a corrupção é imediatamente visível para o contêiner privilegiado no mesmo nó — com zero comunicação entre contêineres.
Quando o DaemonSets privilegiado executa qualquer binário corrompido (através do seu ciclo normal de operação), o kernel carrega as páginas corrompidas do cache de páginas. A carga útil do atacante é executada com todos os privilégios do DaemonSets — potencialmente incluindo:
A carga útil nesta PoC (payload/payload.c) simplesmente monta o sistema de arquivos raiz do host e escreve um arquivo marcador em /root/res como prova de execução de código no nível do nó.
┌──────────────────────────┐ ┌──────────────────────────┐
│ Contêiner PoC │ │ DaemonSets Privilegiado│
│ (não privilegiado) │ │ (ex.: kube-proxy, │
│ │ │ agente de monitora.) │
│ 1. Abre binário alvo │ │ │
│ (somente leitura) │ │ │
│ │ │ │
│ 2. Corrida splice AF_ALG│ │ │
│ corrompe cache de │ │ │
│ páginas │ │ │
│ │ │ │ │
└──────────┼───────────────┘ └──────────────────────────┘
│ │
▼ │
┌─────────────────────┐ │
│ Cache de Páginas │ │
│ do Kernel │ │
│ │◄────────────────────┘
│ Binário da camada │ 3. DaemonSets executa o
│ compartilhada │ binário corrompido
│ (CORROMPIDO) │ → carrega páginas
│ contém bytes da │ corrompidas
│ carga útil do │ → carga útil executa com
│ atacante │ privilégios do DaemonSets
└─────────────────────┘
A PoC foi validada com sucesso nas seguintes plataformas Kubernetes gerenciadas:



Em todos os três casos, um pod PoC não privilegiado escreveu com sucesso o arquivo marcador [*] success no sistema de arquivos do host — provando execução de código no nível do nó através do DaemonSets privilegiado kube-proxy.
Para os guias completos (análise de camada de imagem, passos de construção, implantação):
Esta PoC usa kube-proxy como alvo porque é um dos DaemonSets privilegiados mais comuns em clusters Kubernetes. Três variantes são fornecidas:
FROM registry.k8s.io/kube-proxy:v1.35.2 (veja Dockerfile)FROM public.ecr.aws/eks-distro-build-tooling/eks-distro-minimal-base-iptables:2026-03-11-1773190710.2023 (veja Dockerfile.eks)FROM us-central1-artifactregistry.gcr.io/gke-release/gke-release/kube-proxy:v1.35.3-gke.1234000 (veja Dockerfile.gke)Todas as variantes corrompem binários como /usr/sbin/ipset, /usr/sbin/nft, /usr/sbin/xtables-legacy-multi e /usr/sbin/xtables-nft-multi.
Observações importantes:
ipset apenas quando configurado no modo ipvs. O modo padrão (iptables) não usa ipset. Veja kubernetes/enhancements#5495 para o plano de depreciação do ipvs.ipset, nft, 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 aplica — você só precisa identificar um DaemonSets privilegiado diferente que compartilhe camadas de imagem com uma imagem base que você possa construir.
Para adaptar esta PoC a um DaemonSets privilegiado diferente:
FROM a mesma imagem base usada por aquele DaemonSets..
├── cmd/copyfail/main.go # Ponto de entrada; incorpora a carga útil compilada
├── internal/
│ ├── exploit/
│ │ ├── exploit.go # Exploit principal: loop de corrida AF_ALG splice
│ │ └── patch.go # Divide a carga útil em janelas de patch de 4 bytes
│ └── alg/
│ └── alg.go # Abstração de socket AF_ALG AEAD
├── payload/
│ ├── payload.c # Carga útil ACK/upstream (monta /dev/vda3 ext4)
│ ├── payload-eks.c # Carga útil EKS (detecção automática de dispositivo NVMe/Xen)
│ ├── payload-gke.c # Carga útil GKE (detecção automática de dispositivo COS/Ubuntu)
│ └── nolibc/ # libc mínima do kernel para payloads estáticos sem dependências
├── deploy/
│ ├── poc.yaml # Manifesto de Deployment Kubernetes (ACK/upstream)
│ ├── poc-eks.yaml # Manifesto de Deployment EKS
│ └── poc-gke.yaml # Manifesto de Deployment GKE
├── Dockerfile # ACK/upstream: FROM registry.k8s.io/kube-proxy
├── Dockerfile.eks # EKS: FROM eks-distro-minimal-base-iptables
├── Dockerfile.gke # GKE: FROM gke-release/kube-proxy
├── Makefile # Orquestração de build (inclui alvos *-eks e *-gke)
└── docs/
├── eks-poc.md # Guia completo da PoC EKS
├── gke-poc.md # Guia completo da PoC GKE
├── ack-poc-res.png # Captura de tela de validação ACK
├── eks-poc-res.png # Captura de tela de validação EKS
└── gke-poc-res.png # Captura de tela de validação GKE
x86_64-linux-gnu-gcc)imagePullPolicy: IfNotPresent no DaemonSets alvo (o padrão do Kubernetes)# Compilar carga útil + binário Go
make build
# Construir imagem Docker
make docker-build
# Construir e enviar para GHCR
make docker-push IMAGE=ghcr.io/<voce>/copy-fail-poc TAG=latest
# Compilar carga útil EKS + binário Go + imagem Docker
make docker-build-eks
# Construir e enviar para GHCR
make docker-push-eks IMAGE=ghcr.io/<voce>/copy-fail-poc
Para alvos arm64 (Graviton):
make build-eks CC=aarch64-linux-gnu-gcc GOARCH=arm64
# Compilar carga útil GKE + binário Go + imagem Docker
make docker-build-gke
# Construir e enviar para GHCR
make docker-push-gke IMAGE=ghcr.io/<voce>/copy-fail-poc
Para nós arm64:
make docker-build-gke CC=aarch64-linux-gnu-gcc GOARCH=arm64 PLATFORM=linux/arm64
# ACK / upstream Kubernetes
kubectl apply -f deploy/poc.yaml
# Amazon EKS
kubectl apply -f deploy/poc-eks.yaml
# Google GKE
kubectl apply -f deploy/poc-gke.yaml
O Deployment cria um único pod não privilegiado. Ele:
/bin/copyfail para corromper o cache de páginas dos binários alvo na camada de imagem compartilhada.Após o DaemonSets privilegiado alvo executar um binário corrompido (para kube-proxy, isso normalmente acontece em segundos devido ao seu loop de reconciliação), verifique o nó:
# SSH no nó, ou use um pod de depuração privilegiado
# ACK / EKS (sistema de arquivos raiz gravável)
cat /root/res
# Saída esperada: [*] success
# Nós COS do GKE (raiz somente leitura, partição stateful gravável)
cat /mnt/stateful_partition/copyfail-res
# Saída esperada: [*] success
A presença do arquivo marcador no sistema de arquivos do host prova que o código fornecido pelo atacante foi executado com privilégios de nível de nó — a partir do contexto do contêiner do DaemonSets privilegiado.
kubectl delete -f deploy/poc.yaml # ou poc-eks.yaml / poc-gke.yaml
# No(s) nó(s) afetado(s), remova o marcador e reinicie o DaemonSets alvo:
rm -f /root/res # ACK / EKS
rm -f /copyfail-res /mnt/stateful_partition/copyfail-res # Nós COS GKE
# Para kube-proxy: delete o pod para forçar a releitura da camada de imagem
kubectl delete pod -n kube-system -l k8s-app=kube-proxy --field-selector spec.nodeName=<nó>
A carga útil padrão (payload/payload.c) é um programa apenas de validação que escreve um arquivo marcador. Para construir uma carga útil personalizada:
payload/payload.c. O programa é compilado contra nolibc (a biblioteca C mínima do kernel) para um binário estático e sem dependências.make payload para compilar cruzadamente.//go:embed.privileged: true.AF_ALG. A regra está disponível através dos enforcers AppArmor e BPF.tools/include/nolibc/)O código Go do exploit neste repositório é fornecido como está para fins de pesquisa.
A carga útil (payload/payload.c) é derivada de copy-fail-c e é duplamente licenciada sob LGPL-2.1-or-later OU MIT. Veja LICENSE-LGPL e LICENSE-MIT.
| Propriedade | Valor |
|---|
| Plataforma | Alibaba Cloud Container Service for Kubernetes (ACK) |
| Kubernetes | v1.35.2 |
| Kernel do Nó | 6.6.88-4.2.alnx4.x86_64 |
| kube-proxy | registry-cn-*.ack.aliyuncs.com/acs/kube-proxy:v1.35.2-aliyun.1 |
| Imagem Base | registry.k8s.io/kube-proxy:v1.35.2 (upstream) |
| Dispositivo Raiz | /dev/vda3 (ext4) |
| Propriedade | Valor |
|---|
| Plataforma | Amazon Elastic Kubernetes Service (EKS) |
| Kubernetes | v1.35.4 |
| Kernel do Nó | 6.12.79-101.147.amzn2023.x86_64 |
| kube-proxy | ***.dkr.ecr.***.amazonaws.com.cn/eks/kube-proxy:v1.35.3-eksbuild.2 |
| Imagem Base | public.ecr.aws/eks-distro-build-tooling/eks-distro-minimal-base-iptables:2026-03-11-1773190710.2023 |
| Dispositivo Raiz | /dev/nvme0n1p1 (xfs) |
| Propriedade | Valor |
|---|
| Plataforma | Google Kubernetes Engine (GKE) |
| Kubernetes | v1.35.3-gke.1234000 |
| SO do Nó | Container-Optimized OS (COS) 125, BUILD_ID 19216.220.72 |
| Kernel do Nó | 6.12.68+ x86_64 |
| kube-proxy | us-central1-artifactregistry.gcr.io/gke-release/gke-release/kube-proxy:v1.35.3-gke.1234000 |
| Imagem Base | Mesma do kube-proxy (imagem gerenciada pelo provedor GKE no Artifact Registry) |
| Dispositivo Raiz | /dev/dm-0 (ext2, somente leitura); /dev/sda1 (ext4, partição stateful gravável) |
| Caminho do Marcador | /mnt/stateful_partition/copyfail-res |