Problema

En entornos donde los contenedores se usan para tareas de CI/CD, depuración o transferencia de datos, es frecuente emplear docker cp para mover archivos entre el host y el contenedor. Un patrón de falla aparece cuando el proceso que ejecuta docker cp tiene permisos suficientes en el host (por ejemplo, usuario root o miembro del grupo docker). Si el contenedor controla el contenido del archivo que se está copiando, puede aprovechar una condición de carrera entre la creación del archivo temporal y la extracción del archivo tar, combinada con enlaces simbólicos maliciosos, para escribir o sobrescribir cualquier ruta del host. El resultado es una escritura arbitraria que, dependiendo del contexto, puede escalar a ejecución de código.

Este tipo de vulnerabilidad no es exclusivo de una versión concreta; cualquier implementación que:

  1. Genere un archivo tar en el host a partir del contenido del contenedor sin validar rutas.
  2. Extraiga ese tar sin sanitizar enlaces simbólicos.
  3. Permita que el proceso de extracción corra con privilegios de escritura en directorios críticos.

puede ser explotado de forma similar.

Causa

1. Creación de archivo tar sin aislamiento

docker cp delega la creación del archivo tar a la CLI del host. El proceso abre un descriptor de archivo temporal en el directorio de trabajo del usuario que ejecuta el comando. Si el contenedor controla el nombre del archivo dentro del tar, puede incluir rutas como ../../../../etc/passwd o un symlink que apunte a /usr/local/bin/malware.

Al descomprimir el tar, Docker usa la biblioteca de archivado estándar sin validar que los enlaces simbólicos apunten fuera del directorio de destino. Un symlink creado dentro del tar que apunte a /etc/cron.d permite que el archivo siguiente sea escrito allí.

3. Condición de carrera (race condition)

El proceso de creación del tar y la extracción pueden ejecutarse en hilos diferentes. Un atacante puede modificar la tabla de inodos entre ambos pasos, forzando que el descriptor temporal se re‑asigne a un archivo de destino controlado.

4. Privilegios del proceso Docker CLI

Si el usuario que ejecuta docker cp pertenece al grupo docker, el proceso corre como root dentro del daemon, otorgándole acceso de escritura a todo el filesystem del host. En entornos de CI donde los agentes usan docker sin restricciones, el riesgo se multiplica.

Solución

A. Actualizar Docker a versiones parcheadas

Los mantenedores ya han corregido la combinación de race condition y manejo de symlinks en versiones posteriores a 29.7.2 (Engine/CLI) y 4.86.0 (Desktop). Verificar la versión y, de ser necesario, aplicar el parche es el primer paso.

B. Restringir el uso de docker cp

  1. Deshabilitar en entornos críticos: Configurar políticas de IAM o sudoers para que solo usuarios de confianza puedan ejecutar docker cp.
  2. Reemplazar por volúmenes temporales: En pipelines CI, montar un volumen compartido de solo lectura y copiar archivos desde dentro del contenedor con cat > /host/path bajo control de la propia aplicación, evitando la CLI.

C. Aplicar hardening a nivel de daemon

  • User namespaces: Ejecutar el daemon con --userns-remap para que los UID/GID del contenedor se mapeen a un rango no privilegiado en el host.
  • Seccomp y AppArmor: Añadir perfiles que bloqueen syscalls de openat con flags de escritura en rutas fuera del directorio de trabajo del contenedor.
  • Read‑only root filesystem: Lanzar contenedores con --read-only y montar únicamente los directorios necesarios como volúmenes RW.

D. Validar rutas antes de la extracción (patch local)

Si no es posible actualizar inmediatamente, se puede envolver docker cp con un script que:

  1. Inspeccione el contenido del tar usando tar -tf.
  2. Rechace entradas que contengan .. o que sean symlinks.
  3. Extraiga en un directorio sandbox y luego mueva los archivos a su destino final con mv -T bajo control de ACL.

E. Monitoreo y detección

  • Auditd: Configurar reglas para registrar open y creat en directorios críticos cuando el proceso padre sea docker.
  • Falco: Crear una regla que dispare cuando docker cp intente escribir fuera de /var/lib/docker/tmp.

Cuándo aplicar esta solución

  • Entornos con CI/CD que usan docker cp: Si tus pipelines descargan artefactos desde contenedores, aplica la política B y D.
  • Servidores de producción con acceso remoto al daemon Docker: Prioriza A y C, ya que cualquier usuario con acceso al socket Docker puede lanzar la vulnerabilidad.
  • Desarrolladores locales: Si trabajas con Docker Desktop, la actualización (A) es suficiente; sin embargo, habilitar user namespaces (C) añade una capa extra de seguridad.

No es necesario aplicar todas las capas simultáneamente; el modelo de defensa en profundidad sugiere combinar al menos dos: versión parcheada + restricción de privilegios + monitoreo.

Código

# 1. Verificar versión
docker version --format '{{.Server.Version}} {{.Client.Version}}'

# 2. Habilitar user namespaces (requiere reinicio del daemon)
cat <<EOF > /etc/docker/daemon.json
{
  "userns-remap": "default"
}
EOF
systemctl restart docker

# 3. Script wrapper para docker cp
#!/usr/bin/env bash
set -euo pipefail

SRC=$1
DST=$2

TMP_TAR=$(mktemp /tmp/dockercp.XXXXXX.tar)
docker cp "$SRC" - > "$TMP_TAR"

# inspección de rutas
if tar -tf "$TMP_TAR" | grep -E '(^|/)\.\.'; then
  echo "Error: archivo contiene rutas de retroceso."
  rm -f "$TMP_TAR"
  exit 1
fi

if tar -tf "$TMP_TAR" | grep -E '^[^/]+$' | xargs -I{} tar -xf "$TMP_TAR" -C "$(dirname "$DST")" {}; then
  echo "Copiado seguro completado."
else
  echo "Fallo al extraer."
fi

rm -f "$TMP_TAR"

Verificación

  1. Prueba de control de versiones
    Ejecuta docker version. La salida debe mostrar una versión mayor o igual a 29.7.2 (Engine/CLI) o 4.86.0 (Desktop).

  2. Simulación de ataque

    • Crea un contenedor con un archivo tar que incluya ../../../../etc/cron.d/malicious.
    • Ejecuta el wrapper anterior.
    • Verifica que el script aborta y que no se crea el archivo en /etc/cron.d.
  3. Auditoría en tiempo real

    • Inicia auditctl -w /etc/cron.d -p wa -k docker_cp_test.
    • Ejecuta un docker cp legítimo.
    • Revisa ausearch -k docker_cp_test para confirmar que solo los procesos esperados generan eventos.

Notas adicionales

  • En entornos Kubernetes, el mismo patrón se reproduce cuando los pods usan kubectl cp. Aplicar las mismas restricciones a kubectl y habilitar ReadOnlyRootFilesystem en los pods reduce la superficie.
  • Los runners de GitHub Actions y GitLab CI corren con el socket Docker montado; revisa sus políticas de permisos y considera usar docker exec con --user para limitar la UID dentro del contenedor.
  • Si tu infraestructura depende de docker cp para migraciones masivas, planifica una ventana de mantenimiento para actualizar y validar los scripts de wrapper antes de la migración final.