Seguridad VS Comodidad
Al trabajar con docker seguramente hayas creado un usuario y lo hayas metido en el grupo de docker para no tener que escribir sudo delante del comando docker, a no ser que trabajes como root directamente que espero que no sea así…
Si tu también eres perezoso y has creado ese usuario para evitar poner un sudo, aunque sea con pocos permisos, ten cuidado ya que de forma muy sencilla alguien podría usar esto en tu contra para escalar privilegios hasta llegar a ser root.
Pero para entender esto, debemos saber que es docker y cómo funciona.
¿Qué es docker?
Docker es por definición: una plataforma de software que permite empaquetar, desplegar y ejecutar aplicaciones dentro de contenedores. Es decir, poder correr servicios y aplicaciones sin tocar el host, todo ocurre de forma aislada en los contenedores, pudiendo replicar estos en diferentes sistemas operativos y haciendo que funcionen de la misma manera ya que todo lo necesario para su funcionamiento vive dentro del contenedor.
Esto trae consigo muchas ventajas, así como:
Portabilidad total: Se ejecutan de la misma manera en tu host que en cualquier otro.
Mayor eficiencia: Un uso muy bajo de memoria y sin necesidad de un sistema operativo invitado.
Aislamiento de aplicaciones: Cada contenedor queda aislado del sistema operativo y de los demás contenedores.
Despliegue rápido: Con un docker run puedes tener casi lo que quieras al instante ejecutándose.
Escalando privilegios con docker
Pero como decía, por como está montado docker, puede que sea peligroso, no por una vulnerabilidad conocida de por sí, sino por como sus propios creadores mencionan en https://docs.docker.com/engine/security/#docker-daemon-attack-surface, es un tema de arquitectura.
Para trabajar con Docker, el CLI envía instrucciones al demonio dockerd a través de un socket Unix ubicado en /var/run/docker.sock. Este socket pertenece al grupo docker con permisos de lectura y escritura, por lo que cualquier usuario de ese grupo puede comunicarse directamente con el daemon, que sí corre como root. Es lo que se conoce como root delegado: sin ser root, puedes pedirle al daemon que ejecute operaciones privilegiadas en tu nombre. Y pensarás, ¿cómo alguien puede atacar mi host si solo puede crear contenedores?
Hay una cosa en docker llamada volúmenes, los cuales nos permiten almacenar datos de forma persistente, enlazando un directorio de nuestro host con un directorio del contenedor. Sabiendo esto, qué pasaría si creamos un volumen en un contenedor de docker, adjuntando a nuestro volumen la raíz ( / ) del host? Efectivamente, dentro del contenedor somos root, y el volumen apunta directamente al filesystem real del host — no es una copia, son los mismos ficheros. Cualquier modificación dentro del contenedor se escribe directamente en el host.
Hay varias formas desde este punto para escalar privilegios, para mi el más sencillo es, sin siquiera tener que entrar en el propio contenedor, editar el archivos sudoers y adjuntar el nombre del usuario que queramos a este archivo. Vamos a verlo de forma práctica:
Supongamos que hemos conseguido tener acceso al host y al usuario con bajos privilegios, pero en el grupo de docker. Para simular este escenario he creado una VM con una ISO Debian 12 y he ejecutado estos comandos:
Con el usuario admin (por defecto) actualizo el sistema, instalo docker, creo el usuario lowuser (simulando el usuario sin privilegios que maneja docker en el servidor) y le añado al grupo de docker:
sudo apt update && sudo apt upgrade -y
sudo apt install docker.io
sudo adduser lowuser
sudo usermod -aG docker lowuser
Ahora cambio al usuario lowuser (vamos a suponer que he ganado acceso a este usuario como un hacker) y voy a levantar un contenedor el cual “secuestrare” todo el sistema de archivos del servidor ( / ) y lo montare en un volumen dentro del contenedor:
su - lowuser
NOTA: Cambiar <usuario> por el nombre de usuario real
docker run --rm -v /:/mnt/host ubuntu:22.04 bash -c 'echo "<usuario> ALL=(ALL) NOPASSWD:ALL" >> /mnt/host/etc/sudoers'
Ejemplo:
docker run --rm -v /:/mnt/host ubuntu:22.04 bash -c 'echo "lowuser ALL=(ALL) NOPASSWD:ALL" >> /mnt/host/etc/sudoers'
Listo, probamos a hacer sudo su para cambiar a root y ya tendremos control total sobre el host.
¿Cómo protegernos con Rootless Mode?
Mucha gente trabaja con docker, y aunque ya lo explican en su propia documentación (repito, esto no es una vulnerabilidad, ocurre por como funciona docker), no mucha gente aplica seguridad, por ejemplo empleando el rootless mode, el cual podéis ver en la documentación de docker, aunque en este blog os voy a explicar a cómo hacerlo de forma detallada y siguiendo con el ejemplo del “exploit” de arriba.
link: https://docs.docker.com/engine/security/rootless/
El modo rootless, valga la redundancia, permite levantar contenedores docker sin permisos root. Como prerrequisito debemos de instalar:
uidmap: Para mapear los uids entre el usuario root dentro del contenedor y el usuario sin privilegios que ha levantado ese contenedor.
Otro prerrequisito que no es necesario para rootless pero si para este tutorial es que elimines al usuario anteriormente creado de sudoers, sino que sentido tendría proteger a los usuarios no privilegiados si este lo hemos convertido antes en uno?
Como pone en la documentación, en caso de que el demonio de docker siga corriendo debemos pararlo y eliminar el socket, para que así no haya conflictos y podamos trabajar de forma segura con el usuario sin privilegios.
Con el usuario admin:
sudo systemctl disable --now docker.service docker.socket
sudo rm /var/run/docker.sock
Ahora sí, ya podemos instalar el rootless para el usuario lowuser. En el tutorial dice que ejecutemos el comando dockerd-rootless-setuptool.sh install, pero como en este tutorial se ha instalado docker.io no viene este paquete, asi que hay que instalarlo haciendo:
Con el usuario lowuser:
curl -fsSL https://get.docker.com/rootless | sh
En información vemos que pone que tenemos que añadir de forma manual ciertas líneas en ~ /.bashrc. Podríamos entrar con nano y añadirlo pero podeis copiar y pegar estos comandos para hacerlo más rápido:
echo 'export PATH=$HOME/bin:$PATH' >> ~/.bashrc
echo 'export XDG_RUNTIME_DIR=/run/user/$(id -u)' >> ~/.bashrc
echo 'export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock' >> ~/.bashrc
No va a surtir efecto de primeras ya que ese archivo se ejecuta cada vez que iniciamos sesion, asi que podemos ejecutar este comando para obligar a leer de nuevo y aplicar los cambios del archivo:
source ~/.bashrc
Por último, encendemos docker solo para el usuario lowuser con el siguiente comando:
systemctl --user enable --now docker
Es la hora de la verdad, si intentamos volver a levantar un contenedor creando un volumen adjunto a la raíz del host con el usuario lowuser para escalar privilegios, al no tener permisos suficientes (ya no se ejecuta como root sino con el UID del propio usuario) nos dará un error, mitigando así este riesgo de seguridad derivado de la arquitectura por defecto de Docker.
No hay comentarios:
Publicar un comentario