
Funktionsfähiger PoC für CVE-2026-17106 (CopyEscape): TOCTOU-Race in docker cp, das beliebiges Schreiben auf dem Docker-Host ermöglicht. Docker-Labor + inotify-Monitor + LD_PRELOAD. Harmlose macOS-Variante und destruktive Linux-Variante.
Pädagogisches Proof of Concept zu CopyEscape (CVE-2026-17106), einer TOCTOU-Race-Condition-Schwachstelle in
docker cp, die es einem bösartigen Container ermöglicht, beliebige Dateien auf dem Docker-Host zu schreiben. Inspiriert von der ursprünglichen Arbeit von masasron, der die Schwachstelle im Blog "CopyEscape: Taking Over Docker Hosts with docker cp" entdeckte und dokumentierte.
CVE-2026-17106 ist eine TOCTOU-Race-Condition-Schwachstelle bei der Extraktion der Tar-Datei, die der Docker-Daemon während docker cp <contenedor>:<ruta> <destino> erzeugt. Betroffen sind die Docker CLI 29.6.1 und frühere Versionen; ab 29.7.0 wurde sie gepatcht. Der Fehler liegt in der CLI (Tar-Extraktion auf dem Host), nicht im Daemon.
Die als CopyEscape bezeichnete Kette zeigt, wie ein Angreifer – allein ausgehend von einem laufenden Container ohne zusätzliche Capabilities – Folgendes erreichen kann:
/watched/file.txt/ vor jedem Prozess, der es innerhalb des Containers öffnet, als reguläre Datei tarnen – mithilfe einer LD_PRELOAD-Bibliothek, die open, openat, openat2, fopen, stat, lstat, fstatat, statx und die 64-Varianten abfängt.aaa.txt bereithalten, die den Daemon zwingt, bei der Tar-Erstellung lange genug zu brauchen, damit der Monitor den Wettlauf gewinnt.inotify die Zugriffe des Daemons überwachen und im Wettlauffenster ein Unterverzeichnis escape/ durch einen absoluten Symlink auf das Host-Ziel ( unter macOS oder unter Linux) ersetzen – mittels zweier atomarer -Aufrufe mit Rollback.Das Ergebnis ist beliebiges Schreiben von Dateien auf dem Host mit den Rechten des Prozesses, der docker cp ausführt. Die Linux-Variante überschreibt /usr/bin/runc und erreicht beim nächsten docker run / exec Codeausführung als root auf dem Host.
Dieses Repository enthält ausschließlich lehrreiches und defensives Material.
runc des Hosts: Verwende sie ausschließlich in einer Wegwerf-VM mit verifizierter Sicherungskopie.Die PoC wird mit einer harmlosen macOS-Variante geliefert (schreibt ~/pwnd als Marker), deren einziger Zweck es ist, als reproduzierbare Demonstration zu dienen.
┌─────────────────────────────────────────────────────────────────────┐
│ 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) │
└─────────────────────────────────────────────────────────────────────┘
Die PoC ist für die Ausführung unter macOS ausgelegt mit:
curl, tar, git und docker im PATH.alpine:3.21.HOME in der Form /Users/<usuario>.Für die macOS-Variante wird kein Root benötigt. Die Linux-Variante erfordert Root und eine Wegwerf-VM.
Der Runner run-poc.sh lädt das Binärprogramm der anfälligen CLI 29.6.1 automatisch von der offiziellen Docker-Website (download.docker.com) herunter. Er verändert die Docker-Desktop-Installation des Systems nicht: Er verwendet den aktuellen Daemon, ruft jedoch das heruntergeladene Binärprogramm für die docker cp-Operation auf.
POC CopyEscape (CVE-2026-17106)/
├── README.md ← Este archivo
├── LICENSE ← Licencia MIT
├── .gitignore
├── Dockerfile ← Dockerfile unificado (macos | linux)
├── entrypoint.sh ← Arranque del monitor sin LD_PRELOAD
├── monitor.c ← Monitor inotify unificado (una sola fuente)
├── watched_preload.c ← LD_PRELOAD con cobertura ampliada
└── run-poc.sh ← Runner robusto con reintentos
Das Verzeichnis
cli-vulnerable/(heruntergeladene CLI 29.6.1) wird über.gitignorevom Repository ausgeschlossen. Der Runner lädt sie automatisch herunter, wenn sie nicht vorhanden ist.
cd poc-v2
./run-poc.sh
Der Runner:
copyescape-macos-pocv2:local.COPYESCAPE_HOST_HOME=$HOME./watched/file.txt innerhalb des Containers lesbar ist.docker cp mit der anfälligen CLI aus.~/pwnd../run-poc.sh 10 # 10 intentos (la carrera es probabilistica)
./run-poc.sh --cleanup # 5 intentos y borra la imagen al final
[run-poc] CLI vulnerable: 29.6.1
[run-poc] Intento 1/5 (contenedor copyescape-pocv2-NNNN-RRRR)
[run-poc] /watched/file.txt lee: top-level file
[run-poc] docker cp (CLI 29.6.1)...
[run-poc] EXITO en intento 1: se creo /Users/<tu-usuario>/pwnd
Contenido del marcador: COPYESCAPE_MACOS_DEMO
[run-poc] Logs del monitor:
[monitor] plataforma=macos target_dir=/Users/<tu-usuario> target_file=/Users/<tu-usuario>/pwnd quiet_ms=400 trigger=aaa.txt
[monitor] pivoted /watched/file.txt/escape -> /Users/<tu-usuario>
[run-poc] Resumen: 1 exito en 1 intento(s).
rm -f -- "$HOME/pwnd"
rm -rf -- poc-v2/file.txt
docker image rm copyescape-macos-pocv2:local
Sobald die Kette abgeschlossen ist, dienen die folgenden Elemente als Nachweis:
Die Idee und die Strategie stammen von masasron: der TOCTOU-Wettlauf gegen den Tar-Erzeuger, die Tarnung über LD_PRELOAD, der 16-MiB-Köder, der Pivot mit zwei rename(2)-Aufrufen und die Struktur escape/ + .swap-escape. Ohne dieses Design gäbe es diese Neufassung nicht. Das konzeptionelle Schema ist identisch.
Etwa 15-20 % sind Zeilen, die wörtlich übereinstimmen (kurze Hilfsfunktionen und Layout-Konstanten). Der Rest ist neuer Code.
docker cp nicht aus einem Container aus, dem du nicht vertraust, und unter Linux niemals als root.docker cp, oder blockiere im Daemon Tars, die Kindeinträge unter einem Symlink enthalten.Verteilt unter der MIT-Lizenz. Weitere Details siehe Datei LICENSE.
/Users/<usuario>/usr/binrename(2)escape, gefolgt von einem Kindeintrag escape/<basename>; die anfällige CLI erstellt den Symlink beim Client und extrahiert den Kindeintrag durch ihn hindurch, wobei sie außerhalb des vom Benutzer gewählten Ziels schreibt.| Ressource | Erwarteter Nachweis |
|---|
~/pwnd | Datei mit Inhalt COPYESCAPE_MACOS_DEMO\n |
poc-v2/file.txt/ | Verzeichnis (keine Datei) mit escape -> /Users/<tu-usuario> (Symlink) und aaa.txt (16 MiB) |
docker logs <contenedor> | Zeile pivoted /watched/file.txt/escape -> /Users/<tu-usuario> |
CLI 29.7.2 (docker cp mit gepatchter CLI) | Der Marker wird nicht erstellt; die PoC schlägt fehl |
| Datei | Ursprüngliche Zeilen | Neue Zeilen | Änderung |
|---|
watched_preload.c | 89 | 257 | Neu geschrieben: Wrapper openat2, fstatat, statx, stat64, lstat64, open64, fopen64; thread-sicherer Konstruktor; Guard gegen dlsym == NULL; O_TMPFILE |
monitor.c (macos + linux) | 259 + 233 = 492 (doppelt) | 344 (vereinheitlicht) | Neu geschrieben und mit -DCOPYESCAPE_TARGET_* vereinheitlicht; Rollback in try_pivot; exit_quiet_ms und Trigger konfigurierbar; robuste Erkennung |
entrypoint.sh | 3 | 5 | env -u LD_PRELOAD anstelle von unset |
Dockerfile | 22 | 43 | Vereinheitlicht; Digest-Pinning; --platform; ohne -Werror; -static; LABEL |
demo-macos.sh → run-poc.sh | 88 | 166 | Neu geschrieben: Wettlauf-Wiederholungen; Logs bei Fehlschlag; --cleanup; Validierung an das Binärprogramm delegiert |