
PoC : évasion de conteneur totalement non privilégiée vers l'exécution de code au niveau du nœud sur Kubernetes via la corruption du page-cache CVE-2026-31431 + couches d'images partagées. Validé sur Alibaba Cloud ACK, Amazon EKS et Google GKE.
Une preuve de concept démontrant comment un conteneur totalement non privilégié peut obtenir l'exécution de code au niveau du nœud sur Kubernetes en exploitant la vulnérabilité CVE-2026-31431 du noyau Linux (corruption du cache de pages) via des couches d'images conteneur partagées.
La primitive d'attaque de base est la suivante : tout DaemonSet privilégié partageant des couches d'images avec un conteneur contrôlé par l'attaquant peut être utilisé comme arme pour une évasion de conteneur. Ce PoC utilise kube-proxy comme exemple concret, mais la technique se généralise à toute charge de travail privilégiée du cluster.
Validé sur Alibaba Cloud ACK, Amazon EKS et Google GKE — un pod non privilégié écrit [*] success sur le système de fichiers de l'hôte via le DaemonSet privilégié kube-proxy :
| Alibaba Cloud ACK (noyau 6.6.88) | Amazon EKS (noyau 6.12.79) | Google GKE (noyau 6.12.68) |
|---|---|---|
![]() | ![]() | ![]() |
Avertissement : Ce dépôt est publié à des fins exclusivement éducatives et défensives. Utilisez-le exclusivement sur des systèmes que vous possédez ou pour lesquels vous disposez d'une autorisation explicite de test.
CVE-2026-31431 (« Copy Fail ») est une vulnérabilité du noyau Linux dans le chemin Copy-on-Write (CoW) du cache de pages. Une condition de course splice AF_ALG permet à un processus non privilégié de corrompre les pages du cache de pages d'un fichier en lecture seule. La corruption persiste dans le cache de pages du noyau et est visible par tout processus qui lit ou exécute ensuite le fichier — y compris les processus d'autres conteneurs ou de l'hôte.
Pour tous les détails sur la vulnérabilité d'origine, voir copy.fail.
L'attaque exploite trois propriétés qui coexistent couramment dans les clusters Kubernetes :
privileged: true, hostNetwork: true, des capacités étendues, etc.) qui exécutent périodiquement des binaires de leur image.Lorsque ces conditions sont réunies, un pod non privilégié peut corrompre un binaire dans une couche d'image partagée, et un DaemonSet privilégié sur le même nœud exécutera sans le savoir le binaire corrompu avec ses privilèges élevés — aboutissant à une exécution complète de code au niveau du nœud.
La cible de la vulnérabilité n'est PAS limitée à kube-proxy. Tout DaemonSet privilégié (agents de surveillance, plugins CNI, collecteurs de journaux, agents de sécurité, etc.) dont l'image conteneur partage des couches avec une image contrôlée par l'attaquant est une cible viable.
La chaîne d'attaque comporte trois étapes : corruption du cache de pages, propagation entre conteneurs et exécution privilégiée.
Le sous-système AF_ALG (crypto) du noyau expose une interface basée sur des sockets pour les opérations cryptographiques de l'espace utilisateur. L'exploit abuse d'une condition de course dans la façon dont le noyau gère splice() d'un fichier vers une socket AF_ALG :
authencesn(hmac(sha256),cbc(aes)).MSG_MORE, indiquant au noyau d'attendre plus de données.splice() le contenu du fichier cible d'un fd → pipe → socket AF_ALG.L'exploit répète cette opération pour chaque fenêtre de 4 octets jusqu'à ce que toutes les pages en cache du binaire cible soient écrasées par un payload personnalisé.
Aucune permission d'écriture sur le fichier n'est nécessaire. Le fichier sur disque reste inchangé — seul le cache de pages en mémoire est corrompu.
Les runtimes de conteneurs utilisent des systèmes de fichiers overlay. Lorsque deux conteneurs partagent la même couche d'image, le noyau sert leurs lectures de fichiers à partir des mêmes pages du cache de pages.
L'attaquant construit son image PoC FROM la même image de base que le DaemonSet privilégié ciblé. Comme les deux conteneurs partagent le même lowerdir overlay, les binaires de la couche partagée correspondent à des pages du cache de pages identiques.
Lorsque le conteneur PoC non privilégié corrompt le cache de pages d'un binaire, la corruption est immédiatement visible pour le conteneur privilégié sur le même nœud — sans aucune communication entre conteneurs.
Lorsque le DaemonSet privilégié exécute ensuite un binaire corrompu (au cours de son cycle de fonctionnement normal), le noyau charge les pages corrompues du cache de pages. Le payload de l'attaquant s'exécute avec tous les privilèges du DaemonSet — pouvant inclure :
Le payload de ce PoC (payload/payload.c) monte simplement le système de fichiers racine de l'hôte et écrit un fichier marqueur dans /root/res comme preuve de l'exécution de code au niveau du nœud.
┌──────────────────────────┐ ┌──────────────────────────┐
│ PoC Container │ │ Privileged DaemonSet │
│ (unprivileged) │ │ (e.g. kube-proxy, │
│ │ │ monitoring agent, etc.)│
│ 1. Open target binary │ │ │
│ (read-only) │ │ │
│ │ │ │
│ 2. AF_ALG splice race │ │ │
│ corrupts page cache │ │ │
│ │ │ │ │
└──────────┼───────────────┘ └──────────────────────────┘
│ │
▼ │
┌─────────────────────┐ │
│ Kernel Page Cache │ │
│ │◄────────────────────┘
│ Shared-layer binary │ 3. DaemonSet executes the
│ (CORRUPTED) │ corrupted binary
│ contains attacker's │ → loads corrupted pages
│ payload bytes │ → payload runs with
└─────────────────────┘ DaemonSet's privileges
Le PoC a été validé avec succès sur les plateformes Kubernetes gérées suivantes :



