
Proof-of-concept che dimostra l'evasione dal container su Amazon EKS sfruttando Dirty Frag (CVE-2026-43284), una corruzione della page-cache del kernel tramite layer di immagini condivisi e DaemonSets privilegiati.
Una proof-of-concept che dimostra come un Pod Kubernetes predefinito e non privilegiato possa ottenere esecuzione di codice a livello di nodo su Amazon EKS sfruttando la vulnerabilità di corruzione della page cache del kernel Linux Dirty Frag tramite livelli di immagine container condivisi.
Il primitivo di attacco principale è: qualsiasi DaemonSet privilegiato che condivide livelli di immagine con un container controllato dall'attaccante può essere armato per il container escape. Questo PoC usa kube-proxy come esempio concreto, ma la tecnica si generalizza a qualsiasi workload privilegiato sul cluster.
Validato su Amazon EKS (kernel 6.12.80) — un pod non privilegiato scrive [*] success nel filesystem dell'host tramite il DaemonSet privilegiato kube-proxy:

Disclaimer: Questo repository è pubblicato esclusivamente a scopo educativo e difensivo. Usalo solo su sistemi di tua proprietà o per i quali hai esplicita autorizzazione al test.
Dirty Frag (CVE-2026-43284) è una vulnerabilità di corruzione della page cache del kernel Linux nel percorso di ricezione xfrm/ESP. Nel percorso interessato, esp_input() può saltare skb_cow_data() per uno skb non lineare senza frag_list, consentendo a crypto_authenc_esn_decrypt() di memorizzare 4 byte di dati controllati dall'attaccante in una pagina della page cache raggiunta tramite splice().
Il file su disco non viene modificato. I byte corrotti risiedono nella page cache del kernel e vengono osservati dai successivi lettori della stessa pagina di file in cache.
Per tutti i dettagli sulla vulnerabilità originale, vedi V4bel/dirtyfrag.
L'attacco sfrutta tre proprietà che coesistono comunemente nei cluster Kubernetes:
privileged: true, hostNetwork: true, ampie capability, ecc.) che eseguono periodicamente binari dalla propria immagine.Quando queste condizioni si allineano, un pod non privilegiato può corrompere un binario in un livello di immagine condiviso, e un DaemonSet privilegiato sullo stesso nodo eseguirà inconsapevolmente il binario corrotto con i suoi privilegi elevati — ottenendo piena esecuzione di codice a livello di nodo.
Il target della vulnerabilità NON è limitato a kube-proxy. Qualsiasi DaemonSet privilegiato (agenti di monitoraggio, plugin CNI, collector di log, agenti di sicurezza, ecc.) la cui immagine container condivide livelli con un'immagine controllata dall'attaccante è un target valido.
Questo progetto è ispirato al modello di sfruttamento Kubernetes documentato nel Copy Fail Kubernetes PoC, ma usa un primitivo del kernel diverso.
La catena di attacco ha tre fasi: corruzione della page cache, propagazione tra container ed esecuzione privilegiata.
Il binario PoC esegue la seguente sequenza da un container non privilegiato:
unshare(CLONE_NEWUSER | CLONE_NEWNET).splice() e input ESP appositamente costruiti per attivare il percorso vulnerabile del kernel.Non è richiesto alcun permesso di scrittura sul file target. Il file su disco rimane invariato — solo la page cache in memoria viene corrotta.
I runtime container servono le letture dai livelli inferiori overlay tramite la page cache del kernel. Se il container PoC e kube-proxy condividono lo stesso file di livello inferiore, entrambi osservano le stesse pagine in cache.
L'immagine EKS in questo repository è costruita da:
public.ecr.aws/eks-distro-build-tooling/eks-distro-minimal-base-iptables:2026-03-11-1773190710.2023
Quella base è scelta per corrispondere al livello della toolchain userspace kube-proxy EKS usato nell'ambiente validato.
Quando kube-proxy esegue successivamente un binario della famiglia iptables patchato, il kernel carica le pagine in cache corrotte. Il payload PoC monta il dispositivo root dell'host e scrive un file marcatore in /root/res.
Il contenuto atteso del marcatore è:
[*] success
┌──────────────────────────────┐ ┌────────────────────────┐ ┌──────────────────────────┐
│ Pod PoC │ │ Page Cache del Kernel │ │ DaemonSet kube-proxy │
│ container non privilegiato │ │ │ │ container privilegiato │
│ │ │ │ │ │
│ 1. unshare user+net ns │ │ │ │ │
│ 2. installa SA xfrm │ │ │ │ │
│ 3. splice binario target │────▶│ binario livello │────▶│ esegue binario patchato │
│ tramite percorso ESP │ │ condiviso page cache │ │ payload gira con │
│ │ │ patchato │ │ privilegi di nodo │
└──────────────────────────────┘ └────────────────────────┘ └──────────────────────────┘
Ho testato su cluster GKE e ACK. Tutti falliti.
Il primitivo Dirty Frag richiede la creazione di user namespace (CLONE_NEWUSER) per ottenere CAP_NET_ADMIN all'interno di un nuovo network namespace. Sia ACK che GKE bloccano questo a livello di nodo tramite meccanismi diversi:
user.max_user_namespaces=0) impedisce completamente la creazione di user namespace non privilegiati.--seccomp-default di kubelet) blocca la syscall unshare indipendentemente dal limite dei namespace.Questa è una differenza chiave rispetto a Copy Fail (CVE-2026-31431), che non richiede user namespace e sfrutta con successo tutte e tre le piattaforme.
La variante EKS fornita patch i seguenti binari quando presenti:
/usr/sbin/xtables-legacy-multi
/usr/sbin/xtables-nft-multi
Questi binari vengono invocati dalla toolchain iptables usata da kube-proxy. La tempistica esatta del trigger dipende dall'attività di riconciliazione di nodi e servizi. Nell'ambiente validato, il payload è stato attivato dalla normale riconciliazione di kube-proxy.
Avvertenze importanti:
ipset solo quando configurato in modalità ipvs. La modalità predefinita (iptables) non usa ipset.xtables-legacy-multi, xtables-nft-multi) per coprire diverse modalità proxy, ma se vengono invocati dipende dalla configurazione del cluster.Se kube-proxy non è privilegiato nel tuo cluster, il principio di attacco rimane valido — devi solo identificare un diverso DaemonSet privilegiato che condivida livelli di immagine con un'immagine base da cui puoi costruire.
.
├── exploit/
│ └── dirtyfrag.c # Scrittore page cache xfrm/ESP
├── payload/
│ ├── payload-eks.c # Payload nolibc che scrive /root/res sull'host
│ └── nolibc/ # Header Linux nolibc
├── deploy/
│ └── poc-eks.yaml # Manifest Deployment EKS non privilegiato
├── scripts/
│ ├── setup-eks.sh # Copia, compila e importa l'immagine su un nodo EKS
│ ├── run-poc.sh # Distribuisce e verifica il marcatore
│ └── cleanup.sh # Rimuove pod, marcatore, pagine in cache e immagine locale
├── Dockerfile.eks # Immagine EKS basata su eks-distro-minimal-base-iptables
├── Makefile # Target di build per payload, exploit, Docker e nerdctl
└── .github/workflows/
└── docker-publish.yml # Workflow di pubblicazione GHCR
# Compila payload + binario exploit
make build-eks CC=x86_64-linux-gnu-gcc
# Compila immagine Docker
make docker-build-eks
# Distribuisci (pod non privilegiato)
kubectl apply -f deploy/poc-eks.yaml
# Controlla i log
kubectl logs deployment/dirtyfrag-poc-eks
# Verifica l'escape sul nodo
ssh ec2-user@<node-ip> "sudo cat /root/res"
# Atteso: [*] success
Il workflow GitHub Actions (.github/workflows/docker-publish.yml) pubblica l'immagine su GHCR al push su main o alla creazione di un tag. Sostituisci <owner> in deploy/poc-eks.yaml con l'utente o l'organizzazione GitHub che possiede il 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 il backport del vendor.esp4 e esp6 se IPsec ESP non è richiesto sui nodi worker.user.max_user_namespaces=0 impedisce a questo PoC di ottenere CAP_NET_ADMIN in un nuovo network namespace (questo è già il predefinito su ACK).privileged: true e ampio accesso host se non strettamente necessario.Esempio di blocco modulo:
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
Il codice dell'exploit è adattato da V4bel/dirtyfrag sotto licenza MIT.
Il codice del payload deriva da tgies/copy-fail-c ed è concesso in doppia licenza LGPL-2.1-or-later OPPURE MIT.
Gli header nolibc provengono dall'infrastruttura di self-test del kernel Linux.
| Proprietà | Copy Fail | Dirty Frag |
|---|
| CVE | CVE-2026-31431 | CVE-2026-43284 |
| Percorso kernel | AF_ALG + splice() | xfrm/ESP + splice() |
| Requisito namespace | Non richiesto | Richiede user namespace |
| Capability principale usata | Nessuna nel container iniziale | CAP_NET_ADMIN all'interno del nuovo net namespace |
| Modulo rilevante | algif_aead | esp4 |
| Distinzione pratica | Fallisce se il vettore AF_ALG è bloccato | Ancora rilevante quando AF_ALG non è disponibile ma ESP/user namespace sono abilitati |
| Proprietà | Valore |
|---|
| Piattaforma | Amazon Elastic Kubernetes Service (EKS) |
| Kernel del Nodo | 6.12.80-106.156.amzn2023.x86_64 |
| Stato Patch | Kernel pre-fix, manca f4c50a4034e6 |
Modulo esp4 | Caricato |
| User Namespaces | Abilitati (user.max_user_namespaces=15030) |
| SELinux | Permissivo |
| Seccomp | Non confinato nel contesto del pod testato |
| DaemonSet Target | kube-proxy |
| Privilegi Target | privileged: true, hostNetwork: true |
| Modalità Proxy | iptables |
| Percorso Marcatore | /root/res |
| Piattaforma | Risultato | Motivo |
|---|
| Alibaba Cloud ACK | Fallito | user.max_user_namespaces è impostato a 0 sulle immagini nodo predefinite, quindi gli utenti non privilegiati non possono usare CLONE_NEWUSER unshare. |
| Google GKE | Fallito | user.max_user_namespaces è 15426, ma kubelet abilita --seccomp-default. La policy seccomp predefinita disabilita la syscall unshare. |