Docker socket to host: cuando el contenedor toca el sistema
Montar /var/run/docker.sock dentro de un contenedor suele equivaler a entregar el host. Por qué se rompe el aislamiento y cómo no hacerlo.
Antes de empezar necesitás
- Saber qué es un contenedor y una imagen
- Haber corrido docker run alguna vez
Al terminar vas a poder
- Entender qué permite el Docker socket y por qué es tan poderoso
- Explicar por qué montarlo rompe la frontera contenedor/host
- Reconocer dónde aparece este patrón en CI y herramientas internas
- Tener un checklist de qué nunca montar a ciegas
Este lab es defensivo. No hay exploit acá: hay un modelo mental para entender por qué una práctica común —montar el Docker socket dentro de un contenedor— es una frontera de seguridad rota, y qué hacer en lugar de eso.
Por qué se rompe el aislamiento
La promesa de un contenedor es aislamiento: lo que pasa adentro, queda adentro. Ese límite se sostiene en namespaces, cgroups y capabilities del kernel. Pero el socket de Docker saltea ese modelo, porque no es un recurso del contenedor: es un canal directo al demonio del host.
Si un contenedor tiene el socket montado y alguien lo compromete, no necesita “escapar” del contenedor rompiendo el kernel. Solo tiene que pedirle a Docker, educadamente, que le cree otro contenedor con el sistema de archivos del host montado adentro. Docker, que es root, obedece.
┌─────────────────────────────┐
│ Contenedor "inofensivo" │
│ (un panel, un CI runner, │
│ un agente…) │
│ │
│ /var/run/docker.sock ────┼──► demonio Docker (root en el host)
└─────────────────────────────┘ │
▼
puede crear un nuevo contenedor
que monta / del host → control del host
Dónde aparece este patrón
No es exótico. El socket montado aparece todo el tiempo, casi siempre por comodidad:
- Runners de CI/CD que necesitan “buildear imágenes” y montan el socket para hablar con Docker.
- Dashboards de administración de contenedores (paneles que listan y manejan contenedores).
- Agentes y scripts internos que orquestan otros contenedores.
- Tutoriales de “Docker-in-Docker” que toman el atajo del socket.
# Buscar montajes del socket en todo el repo
grep -R --line-number "docker.sock" . Si aparece un - /var/run/docker.sock:/var/run/docker.sock en un docker-compose.yml, esa línea merece una conversación, no un merge automático.
El modelo defensivo
La pregunta no es “¿cómo lo exploto?”, es “¿este contenedor necesita de verdad comandar a Docker?”. Casi siempre la respuesta es no, y hay opciones más seguras:
- No montarlo. El 90% de las veces el contenedor no necesita el socket; necesitaba otra cosa.
- Un proxy con allowlist (tipo
docker-socket-proxy) que solo exponga las pocas operaciones realmente necesarias y bloqueecreate,exec, mounts, etc. - Builds sin demonio root, con herramientas tipo BuildKit/buildah en modo rootless, que no requieren el socket privilegiado.
- Rootless Docker, para que el demonio no corra como root y el blast radius sea menor.
Checklist: qué no montar a ciegas
[ ] /var/run/docker.sock → control del demonio = control del host
[ ] / (raíz del host) → acceso total al filesystem del host
[ ] /etc, /root, /home → configs, claves y datos sensibles
[ ] /proc, /sys (sin razón) → información y control del kernel
[ ] dispositivos (--device) → acceso directo al hardware
[ ] --privileged → desactiva casi todas las protecciones
Por cada ítem, la misma pregunta: ¿es estrictamente necesario, o es comodidad? En seguridad, “es más cómodo” casi nunca gana la discusión.
Un contenedor que puede tocar el sistema deja de ser un contenedor: es un proceso con disfraz. Entender dónde está esa frontera es lo que te permite no romperla sin darte cuenta.
Lo que practicás en este lab
Llevátelo a tu repo si querés, pero no es obligatorio: es tu aprendizaje.
- Diagrama del límite contenedor → socket → host
- Writeup defensivo: por qué 'solo administra contenedores' es un error mental
- Checklist de mounts peligrosos para revisar en compose y manifests
Reto
Revisá un docker-compose.yml (real tuyo o de ejemplo) y buscá montajes de docker.sock o de rutas del host. Por cada uno, escribí una línea: ¿es necesario? ¿qué alternativa más segura hay?
Resolvelo y escribí dos líneas explicando qué pasó. Con eso lo fijás.