Crypto-minage dans Kubernetes : le détecter dans les logs
Détecter le crypto-minage dans Kubernetes via les logs d'audit : images et arguments de mineurs, registries inhabituels, persistance CronJob et DaemonSet.
TL;DR. Un mineur dans un cluster est un workload, et tout workload est créé via l'API server. Dans le log d'audit, cherchez les écritures de workloads dont l'image ou les arguments nomment un mineur (XMRig et consorts) ou un pool de minage (stratum+tcp://, --donate-level), les images tirées de registries que vous n'utilisez pas, et la persistance qui les entoure : CronJobs, DaemonSets, souvent dans kube-system. Trouvez ensuite l'identité qui les a créés : le mineur est le symptôme, l'identifiant volé est l'incident. Le détournement de ressources correspond à ATT&CK T1496.
Le crypto-minage est la façon la plus courante dont une intrusion dans un cluster devient visible, parce qu'il coûte de l'argent et du CPU. C'est aussi la partie la moins intéressante de l'enquête : quiconque a déployé le mineur avait assez d'accès pour faire bien pire, et l'a peut-être fait.
Un schéma documenté
Microsoft a décrit une telle campagne en 2020 : des dashboards Kubeflow exposés à Internet via un load balancer permettaient à n'importe qui de déployer des conteneurs, et des attaquants s'en sont servis pour lancer une image exécutant XMRig sur des dizaines de clusters, pour la plupart sur AKS (blog Microsoft Security, « Misconfigured Kubeflow workloads are a security risk »). Le même billet explique l'attrait de ces clusters : les nœuds de machine learning sont puissants et ont parfois des GPU.
Le schéma est courant : un dashboard exposé ou un token surprivilégié comme point d'entrée, puis des workloads créés avec les propres identifiants du cluster. Le déroulé de l'incident fictif de ce site suit la même forme.
Ce que le log d'audit enregistre
Au niveau Request ou RequestResponse pour les écritures de workloads (le niveau par défaut sur EKS et GKE, voir le guide d'export), le requestObject d'un Deployment, DaemonSet, Job ou CronJob contient le template de pod : images, commandes, arguments, ressources. Un CronJob fictif :
{
"verb": "create",
"user": { "username": "system:serviceaccount:kube-system:node-health" },
"objectRef": { "resource": "cronjobs", "namespace": "kube-system", "name": "kube-state-sync", "apiGroup": "batch" },
"requestObject": {
"spec": {
"schedule": "*/5 * * * *",
"jobTemplate": { "spec": { "template": { "spec": {
"containers": [{
"name": "sync",
"image": "registry.k8s-mirror.example:5000/xmrig/xmrig:6.21.0",
"args": ["--url=pool.minexmr.example:4444", "--donate-level=0", "--cpu-max-threads-hint=75"],
"resources": { "limits": { "cpu": "2" } }
}]
} } } }
}
}
}
Tout ce qu'il vous faut tient dans un événement : le mineur, le pool, un nom qui imite un add-on Kubernetes, un namespace choisi pour se fondre dans le décor, un registry qui n'est pas le vôtre, une planification qui recrée les pods si quelqu'un les supprime, et une limite CPU pensée pour rester sous les radars.
Détections
| Règle | Sévérité | Logique |
|---|---|---|
| Image ou commande de crypto-minage | Critique | L'image contient un marqueur de mineur (xmrig, xmr-stak, minerd, cpuminer, nicehash, kinsing, c3pool, moneroocean…), ou la commande / les arguments du conteneur contiennent stratum+tcp, stratum+ssl, --donate-level, xmrig, minerd ; également les alertes Falco dont la commande correspond |
| Image issue d'un registry inhabituel | Faible | Registry hors d'une liste de registries publics et cloud courants (docker.io, registry.k8s.io, gcr.io, ghcr.io, quay.io, mcr.microsoft.com, public.ecr.aws, *.amazonaws.com, *.pkg.dev, *.azurecr.io…) |
| CronJob créé | Moyenne | Tout CronJob écrit par une identité non système |
| DaemonSet créé dans kube-system | Élevée | Voir pods privilégiés et évasion de conteneur |
La règle des mineurs est la seule de l'analyseur qui se déclenche quel que soit le créateur du pod, contrôleurs compris : un mineur lancé par un CronJob apparaît comme des pods créés par le contrôleur Job, et vous voulez quand même le voir. Le regroupement se fait par image : un seul constat couvre toutes les réplicas.
La règle des registries est volontairement de faible sévérité. Votre propre registry privé la déclenchera tant que vous ne l'aurez pas ajouté à la liste de confiance ; ensuite, tout le reste ressort.
L'évasion à laquelle s'attendre
La correspondance par nom attrape les attaquants paresseux. Les autres :
- renomment le binaire et l'image (
alpine,nginx,busyboxavec une charge téléchargée) ; - partent d'une image légitime et récupèrent le mineur dans la commande (
curl … | sh) ; - minent en TLS vers un proxy sur le port 443 au lieu d'un pool connu ;
- lancent le mineur sur le nœud après une évasion de conteneur, sans aucun appel d'API.
Regardez donc aussi le contexte que le log d'audit enregistre : nouveaux workloads créés par des identités qui ne déploient jamais, images issues de nouveaux registries, champs command au motif « télécharger et exécuter », CronJobs et DaemonSets créés hors de votre pipeline de déploiement, et workloads créés juste après un nouveau binding cluster-admin.
Signaux hors du log d'audit
Le minage fait du bruit sur d'autres canaux, et c'est souvent par là qu'on le découvre :
- Métriques CPU et nœuds : nœuds collés à leur limite, cluster autoscaler qui ajoute des nœuds sans raison métier.
- Trafic sortant : connexions vers des pools de minage, typiquement sur des ports comme 3333, 4444 ou 5555, ou vers des endpoints TLS inhabituels.
- Facturation cloud : un bond du compute, parfois dans des régions que vous n'utilisez pas.
- Détection runtime : Falco peut signaler un comportement de minage au niveau des processus et du réseau ; le projet décrit l'approche dans Cryptomining Detection Using Falco. Chargez les alertes Falco en JSON à côté des logs d'audit dans l'analyseur pour avoir les deux sur une même chronologie.
Un nettoyage qui fonctionne vraiment
Supprimer les pods ne suffit pas : le CronJob ou le DaemonSet les recrée en quelques minutes.
- Préservez : sauvegardez les specs (
kubectl get cronjob,daemonset,deployment -A -o yaml), les logs d'audit et, si une évasion de conteneur est possible, des snapshots disque des nœuds. - Trouvez chaque copie : cherchez l'image, l'adresse du pool et la commande dans tous les namespaces, pas seulement celui où vous l'avez trouvée.
- Supprimez d'abord les contrôleurs (CronJobs, DaemonSets, Deployments, Jobs), puis les pods restants.
- Supprimez l'accès : le binding et le compte de service utilisés pour les créer, et le point d'entrée d'origine.
- Reconstruisez les nœuds si le mineur a tourné en privilégié ou avec des montages de l'hôte.
- Bloquez le trafic sortant vers les pools de minage et restreignez les sorties avec des network policies.
- Restreignez les registries avec une politique d'admission (ValidatingAdmissionPolicy, Kyverno ou Gatekeeper), idéalement avec vérification de signature.
- Vérifiez la facturation et le log d'audit cloud pour tout ce qui aurait été créé hors du cluster avec les mêmes identifiants.