
PoC funcional de CVE-2026-17106 (CopyEscape): carrera TOCTOU en docker cp que permite escritura arbitraria en el host Docker. Laboratorio Docker + monitor inotify + LD_PRELOAD. Variante macOS inofensiva y Linux destructiva.
Proof of Concept educativa de CopyEscape (CVE-2026-17106), vulnerabilidad de carrera TOCTOU en
docker cpque permite a un contenedor malicioso escribir archivos arbitrarios en el host Docker. Inspirada en el trabajo original de masasron, quien descubrio y documento la vulnerabilidad en el blog "CopyEscape: Taking Over Docker Hosts with docker cp".
CVE-2026-17106 es una vulnerabilidad de condicion de carrera (TOCTOU) en la extraccion del archivo tar que el daemon de Docker produce durante docker cp <contenedor>:<ruta> <destino>. Afecta a Docker CLI 29.6.1 y versiones anteriores; parcheada a partir de la 29.7.0. El bug reside en la CLI (extraccion del tar en el host), no en el daemon.
La cadena bautizada CopyEscape demuestra como, partiendo unicamente de un contenedor en ejecucion sin capacidades extra, un atacante puede:
/watched/file.txt/ como un archivo regular ante cualquier proceso que lo abra dentro del contenedor, mediante una biblioteca LD_PRELOAD que intercepta open, openat, openat2, fopen, stat, lstat, fstatat, statx y las variantes 64.aaa.txt de 16 MiB que fuerza al daemon a tardar lo suficiente en producir el tar para que el monitor gane la carrera.inotify los accesos del daemon y, en la ventana de carrera, sustituir un subdirectorio escape/ por un symlink absoluto al target del host (/Users/<usuario> en macOS o /usr/bin en Linux) mediante dos rename(2) atomicos con rollback.escape seguido de una entrada hija escape/<basename>; la CLI vulnerable crea el symlink en el cliente y extrae la entrada hija a traves de el, escribiendo fuera del destino elegido por el usuario.El resultado es escritura arbitraria de archivos en el host con los permisos del proceso que ejecuta docker cp. La variante Linux sobrescribe /usr/bin/runc y consigue ejecucion de codigo como root en el host al siguiente docker run / exec.
Este repositorio contiene material estrictamente educativo y defensivo.
runc del host: usala unicamente en una VM desechable con copia de seguridad verificada.La PoC se entrega con una variante macOS inocua (escribe ~/pwnd como marcador) cuya unica finalidad es servir como demostracion reproducible.
┌─────────────────────────────────────────────────────────────────────┐
│ 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 esta construida para ejecutarse en macOS con:
curl, tar, git y docker en PATH.alpine:3.21.HOME con la forma /Users/<usuario>.No se necesita root para la variante macOS. La variante Linux requiere root y una VM desechable.
El runner run-poc.sh descarga automaticamente el binario de la CLI vulnerable 29.6.1 desde el sitio oficial de Docker (download.docker.com). No modifica la instalacion de Docker Desktop del sistema: usa el daemon actual pero invoca el binario descargado para la operacion docker cp.