Pour créer ou entrer dans un conteneur, runc se ré-exécute à l'intérieur des namespaces du conteneur via /proc/self/exe, un lien symbolique que le noyau maintient vers l'exécutable en cours d'exécution, qui pointe ici vers le binaire runc de l'hôte. L'évasion classique abuse du moment où le propre code du conteneur s'exécute avec un chemin de descripteur de fichier qui se résout encore vers ce binaire de l'hôte. Une image malveillante fait en sorte que son processus d'entrée ouvre /proc/self/exe (le runc de l'hôte) en écriture, ou qu'il soit un chargeur dynamique ou un shim de script permettant au conteneur d'influencer ce que runc ré-exécute, et écrase le binaire. Comme runc s'exécute en tant que root sur l'hôte pour chaque action du cycle de vie du conteneur, le binaire écrasé exécute le code de l'attaquant en tant que root sur l'hôte la prochaine fois qu'un conteneur est démarré ou intégré.
Confirmez le runtime et la fenêtre de versions :
runc --version 2>/dev/null # pre-fix runc (older 1.0-rc line) is affected
readlink /proc/self/exe # resolves to the runtime binary during entry
Technique#
# A malicious image ships an entrypoint that, when runc execs into it:
# 1. Opens /proc/self/exe, which still resolves to the host runc binary
# 2. Holds the descriptor until runc has dropped into the container, then
# reopens it via /proc/self/fd/<n> for writing and overwrites the ELF
# 3. Writes a payload binary in place of runc
# Minimal shape of the entry payload:
exec 3< /proc/self/exe # fd to the host runc
# busy-wait until the fd is writable (runc has finished its exec), then:
cat payload_elf > /proc/self/fd/3 # overwrite host runc on disk
Le prochain create/start/exec de conteneur sur l'hôte invoque le runc désormais malveillant, exécutant la charge en tant que root sur l'hôte.
Notes d'exploitation#
- L'attaquant doit exécuter du code dans un conteneur depuis une image ou un processus qu'il contrôle ; c'est pourquoi il s'agit d'un risque majeur lorsqu'une plateforme exécute des images non fiables ou autorise un
execfourni par l'utilisateur. - Les correctifs font en sorte que
runcopère sur une copie privée de lui-même en mémoire (unmemfdscellé) de sorte que/proc/self/exene se résout plus vers le binaire de l'hôte sur disque ; les runtimes corrigés mettent la technique en échec. - Un écrasement réussi affecte chaque opération de conteneur ultérieure sur l'hôte, c'est donc à la fois une évasion et un mécanisme de persistance à l'échelle de l'hôte.