Dans les trois cas, un pod PoC non privilégié a écrit avec succès le fichier marqueur [*] success sur le système de fichiers de l'hôte — prouvant l'exécution de code au niveau du nœud via le DaemonSet privilégié kube-proxy.
Pour les procédures pas à pas complètes (analyse des couches d'images, étapes de compilation, déploiement) :
Ce PoC utilise kube-proxy comme cible car c'est l'un des DaemonSets privilégiés les plus courants dans les clusters Kubernetes. Trois variantes sont fournies :
FROM registry.k8s.io/kube-proxy:v1.35.2 (voir Dockerfile)FROM public.ecr.aws/eks-distro-build-tooling/eks-distro-minimal-base-iptables:2026-03-11-1773190710.2023 (voir Dockerfile.eks)FROM us-central1-artifactregistry.gcr.io/gke-release/gke-release/kube-proxy:v1.35.3-gke.1234000 (voir Dockerfile.gke)Toutes les variantes corrompent des binaires comme /usr/sbin/ipset, /usr/sbin/nft, /usr/sbin/xtables-legacy-multi et /usr/sbin/xtables-nft-multi.
Points d'attention importants :
ipset que lorsqu'il est configuré en mode ipvs. Le mode par défaut (iptables) n'utilise pas ipset. Voir kubernetes/enhancements#5495 pour le plan de dépréciation d'ipvs.ipset, nft, xtables-legacy-multi, xtables-nft-multi) pour couvrir différents modes de proxy, mais la question de savoir s'ils sont invoqués dépend de la configuration du cluster.Si kube-proxy n'est pas privilégié dans votre cluster, le principe de l'attaque reste valable — il vous suffit d'identifier un autre DaemonSet privilégié qui partage des couches d'images avec une image de base à partir de laquelle vous pouvez construire.
Pour adapter ce PoC à un autre DaemonSet privilégié :
FROM la même image de base que celle utilisée par ce DaemonSet..
├── cmd/copyfail/main.go # Entry point; embeds compiled payload
├── internal/
│ ├── exploit/
│ │ ├── exploit.go # Core exploit: AF_ALG splice race loop
│ │ └── patch.go # Splits payload into 4-byte patch windows
│ └── alg/
│ └── alg.go # AF_ALG AEAD socket abstraction
├── payload/
│ ├── payload.c # ACK/upstream payload (mount /dev/vda3 ext4)
│ ├── payload-eks.c # EKS payload (NVMe/Xen device auto-detection)
│ ├── payload-gke.c # GKE payload (COS/Ubuntu device auto-detection)
│ └── nolibc/ # Kernel's tiny libc for static, no-dependency payloads
├── deploy/
│ ├── poc.yaml # Kubernetes Deployment manifest (ACK/upstream)
│ ├── poc-eks.yaml # EKS Deployment manifest
│ └── poc-gke.yaml # GKE Deployment manifest
├── 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 # Build orchestration (includes *-eks and *-gke targets)
└── docs/
├── eks-poc.md # EKS PoC full walkthrough
├── gke-poc.md # GKE PoC full walkthrough
├── ack-poc-res.png # ACK validation screenshot
├── eks-poc-res.png # EKS validation screenshot
└── gke-poc-res.png # GKE validation screenshot
x86_64-linux-gnu-gcc)imagePullPolicy: IfNotPresent sur le DaemonSet cible (la valeur par défaut de Kubernetes)# Build payload + Go binary
make build
# Build Docker image
make docker-build
# Build and push to GHCR
make docker-push IMAGE=ghcr.io/<you>/copy-fail-poc TAG=latest
# Build EKS payload + Go binary + Docker image
make docker-build-eks
# Build and push to GHCR
make docker-push-eks IMAGE=ghcr.io/<you>/copy-fail-poc
Pour les cibles arm64 (Graviton) :
make build-eks CC=aarch64-linux-gnu-gcc GOARCH=arm64
# Build GKE payload + Go binary + Docker image
make docker-build-gke
# Build and push to GHCR
make docker-push-gke IMAGE=ghcr.io/<you>/copy-fail-poc
Pour les nœuds 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
Le Deployment crée un seul pod non privilégié. Il :
/bin/copyfail pour corrompre le cache de pages des binaires cibles dans la couche d'image partagée.Une fois que le DaemonSet privilégié cible a exécuté un binaire corrompu (pour kube-proxy, cela se produit généralement en quelques secondes grâce à sa boucle de réconciliation), vérifiez le nœud :
# SSH into the node, or use a privileged debug pod
# ACK / EKS (writable root filesystem)
cat /root/res
# Expected output: [*] success
# GKE COS nodes (read-only root, writable stateful partition)
cat /mnt/stateful_partition/copyfail-res
# Expected output: [*] success
La présence du fichier marqueur sur le système de fichiers de l'hôte prouve que le code fourni par l'attaquant s'est exécuté avec des privilèges au niveau du nœud — depuis le contexte du conteneur du DaemonSet privilégié.
kubectl delete -f deploy/poc.yaml # or poc-eks.yaml / poc-gke.yaml
# On the affected node(s), remove the marker and restart the target DaemonSet:
rm -f /root/res # ACK / EKS
rm -f /copyfail-res /mnt/stateful_partition/copyfail-res # GKE COS nodes
# For kube-proxy: delete the pod to force image layer re-read
kubectl delete pod -n kube-system -l k8s-app=kube-proxy --field-selector spec.nodeName=<node>
Le payload par défaut (payload/payload.c) est un programme de validation uniquement qui écrit un fichier marqueur. Pour construire un payload personnalisé :
payload/payload.c. Le programme est compilé avec nolibc (la bibliothèque C minimale du noyau) pour produire un binaire statique sans dépendances.make payload pour compiler en croisé.//go:embed.privileged: true.AF_ALG. La règle est disponible via les mécanismes d'application AppArmor et BPF.tools/include/nolibc/)Le code d'exploitation Go de ce dépôt est fourni tel quel à des fins de recherche.
Le payload (payload/payload.c) est dérivé de copy-fail-c et est sous double licence LGPL-2.1-or-later OU MIT. Voir LICENSE-LGPL et LICENSE-MIT.
| Propriété | Valeur |
|---|
| Plateforme | Alibaba Cloud Container Service for Kubernetes (ACK) |
| Kubernetes | v1.35.2 |
| Noyau du nœud | 6.6.88-4.2.alnx4.x86_64 |
| kube-proxy | registry-cn-*.ack.aliyuncs.com/acs/kube-proxy:v1.35.2-aliyun.1 |
| Image de base | registry.k8s.io/kube-proxy:v1.35.2 (amont) |
| Périphérique racine | /dev/vda3 (ext4) |
| Propriété | Valeur |
|---|
| Plateforme | Amazon Elastic Kubernetes Service (EKS) |
| Kubernetes | v1.35.4 |
| Noyau du nœud | 6.12.79-101.147.amzn2023.x86_64 |
| kube-proxy | ***.dkr.ecr.***.amazonaws.com.cn/eks/kube-proxy:v1.35.3-eksbuild.2 |
| Image de base | public.ecr.aws/eks-distro-build-tooling/eks-distro-minimal-base-iptables:2026-03-11-1773190710.2023 |
| Périphérique racine | /dev/nvme0n1p1 (xfs) |
| Propriété | Valeur |
|---|
| Plateforme | Google Kubernetes Engine (GKE) |
| Kubernetes | v1.35.3-gke.1234000 |
| OS du nœud | Container-Optimized OS (COS) 125, BUILD_ID 19216.220.72 |
| Noyau du nœud | 6.12.68+ x86_64 |
| kube-proxy | us-central1-artifactregistry.gcr.io/gke-release/gke-release/kube-proxy:v1.35.3-gke.1234000 |
| Image de base | Identique à kube-proxy (image Artifact Registry gérée par le fournisseur GKE) |
| Périphérique racine | /dev/dm-0 (ext2, lecture seule) ; /dev/sda1 (ext4, partition avec état inscriptible) |
| Chemin du marqueur | /mnt/stateful_partition/copyfail-res |