Attaque Kubernetes pas à pas : un incident fictif
Incident Kubernetes fictif pas à pas dans le log d'audit : token de dashboard exposé, recon can-i, vol de secrets, DaemonSet privilégié, cluster-admin, XMRig.
TL;DR. Voici un incident fictif, généré pour le bouton « Essayer un exemple » de l'analyseur de logs d'audit Kubernetes. Cluster, personnes, adresses IP, images et registry sont inventés. En quinze minutes d'événements d'audit, un attaquant utilise depuis Internet un token de compte de service du Kubernetes Dashboard exposé, cartographie ses permissions avec kubectl auth can-i, liste tous les secrets, déploie un DaemonSet privilégié qui monte le système de fichiers de chaque nœud, ouvre un shell root avec chroot /host, crée un compte de service lié à cluster-admin, planifie un CronJob XMRig et supprime les Events. Le verdict est « Compromission probable » ; cet article lit les preuves ligne par ligne.
Scénario fictif. Tout ce qui suit est synthétique. Les adresses Internet proviennent des plages réservées à la documentation (RFC 5737), les domaines se terminent par
.example, et aucune organisation, personne ou incident réel n'est décrit.
L'exemple couvre six heures (de 08:00 à 14:00 UTC le 14 septembre 2026) d'un petit cluster de production nommé shop-prod : trois nœuds workers qui renouvellent leurs leases, des contrôleurs qui réconcilient, Prometheus qui scrape, une CI basée sur Helm qui déploie deux fois par heure, une admin plateforme, Alice, qui utilise kubectl, et le Kubernetes Dashboard qui interroge les pods depuis son adresse interne au cluster. Environ 2 080 événements au total, dont une quarantaine appartiennent à l'attaque. Ce ratio est réaliste : la difficulté est de trouver ces quelques dizaines de lignes.
Le verdict
Charger l'exemple (le log d'audit plus deux alertes Falco) donne Compromission probable, porté par trois constats critiques (évasion de conteneur vers le nœud, binding vers cluster-admin, image de crypto-minage) et plusieurs constats de sévérité élevée. La checklist de remédiation commence par la préservation des preuves et la délimitation, puis la suppression des workloads malveillants, la reconstruction des nœuds, l'application de Pod Security, la suppression des bindings et la révocation des tokens.
Voici maintenant la chronologie, telle que l'affiche la chronologie de l'incident de l'analyseur (heures UTC).
13:01:58 – Une identité familière, depuis un lieu inhabituel
Le premier événement de l'attaque est un get /version par system:serviceaccount:kubernetes-dashboard:kubernetes-dashboard. Ce compte de service est actif depuis le matin, mais depuis 10.0.3.15 avec le user agent dashboard/v2.7.0. Cette requête-ci vient de 203.0.113.45, une adresse publique, avec kubectl/v1.30.2 (linux/amd64).
Deux constats se déclenchent : Token de compte de service utilisé depuis une IP publique (élevée) et Token de compte de service utilisé avec kubectl / curl (moyenne). L'histoire qu'ils racontent est simple : le token du dashboard est sorti du cluster et quelqu'un s'en sert à la main. Dans ce scénario, le dashboard tourne avec un rôle surprivilégié, et c'est ce qui rend tout le reste possible.
Les appels de découverte vers /api et /apis suivent dans les deux secondes, comme kubectl le fait toujours au premier contact.
13:02:31 – « Qu'ai-je le droit de faire ? »
Sept requêtes en quinze secondes : six selfsubjectaccessreviews et une selfsubjectrulesreviews. Avec une journalisation RequestResponse, les corps montrent exactement ce qui a été demandé : puis-je list secrets, create pods dans kube-system, create daemonsets dans kube-system, create clusterrolebindings, create pods/exec dans kube-system, et * * ? Tout est autorisé sauf le wildcard.
C'est l'Énumération de permissions (kubectl auth can-i), moyenne. Deux réponses 403 à 13:03 sur list nodes et list clusterroles confirment que le rôle est large mais pas illimité. L'attaquant sait maintenant que son plan fonctionnera. Voir secrets et vol de tokens pour ce schéma de reconnaissance.
13:04:02 – Tous les secrets du cluster
Un seul list sur secrets sans namespace : Secrets listés sur tous les namespaces (élevée). Cette unique réponse contient tous les secrets du cluster. L'attaquant lit ensuite douze secrets un par un entre 13:04:40 et 13:05:13 : identifiants de base de données, clé d'API de paiement, identifiants du registry, clé de signature JWT, token Vault, secret de webhook, identifiants Grafana et Alertmanager, token du déployeur de CI, clé privée d'une GitHub App, identifiants cloud et clé du bucket de sauvegarde etcd. La Rafale de lectures de secrets (moyenne) se déclenche au dixième nom distinct.
Pour l'intervenant, cette liste est le plan de rotation. Vu le list global, tous les autres secrets doivent aussi être considérés comme exposés.
13:06:12 – Un DaemonSet privilégié dans kube-system
Le token du dashboard crée un DaemonSet nommé kube-proxy-monitor dans kube-system : image alpine:3.20, hostPID et hostNetwork, un conteneur privilégié, le / du nœud monté sur /host, une toleration pour tous les taints afin d'atterrir sur chaque nœud. Cinq constats se déclenchent sur ce seul événement :
- Évasion de conteneur vers le nœud (critique) : privilégié plus hostPath
/plus hostPID ; - Conteneur privilégié déployé, Chemin hôte sensible monté, DaemonSet créé dans kube-system (élevée) ;
- Le workload partage les namespaces PID / réseau / IPC du nœud (moyenne).
Une seconde plus tard, le contrôleur DaemonSet crée trois pods, un par nœud. Ces créations sont faites par une identité système ; l'identité responsable est celle qui a écrit le DaemonSet. L'article sur les pods privilégiés explique pourquoi l'analyseur lit le template de pod à l'intérieur du workload.
13:07:40 – Un shell root sur le nœud
Un exec dans kube-proxy-monitor-x7k2p avec command=chroot&command=%2Fhost&command=sh : chroot /host sh, un shell sur le propre système de fichiers de worker-1. Un compte de service a exécuté une commande dans un conteneur (élevée). L'événement apparaît en trois stages partageant un même auditID (RequestReceived, ResponseStarted avec le code 101, ResponseComplete à 13:08:55) ; l'analyseur les regroupe en une session d'environ 75 secondes.
Le log d'audit s'arrête là : ce qui a été tapé dans ce shell est invisible. Le fichier Falco de l'exemple contient une alerte « Terminal shell in container » à 13:07:41 sur le même pod, avec chroot comme processus parent. C'est une alerte de niveau Notice, sous le seuil d'alerte de l'analyseur, mais elle figure sur la même chronologie et confirme le shell. L'article sur les limites explique pourquoi ce couplage compte.
13:09 – Une nouvelle identité avec cluster-admin
Trois écritures en 26 secondes :
create serviceaccountskube-system/node-health: un nom plausible.create clusterrolebindingsnode-health-admin,roleRefcluster-admin, sujet le nouveau compte de service : Binding vers cluster-admin créé (critique) et Binding de rôle cluster-wide créé (faible).create serviceaccounts/tokenpournode-health, avecexpirationSeconds: 31536000: Token de compte de service émis (moyenne). Un token valable un an, pour une identité que personne ne pensera à révoquer.
C'est l'étape de persistance décrite dans l'article sur l'escalade RBAC. À partir de 13:11:02, les requêtes viennent de node-health, toujours depuis 203.0.113.45 avec le même build de kubectl : l'IP et le user agent relient les deux identités à un même opérateur.
13:12:30 – Le mineur
node-health crée un CronJob kube-state-sync dans kube-system, toutes les cinq minutes, image registry.k8s-mirror.example:5000/xmrig/xmrig:6.21.0, arguments --url=pool.minexmr.example:4444 --donate-level=0. Constats : Image ou commande de crypto-minage (critique), CronJob créé (moyenne), Image issue d'un registry inhabituel (faible). À 13:15 et 13:20, le contrôleur Job crée les jobs et les pods ; la règle des mineurs se déclenche aussi sur eux, car un mineur reste un mineur, quel que soit celui qui l'a lancé.
La seconde alerte Falco, Critical, à 13:15:07 sur worker-2, montre xmrig qui se connecte au port 4444 : Alerte runtime Falco (élevée). L'article sur le crypto-minage couvre ce schéma.
13:16:40 – Effacer ses traces
deletecollection sur events dans kube-system : Événements Kubernetes supprimés (faible). Les Events auraient montré la planification des pods du DaemonSet et le téléchargement de l'image du mineur. Le log d'audit, stocké hors du cluster, a toujours tout. (Sur EKS, dont la politique managée ne journalise pas du tout les Events, cette étape ne serait pas visible.)
Le faux positif du rapport
Un constat n'a rien à voir avec l'attaque : Commande interactive dans un conteneur (exec / attach) (moyenne) pour alice@example.com à 10:12:03, un sh dans un pod d'API de shop. C'est une session de débogage légitime, depuis son IP habituelle, avec son kubectl habituel. C'est ainsi qu'il faut traiter les constats d'exec : confirmer avec le propriétaire, noter, passer à la suite. Voir détecter kubectl exec.
Ce que fait ensuite l'intervenant
En suivant la checklist de l'analyseur et le guide de réponse à incident :
- Préserver : exporter les logs d'audit, sauvegarder les specs du DaemonSet, du CronJob, du ServiceAccount et du ClusterRoleBinding, faire un snapshot de worker-1 (le shell) et des autres nœuds (des pods privilégiés ont tourné sur les trois).
- Contenir : supprimer
node-health-admin, supprimer puis recréer les deux comptes de service (le token d'un an émis meurt avecnode-health), restreindre l'accès à l'API server, mettre le dashboard hors ligne. - Éradiquer : supprimer le CronJob, ses Jobs et ses pods, ainsi que le DaemonSet ; chercher l'image et le pool dans tous les namespaces.
- Restaurer : remplacer les trois nœuds ; faire tourner chaque secret, en commençant par les identifiants cloud, la clé de la GitHub App et le token de CI, qui donnent accès hors du cluster.
- Élargir au-delà du cluster : les identifiants cloud et le token de CI volés doivent être suivis dans les logs d'audit du cloud et de la CI.
- Durcir : aucun dashboard exposé, rôle du dashboard au moindre privilège, Pod Security Admission sur
kube-system, registries de confiance uniquement.
Essayez vous-même
Ouvrez l'analyseur, cliquez sur Essayer un exemple et suivez : la chronologie, le pivot par entité sur 203.0.113.45 et la fiche détaillée de chaque événement montrent tout ce qui est décrit ici. Le guide pas à pas explique chaque panneau.