
Preuve de concept démontrant une évasion de conteneur sur Amazon EKS en exploitant Dirty Frag (CVE-2026-43284), une corruption du cache de pages du noyau via des couches d'images partagées et des DaemonSets privilégiés.
Une preuve de concept démontrant comment un Pod Kubernetes par défaut, non privilégié peut atteindre une exécution de code au niveau du nœud sur Amazon EKS en exploitant la vulnérabilité de corruption du cache de pages du noyau Linux Dirty Frag via des couches d'images conteneur partagées.
La primitive d'attaque principale est la suivante : tout DaemonSet privilégié partageant des couches d'images avec un conteneur contrôlé par l'attaquant peut être transformé en vecteur d'évasion de conteneur. Cette preuve de concept utilise kube-proxy comme exemple concret, mais la technique se généralise à toute charge de travail privilégiée du cluster.
Validé sur Amazon EKS (noyau 6.12.80) — un pod non privilégié écrit [*] success dans le système de fichiers de l'hôte via le DaemonSet privilégié kube-proxy :

Avertissement : Ce dépôt est publié à des fins éducatives et défensives uniquement. Utilisez-le exclusivement sur des systèmes que vous possédez ou pour lesquels vous disposez d'une autorisation explicite de test.
Dirty Frag (CVE-2026-43284) est une vulnérabilité de corruption du cache de pages du noyau Linux dans le chemin de réception xfrm/ESP. Dans le chemin affecté, esp_input() peut ignorer skb_cow_data() pour un skb non linéaire sans frag_list, permettant à crypto_authenc_esn_decrypt() de stocker 4 octets de données contrôlées par l'attaquant dans une page du cache de pages atteinte via splice().
Le fichier sur disque n'est pas modifié. Les octets corrompus résident dans le cache de pages du noyau et sont observés par les lecteurs ultérieurs de la même page de fichier en cache.
Pour tous les détails sur la vulnérabilité d'origine, voir V4bel/dirtyfrag.
L'attaque exploite trois propriétés qui coexistent couramment dans les clusters Kubernetes :
privileged: true, hostNetwork: true, 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 — atteignant ainsi une exécution de code complète 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.
Ce projet s'inspire du modèle d'exploitation Kubernetes documenté dans la preuve de concept Kubernetes Copy Fail, mais utilise une primitive noyau différente.
La chaîne d'attaque comporte trois étapes : corruption du cache de pages, propagation entre conteneurs et exécution privilégiée.
Le binaire de la preuve de concept effectue la séquence suivante depuis un conteneur non privilégié :
unshare(CLONE_NEWUSER | CLONE_NEWNET).splice() et une entrée ESP conçue pour déclencher le chemin vulnérable du noyau.Aucune permission d'écriture sur le fichier cible n'est requise. Le fichier sur disque est inchangé — seule la mémoire du cache de pages est corrompue.
Les runtimes de conteneurs servent les lectures des couches inférieures overlay via le cache de pages du noyau. Si le conteneur de la preuve de concept et kube-proxy partagent le même fichier de couche inférieure, les deux observent les mêmes pages en cache.
L'image EKS de ce dépôt est construite à partir de :
public.ecr.aws/eks-distro-build-tooling/eks-distro-minimal-base-iptables:2026-03-11-1773190710.2023
Cette base est choisie pour correspondre à la couche de chaîne d'outils espace utilisateur kube-proxy EKS utilisée dans l'environnement validé.
Lorsque kube-proxy exécute ensuite un binaire de la famille iptables corrigé, le noyau charge les pages en cache corrompues. La charge utile de la preuve de concept monte le périphérique racine de l'hôte et écrit un fichier marqueur dans /root/res.
Le contenu attendu du marqueur est :
[*] success
┌──────────────────────────────┐ ┌────────────────────────┐ ┌──────────────────────────┐
│ Pod de la preuve de concept │ │ Cache de pages noyau │ │ DaemonSet kube-proxy │
│ conteneur non privilégié │ │ │ │ conteneur privilégié │
│ │ │ │ │ │
│ 1. unshare user+net ns │ │ │ │ │
│ 2. installer les SA xfrm │ │ │ │ │
│ 3. splice du binaire cible │────▶│ binaire de couche │────▶│ exécute le binaire │
│ via le chemin ESP │ │ partagée corrigé │ │ corrigé │
│ │ │ dans le cache de pages│ │ la charge utile │
│ │ │ │ │ s'exécute avec │
│ │ │ │ │ des privilèges de nœud │
└──────────────────────────────┘ └────────────────────────┘ └──────────────────────────┘
J'ai testé sur des clusters GKE et ACK. Tous ont échoué.
La primitive Dirty Frag exige la création d'espaces de noms utilisateur (CLONE_NEWUSER) pour obtenir CAP_NET_ADMIN dans un nouvel espace de noms réseau. ACK et GKE bloquent tous deux cela au niveau du nœud par différents mécanismes :
user.max_user_namespaces=0) empêche complètement la création d'espaces de noms utilisateur non privilégiés.--seccomp-default de kubelet) bloque l'appel système unshare quelle que soit la limite d'espaces de noms.C'est une différence clé avec Copy Fail (CVE-2026-31431), qui ne requiert pas d'espaces de noms utilisateur et exploite avec succès les trois plateformes.
La variante EKS fournie corrige les binaires suivants lorsqu'ils sont présents :
/usr/sbin/xtables-legacy-multi
/usr/sbin/xtables-nft-multi
Ces binaires sont invoqués par la chaîne d'outils iptables utilisée par kube-proxy. Le moment exact du déclenchement dépend de l'activité de réconciliation des nœuds et des services. Dans l'environnement validé, la charge utile a été déclenchée par la réconciliation normale de kube-proxy.
Avertissements importants :
ipset que lorsqu'il est configuré en mode ipvs. Le mode par défaut (iptables) n'utilise pas ipset.xtables-legacy-multi, xtables-nft-multi) pour couvrir différents modes de proxy, mais leur invocation 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 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.
.
├── exploit/
│ └── dirtyfrag.c # Écrivain du cache de pages xfrm/ESP
├── payload/
│ ├── payload-eks.c # Charge utile nolibc qui écrit /root/res sur l'hôte
│ └── nolibc/ # En-têtes Linux nolibc
├── deploy/
│ └── poc-eks.yaml # Manifeste de déploiement EKS non privilégié
├── scripts/
│ ├── setup-eks.sh # Copie, construit et importe l'image sur un nœud EKS
│ ├── run-poc.sh # Déploie et vérifie le marqueur
│ └── cleanup.sh # Supprime le pod, le marqueur, les pages en cache et l'image locale
├── Dockerfile.eks # Image EKS basée sur eks-distro-minimal-base-iptables
├── Makefile # Cibles de construction payload, exploit, Docker et nerdctl
└── .github/workflows/
└── docker-publish.yml # Workflow de publication GHCR
# Construire la charge utile + le binaire d'exploitation
make build-eks CC=x86_64-linux-gnu-gcc
# Construire l'image Docker
make docker-build-eks
# Déployer (pod non privilégié)
kubectl apply -f deploy/poc-eks.yaml
# Vérifier les journaux
kubectl logs deployment/dirtyfrag-poc-eks
# Vérifier l'évasion sur le nœud
ssh ec2-user@<node-ip> "sudo cat /root/res"
# Attendu : [*] success
Le workflow GitHub Actions (.github/workflows/docker-publish.yml) publie l'image sur GHCR lors d'un push vers main ou de la création d'un tag. Remplacez <owner> dans deploy/poc-eks.yaml par l'utilisateur ou l'organisation GitHub propriétaire du 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 ou le backport du fournisseur.esp4 et esp6 si IPsec ESP n'est pas requis sur les nœuds de travail.user.max_user_namespaces=0 empêche cette preuve de concept d'obtenir CAP_NET_ADMIN dans un nouvel espace de noms réseau (c'est déjà le défaut sur ACK).privileged: true et l'accès étendu à l'hôte sauf si strictement requis.Exemple de bloc de module :
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
Le code d'exploitation est adapté de V4bel/dirtyfrag sous licence MIT.
Le code de la charge utile est dérivé de tgies/copy-fail-c et est sous double licence LGPL-2.1-or-later OU MIT.
Les en-têtes nolibc proviennent de l'infrastructure d'auto-test du noyau Linux.
| Propriété | Copy Fail | Dirty Frag |
|---|
| CVE | CVE-2026-31431 | CVE-2026-43284 |
| Chemin noyau | AF_ALG + splice() | xfrm/ESP + splice() |
| Exigence d'espace de noms | Non requise | Requiert des espaces de noms utilisateur |
| Capacité principale utilisée | Aucune dans le conteneur initial | CAP_NET_ADMIN dans le nouvel espace de noms réseau |
| Module concerné | algif_aead | esp4 |
| Distinction pratique | Échoue si le vecteur AF_ALG est bloqué | Toujours pertinent lorsque AF_ALG est indisponible mais que ESP/les espaces de noms utilisateur sont activés |
| Propriété | Valeur |
|---|
| Plateforme | Amazon Elastic Kubernetes Service (EKS) |
| Noyau du nœud | 6.12.80-106.156.amzn2023.x86_64 |
| État du correctif | Noyau pré-correctif, manquant f4c50a4034e6 |
Module esp4 | Chargé |
| Espaces de noms utilisateur | Activés (user.max_user_namespaces=15030) |
| SELinux | Permissif |
| Seccomp | Non confiné dans le contexte du pod testé |
| DaemonSet cible | kube-proxy |
| Privilèges cibles | privileged: true, hostNetwork: true |
| Mode proxy | iptables |
| Chemin du marqueur | /root/res |
| Plateforme | Résultat | Raison |
|---|
| Alibaba Cloud ACK | Échec | user.max_user_namespaces est défini sur 0 sur les images de nœuds par défaut, donc les utilisateurs non privilégiés ne peuvent pas utiliser CLONE_NEWUSER unshare. |
| Google GKE | Échec | user.max_user_namespaces est 15426, mais kubelet active --seccomp-default. La politique seccomp par défaut désactive l'appel système unshare. |