
Prueba de concepto que demuestra la fuga de contenedores en Amazon EKS explotando Dirty Frag (CVE-2026-43284), una corrupción de la caché de páginas del kernel, mediante capas de imagen compartidas y DaemonSets privilegiados.
Una prueba de concepto que demuestra cómo un Pod de Kubernetes predeterminado y sin privilegios puede lograr ejecución de código a nivel de nodo en Amazon EKS explotando la vulnerabilidad de corrupción de caché de páginas del kernel Linux Dirty Frag a través de capas de imágenes de contenedor compartidas.
El primitivo de ataque principal es: cualquier DaemonSet privilegiado que comparta capas de imagen con un contenedor controlado por el atacante puede ser armado para el escape de contenedor. Este PoC utiliza kube-proxy como un ejemplo concreto, pero la técnica se generaliza a cualquier carga de trabajo privilegiada en el clúster.
Validado en Amazon EKS (kernel 6.12.80) — un pod sin privilegios escribe [*] success en el sistema de archivos del host a través del DaemonSet privilegiado kube-proxy:

Aviso legal: Este repositorio se publica únicamente con fines educativos y defensivos. Úselo exclusivamente en sistemas que posea o para los cuales tenga autorización explícita de prueba.
Dirty Frag (CVE-2026-43284) es una vulnerabilidad de corrupción de caché de páginas del kernel Linux en la ruta de recepción xfrm/ESP. En la ruta afectada, esp_input() puede omitir skb_cow_data() para un skb no lineal sin frag_list, lo que permite que crypto_authenc_esn_decrypt() almacene 4 bytes de datos controlados por el atacante en una página de caché alcanzada a través de splice().
El archivo en disco no se modifica. Los bytes corruptos residen en la caché de páginas del kernel y son observados por lectores posteriores de la misma página de archivo en caché.
Para obtener detalles completos sobre la vulnerabilidad original, consulte V4bel/dirtyfrag.
El ataque explota tres propiedades que comúnmente coexisten en los clústeres de Kubernetes:
privileged: true, hostNetwork: true, capacidades amplias, etc.) que ejecutan periódicamente binarios de su imagen.Cuando estas condiciones se alinean, un pod sin privilegios puede corromper un binario en una capa de imagen compartida, y un DaemonSet privilegiado en el mismo nodo ejecutará sin saberlo el binario corrupto con sus privilegios elevados — logrando ejecución de código completa a nivel de nodo.
El objetivo de la vulnerabilidad NO se limita a kube-proxy. Cualquier DaemonSet privilegiado (agentes de monitoreo, plugins CNI, recolectores de registros, agentes de seguridad, etc.) cuya imagen de contenedor comparta capas con una imagen controlada por el atacante es un objetivo viable.
Este proyecto está inspirado en el modelo de explotación de Kubernetes documentado en el PoC de Kubernetes Copy Fail, pero utiliza un primitivo de kernel diferente.
La cadena de ataque tiene tres etapas: corrupción de caché de páginas, propagación entre contenedores y ejecución privilegiada.
El binario del PoC realiza la siguiente secuencia desde un contenedor sin privilegios:
unshare(CLONE_NEWUSER | CLONE_NEWNET).splice() y entrada ESP manipulada para desencadenar la ruta vulnerable del kernel.No se requiere permiso de escritura en el archivo objetivo. El archivo en disco no cambia — solo se corrompe la caché de páginas en memoria.
Los tiempos de ejecución de contenedores sirven lecturas de capas inferiores overlay a través de la caché de páginas del kernel. Si el contenedor del PoC y kube-proxy comparten el mismo archivo de capa inferior, ambos observan las mismas páginas en caché.
La imagen EKS en este repositorio se construye a partir de:
public.ecr.aws/eks-distro-build-tooling/eks-distro-minimal-base-iptables:2026-03-11-1773190710.2023
Esa base se elige para coincidir con la capa de cadena de herramientas de espacio de usuario kube-proxy de EKS utilizada en el entorno validado.
Cuando kube-proxy ejecuta a continuación un binario de la familia iptables parcheado, el kernel carga las páginas en caché corruptas. La carga útil del PoC monta el dispositivo raíz del host y escribe un archivo marcador en /root/res.
El contenido esperado del marcador es:
[*] success
┌──────────────────────────────┐ ┌────────────────────────┐ ┌──────────────────────────┐
│ Pod del PoC │ │ Caché de Páginas del │ │ DaemonSet kube-proxy │
│ contenedor sin privilegios │ │ Kernel │ │ contenedor privilegiado │
│ │ │ │ │ │
│ 1. unshare user+net ns │ │ │ │ │
│ 2. instalar SAs xfrm │ │ │ │ │
│ 3. splice binario objetivo │────▶│ binario de capa │────▶│ ejecuta binario │
│ a través de la ruta ESP │ │ compartida con caché │ │ parcheado; la carga │
│ │ │ de páginas parcheada │ │ útil se ejecuta con │
│ │ │ │ │ privilegios de nodo │
└──────────────────────────────┘ └────────────────────────┘ └──────────────────────────┘
He probado esto en clústeres GKE y ACK. Todos fallaron.
El primitivo Dirty Frag requiere la creación de espacios de nombres de usuario (CLONE_NEWUSER) para obtener CAP_NET_ADMIN dentro de un nuevo espacio de nombres de red. Tanto ACK como GKE bloquean esto a nivel de nodo mediante diferentes mecanismos:
user.max_user_namespaces=0) impide completamente la creación de espacios de nombres de usuario sin privilegios.--seccomp-default de kubelet) bloquea la llamada al sistema unshare independientemente del límite de espacios de nombres.Esta es una diferencia clave con Copy Fail (CVE-2026-31431), que no requiere espacios de nombres de usuario y explota con éxito las tres plataformas.
La variante EKS proporcionada parchea los siguientes binarios cuando están presentes:
/usr/sbin/xtables-legacy-multi
/usr/sbin/xtables-nft-multi
Estos binarios son invocados por la cadena de herramientas iptables utilizada por kube-proxy. El momento exacto del disparo depende de la actividad de reconciliación de nodos y servicios. En el entorno validado, la carga útil fue disparada por la reconciliación normal de kube-proxy.
Advertencias importantes:
ipset cuando está configurado en modo ipvs. El modo predeterminado (iptables) no utiliza ipset.xtables-legacy-multi, xtables-nft-multi) para cubrir diferentes modos de proxy, pero si se invocan depende de la configuración del clúster.Si kube-proxy no es privilegiado en su clúster, el principio del ataque sigue siendo válido — solo necesita identificar un DaemonSet privilegiado diferente que comparta capas de imagen con una imagen base desde la que pueda construir.
.
├── exploit/
│ └── dirtyfrag.c # Escritor de caché de páginas xfrm/ESP
├── payload/
│ ├── payload-eks.c # Carga útil nolibc que escribe /root/res en el host
│ └── nolibc/ # Encabezados nolibc de Linux
├── deploy/
│ └── poc-eks.yaml # Manifiesto de Deployment EKS sin privilegios
├── scripts/
│ ├── setup-eks.sh # copia, compila e importa la imagen en un nodo EKS
│ ├── run-poc.sh # despliega y verifica el marcador
│ └── cleanup.sh # elimina pod, marcador, páginas en caché e imagen local
├── Dockerfile.eks # Imagen EKS basada en eks-distro-minimal-base-iptables
├── Makefile # objetivos de compilación de payload, exploit, Docker y nerdctl
└── .github/workflows/
└── docker-publish.yml # flujo de trabajo de publicación GHCR
# Compilar binario de payload + exploit
make build-eks CC=x86_64-linux-gnu-gcc
# Compilar imagen Docker
make docker-build-eks
# Desplegar (pod sin privilegios)
kubectl apply -f deploy/poc-eks.yaml
# Verificar registros
kubectl logs deployment/dirtyfrag-poc-eks
# Verificar el escape en el nodo
ssh ec2-user@<node-ip> "sudo cat /root/res"
# Esperado: [*] success
El flujo de trabajo de GitHub Actions (.github/workflows/docker-publish.yml) publica la imagen en GHCR al hacer push a main o al crear una etiqueta. Reemplace <owner> en deploy/poc-eks.yaml con el usuario u organización de GitHub que posee el 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 o el backport del proveedor.esp4 y esp6 si IPsec ESP no es necesario en los nodos de trabajo.user.max_user_namespaces=0 impide que este PoC obtenga CAP_NET_ADMIN en un nuevo espacio de nombres de red (esto ya es el predeterminado en ACK).privileged: true y acceso amplio al host a menos que sea estrictamente necesario.Ejemplo de bloque 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
El código del exploit está adaptado de V4bel/dirtyfrag bajo la licencia MIT.
El código de la carga útil se deriva de tgies/copy-fail-c y tiene doble licencia bajo LGPL-2.1-or-later O MIT.
Los encabezados nolibc provienen de la infraestructura de autopruebas del kernel Linux.
| Propiedad | Copy Fail | Dirty Frag |
|---|
| CVE | CVE-2026-31431 | CVE-2026-43284 |
| Ruta del kernel | AF_ALG + splice() | xfrm/ESP + splice() |
| Requisito de espacio de nombres | No requerido | Requiere espacios de nombres de usuario |
| Capacidad principal usada | Ninguna en el contenedor inicial | CAP_NET_ADMIN dentro del nuevo espacio de nombres de red |
| Módulo relevante | algif_aead | esp4 |
| Distinción práctica | Se rompe si el vector AF_ALG está bloqueado | Sigue siendo relevante cuando AF_ALG no está disponible pero ESP/espacios de nombres de usuario están habilitados |
| Propiedad | Valor |
|---|
| Plataforma | Amazon Elastic Kubernetes Service (EKS) |
| Kernel del Nodo | 6.12.80-106.156.amzn2023.x86_64 |
| Estado del Parche | Kernel anterior al fix, falta f4c50a4034e6 |
Módulo esp4 | Cargado |
| Espacios de Nombres de Usuario | Habilitados (user.max_user_namespaces=15030) |
| SELinux | Permisivo |
| Seccomp | Sin restricciones en el contexto del pod probado |
| DaemonSet Objetivo | kube-proxy |
| Privilegios del Objetivo | privileged: true, hostNetwork: true |
| Modo de Proxy | iptables |
| Ruta del Marcador | /root/res |
| Plataforma | Resultado | Razón |
|---|
| Alibaba Cloud ACK | Falló | user.max_user_namespaces está establecido en 0 en las imágenes de nodo predeterminadas, por lo que los usuarios sin privilegios no pueden usar CLONE_NEWUSER unshare. |
| Google GKE | Falló | user.max_user_namespaces es 15426, pero kubelet habilita --seccomp-default. La política seccomp predeterminada deshabilita la llamada al sistema unshare. |