Sploitus

Exploit for CVE-2026-17106

githubexploit Β· 2026-08-12

Exploit Code

README246 lines
## https://sploitus.com/exploit?id=25FB4F14-D5EF-52FA-A282-EDDD3ACBC28B
# CopyEscape β€” PoC de CVE-2026-17106

> Proof of Concept educativa de **CopyEscape** (CVE-2026-17106), vulnerabilidad de carrera TOCTOU en `docker cp` que 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"*.

---

## Tabla de contenidos

1. [Resumen ejecutivo](#resumen-ejecutivo)
2. [Aviso legal](#aviso-legal)
3. [Arquitectura de la cadena](#arquitectura-de-la-cadena)
4. [Requisitos](#requisitos)
5. [Estructura del repositorio](#estructura-del-repositorio)
6. [Guia rapida de uso](#guia-rapida-de-uso)
7. [Verificacion de la explotacion](#verificacion-de-la-explotacion)
8. [Comparativa con el original](#comparativa-con-el-original)
9. [Mitigacion](#mitigacion)
10. [Creditos](#creditos)
11. [Licencia](#licencia)

---

## Resumen ejecutivo

**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 : `. 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:

1. Camuflar un directorio real `/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`.
2. Mantener un archivo cebo `aaa.txt` de 16 MiB que fuerza al daemon a tardar lo suficiente en producir el tar para que el monitor gane la carrera.
3. Vigilar con `inotify` los accesos del daemon y, en la ventana de carrera, sustituir un subdirectorio `escape/` por un symlink absoluto al target del host (`/Users/` en macOS o `/usr/bin` en Linux) mediante dos `rename(2)` atomicos con rollback.
4. El tar resultante contiene el symlink `escape` seguido de una entrada hija `escape/`; 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`.

---

## Aviso legal

Este repositorio contiene material estrictamente **educativo y defensivo**.

- **Proposito:** demostrar la cadena de explotacion con fines de formacion, auditoria propia y validacion de *patches*.
- **Uso autorizado:** ejecutalo **unicamente** contra sistemas que te pertenezcan o sobre los que dispongas de autorizacion escrita explicita.
- **Prohibido:** cualquier uso contra sistemas de terceros sin consentimiento. El autor del repositorio no se responsabiliza del uso indebido del material aqui incluido.
- **Responsabilidad:** el operador es el unico responsable del cumplimiento de la legislacion aplicable (LOPD/RGPD en Espana, CFAA en EE. UU., etc.).
- La variante Linux **destruye** el `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.

---

## Arquitectura de la cadena

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  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/  (payload: runc o pwnd)                    β”‚
β”‚     └── .swap-escape       (symlink β†’ /Users/ 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/   (symlink absoluto)                       β”‚
β”‚     escape/        (entrada hija que atraviesa el symlink) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                           β”‚
                                           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  6. CLI vulnerable extrae symlink + hija                            β”‚
β”‚     crea escape β†’ /Users/ en el destino local                   β”‚
β”‚     extrae escape/ A TRAVES del symlink                    β”‚
β”‚     escribe /Users// en el host                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                           β”‚
                                           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  macOS: ~/pwnd con "COPYESCAPE_MACOS_DEMO"  (demo inocua)           β”‚
β”‚  Linux: /usr/bin/runc sobrescrito          (RCE como root)          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

---

## Requisitos

La PoC esta construida para ejecutarse en **macOS** con:

- **Docker Desktop** 4.81+ en marcha (el daemon puede ser cualquier version; el bug esta en la CLI).
- `curl`, `tar`, `git` y `docker` en `PATH`.
- Conexion a internet para descargar la CLI vulnerable 29.6.1 (unos 18 MB) y la imagen base `alpine:3.21`.
- `HOME` con la forma `/Users/`.

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`.

---

## Estructura del repositorio

```
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
```

> El directorio `cli-vulnerable/` (CLI 29.6.1 descargada) se excluye del
> repositorio via `.gitignore`. El runner la descarga automaticamente si
> no esta presente.

---

## Guia rapida de uso

### 1. Ejecutar la PoC (variante macOS, inocua)

```console
cd poc-v2
./run-poc.sh
```

El runner:

1. Descarga la CLI vulnerable 29.6.1 si no esta presente.
2. Construye la imagen Docker `copyescape-macos-pocv2:local`.
3. Arranca el contenedor preparado con `COPYESCAPE_HOST_HOME=$HOME`.
4. Espera a que `/watched/file.txt` sea legible dentro del contenedor.
5. Ejecuta `docker cp` con la CLI vulnerable.
6. Verifica la creacion del marcador `~/pwnd`.

### 2. Opciones del runner

```console
./run-poc.sh 10         # 10 intentos (la carrera es probabilistica)
./run-poc.sh --cleanup  # 5 intentos y borra la imagen al final
```

### 3. Salida esperada

```text
[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//pwnd
  Contenido del marcador: COPYESCAPE_MACOS_DEMO
[run-poc]   Logs del monitor:
    [monitor] plataforma=macos target_dir=/Users/ target_file=/Users//pwnd quiet_ms=400 trigger=aaa.txt
    [monitor] pivoted /watched/file.txt/escape -> /Users/
[run-poc] Resumen: 1 exito en 1 intento(s).
```

### 4. Limpieza

```console
rm -f -- "$HOME/pwnd"
rm -rf -- poc-v2/file.txt
docker image rm copyescape-macos-pocv2:local
```

---

## Verificacion de la explotacion

Una vez completada la cadena, los siguientes elementos sirven de evidencia:

| Recurso | Evidencia esperada |
| --- | --- |
| `~/pwnd` | Archivo con contenido `COPYESCAPE_MACOS_DEMO\n` |
| `poc-v2/file.txt/` | Directorio (no archivo) con `escape -> /Users/` (symlink) y `aaa.txt` (16 MiB) |
| `docker logs ` | Linea `pivoted /watched/file.txt/escape -> /Users/` |
| CLI 29.7.2 (`docker cp` con CLI parcheada) | El marcador **no** se crea; la PoC falla |

---

## Comparativa con el original

### Que queda del original

La **idea y la estrategia** son de masasron: la carrera TOCTOU contra el productor de tar, el camuflaje via `LD_PRELOAD`, el cebo de 16 MiB, el pivot con dos `rename(2)`, y la estructura `escape/` + `.swap-escape`. Sin ese diseno, esta reescritura no existiria. El esquema conceptual es identico.

### Que se ha reescrito

| Fichero | Lineas original | Lineas nuevo | Cambio |
| --- | --- | --- | --- |
| `watched_preload.c` | 89 | 257 | Reescrito: wrappers `openat2`, `fstatat`, `statx`, `stat64`, `lstat64`, `open64`, `fopen64`; constructor thread-safe; guard anti `dlsym == NULL`; `O_TMPFILE` |
| `monitor.c` (macos + linux) | 259 + 233 = 492 (duplicados) | 344 (unificado) | Reescrito y unificado con `-DCOPYESCAPE_TARGET_*`; rollback en `try_pivot`; `exit_quiet_ms` y trigger configurables; deteccion robusta |
| `entrypoint.sh` | 3 | 5 | `env -u LD_PRELOAD` en lugar de `unset` |
| `Dockerfile` | 22 | 43 | Unificado; digest pin; `--platform`; sin `-Werror`; `-static`; `LABEL` |
| `demo-macos.sh` β†’ `run-poc.sh` | 88 | 166 | Reescrito: reintentos de carrera; logs en fallo; `--cleanup`; validacion delegada al binario |

Aproximadamente un **15-20 %** son lineas que coinciden literalmente (utilidades cortas y constantes del layout). El resto es codigo nuevo.

---

## Mitigacion

- **Actualizar la CLI de Docker a 29.7.0 o superior.** La PoC deja de funcionar (verificado con 29.7.2).
- No ejecutar `docker cp` desde un contenedor en el que no se confia, y nunca como root en Linux.
- Deteccion simple: vigilar la creacion de symlinks absolutos dentro del destino de `docker cp` durante la extraccion, o bloquear en el daemon los tars que contienen entradas hijas bajo un symlink.
- La raiz del bug es la misma clase de fallo que TarSlip / ZipSlip: la CLI no valida que las entradas siguientes no escapen via un symlink ya creado.

---

## Creditos

- Investigacion original y divulgacion: **masasron** β€” 
- PoC y empaquetado de este repositorio: trabajo propio con fines educativos, reescrito desde cero sobre la idea original.

---

## Licencia

Distribuido bajo licencia MIT. Consulta el archivo `LICENSE` para mas detalles.