
PoC fonctionnel de CVE-2026-17106 (CopyEscape) : course TOCTOU dans docker cp qui permet une écriture arbitraire sur l'hôte Docker. Laboratoire Docker + moniteur inotify + LD_PRELOAD. Variante macOS inoffensive et Linux destructrice.
Preuve de concept pédagogique de CopyEscape (CVE-2026-17106), une vulnérabilité de course TOCTOU dans
docker cpqui permet à un conteneur malveillant d'écrire des fichiers arbitraires sur l'hôte Docker. Inspirée des travaux originaux de masasron, qui a découvert et documenté la vulnérabilité dans le billet "CopyEscape: Taking Over Docker Hosts with docker cp".
CVE-2026-17106 est une vulnérabilité de condition de course (TOCTOU) dans l'extraction de l'archive tar que le démon Docker produit lors de docker cp <contenedor>:<ruta> <destino>. Elle affecte Docker CLI 29.6.1 et versions antérieures ; corrigée à partir de la 29.7.0. Le bug réside dans la CLI (extraction du tar sur l'hôte), pas dans le démon.
La chaîne baptisée CopyEscape montre comment, à partir d'uniquement un conteneur en cours d'exécution sans capacités supplémentaires, un attaquant peut :
/watched/file.txt/ en fichier régulier pour tout processus qui l'ouvre à l'intérieur du conteneur, via une bibliothèque LD_PRELOAD qui intercepte open, openat, openat2, fopen, stat, lstat, fstatat, statx et les variantes 64.aaa.txt de 16 Mio qui force le démon à mettre suffisamment de temps à produire le tar pour que le moniteur gagne la course.inotify les accès du démon et, dans la fenêtre de course, remplacer un sous-répertoire escape/ par un symlink absolu vers la cible de l'hôte (/Users/<usuario> sur macOS ou /usr/bin sur Linux) via deux rename(2) atomiques avec rollback.escape suivi d'une entrée enfant escape/<basename> ; la CLI vulnérable crée le symlink sur le client et extrait l'entrée enfant à travers celui-ci, écrivant en dehors de la destination choisie par l'utilisateur.Le résultat est une écriture arbitraire de fichiers sur l'hôte avec les permissions du processus qui exécute docker cp. La variante Linux écrase /usr/bin/runc et obtient une exécution de code en tant que root sur l'hôte au prochain docker run / exec.
Ce dépôt contient du matériel strictement éducatif et défensif.
runc de l'hôte : utilisez-la uniquement dans une VM jetable avec une sauvegarde vérifiée.La PoC est fournie avec une variante macOS inoffensive (elle écrit ~/pwnd comme marqueur) dont l'unique but est de servir de démonstration reproductible.
┌─────────────────────────────────────────────────────────────────────┐
│ 1. LD_PRELOAD camufla /watched/file.txt como archivo regular │
│ open/openat/openat2/fopen/stat/lstat/fstatat/statx → decoy │
│ docker exec ... cat /watched/file.txt → "top-level file" │
└──────────────────────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 2. Layout real dentro del contenedor │
│ /watched/file.txt/ │
│ ├── aaa.txt (16 MiB de 'B', cebo lento) │
│ ├── escape/ (directorio real inicial) │
│ ├── escape/<basename> (payload: runc o pwnd) │
│ └── .swap-escape (symlink → /Users/<usr> o /usr/bin) │
└──────────────────────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 3. docker cp pide tar al daemon │
│ daemon recorre /watched/file.txt/ y abre aaa.txt │
│ inotify detecta IN_OPEN sobre aaa.txt │
└──────────────────────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 4. Monitor pivot: dos rename(2) atomicos con rollback │
│ escape/ → .old-escape/ (backup) │
│ .swap-escape → escape/ (symlink al target del host) │
└──────────────────────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 5. Tar resultante contiene: │
│ escape → /Users/<usr> (symlink absoluto) │
│ escape/<basename> (entrada hija que atraviesa el symlink) │
└──────────────────────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 6. CLI vulnerable extrae symlink + hija │
│ crea escape → /Users/<usr> en el destino local │
│ extrae escape/<basename> A TRAVES del symlink │
│ escribe /Users/<usr>/<basename> en el host │
└──────────────────────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ macOS: ~/pwnd con "COPYESCAPE_MACOS_DEMO" (demo inocua) │
│ Linux: /usr/bin/runc sobrescrito (RCE como root) │
└─────────────────────────────────────────────────────────────────────┘
La PoC est conçue pour s'exécuter sur macOS avec :
curl, tar, git et docker dans le PATH.alpine:3.21.HOME au format /Users/<usuario>.Aucun root n'est nécessaire pour la variante macOS. La variante Linux nécessite root et une VM jetable.
Le runner run-poc.sh télécharge automatiquement le binaire de la CLI vulnérable 29.6.1 depuis le site officiel de Docker (download.docker.com). Il ne modifie pas l'installation de Docker Desktop du système : il utilise le démon actuel mais invoque le binaire téléchargé pour l'opération docker cp.