🐧Linux - Docker
Docker es una herramienta de código abierto muy popular que ofrece un entorno de ejecución portátil y consistente para aplicaciones de software. Utiliza contenedores como entornos aislados en el espacio de usuario que se ejecutan a nivel del sistema operativo y comparten el sistema de archivos y los recursos del sistema. Una ventaja es que la contenedorización consume significativamente menos recursos que un servidor tradicional o una máquina virtual.
La característica principal de Docker es que las aplicaciones se encapsulan en los denominados contenedores Docker. Por lo tanto, se pueden utilizar para cualquier sistema operativo. Un contenedor Docker representa un paquete de software ejecutable autónomo y liviano que contiene todo lo necesario para ejecutar el código de una aplicación en tiempo de ejecución.
Arquitectura Docker
En el núcleo de la arquitectura de Docker se encuentra un modelo cliente-servidor, donde tenemos dos componentes principales:
El daemon Docker
El cliente Docker
El cliente Docker actúa como nuestra interfaz para emitir comandos e interactuar con el ecosistema Docker, mientras que el demonio Docker es responsable de ejecutar esos comandos y administrar los contenedores.
Daemon Docker
El Docker Daemon
, también conocido como servidor Docker, es una parte fundamental de la plataforma Docker que desempeña un papel fundamental en la gestión y la orquestación de contenedores. Piense en el Docker Daemon como el motor detrás de Docker. Tiene varias responsabilidades esenciales como:
Ejecutar contenedores Docker
Interactuar con contenedores Docker
Aadministrar contenedores Docker en el sistema host.
1. Gestión de contenedores Docker
En primer lugar, se encarga de la funcionalidad básica de contenedorización. Coordina la creación, ejecución y supervisión de los contenedores Docker, manteniendo su aislamiento del host y de otros contenedores. Este aislamiento garantiza que los contenedores funcionen de forma independiente, con sus propios sistemas de archivos, procesos e interfaces de red. Además, se encarga de la gestión de imágenes de Docker. Extrae imágenes de registros, como Docker Hub o repositorios privados, y las almacena localmente. Estas imágenes sirven como bloques de construcción para la creación de contenedores.
Además, Docker Daemon ofrece capacidades de monitoreo y registro, por ejemplo:
Captura registros de contenedores
Proporciona información sobre las actividades del contenedor, los errores y la información de depuración.
El Daemon también monitorea la utilización de recursos, como CPU, memoria y uso de red, lo que nos permite optimizar el rendimiento del contenedor y solucionar problemas.
2. Red y almacenamiento
Facilita la creación de redes de contenedores mediante la creación de redes virtuales y la gestión de interfaces de red. Permite que los contenedores se comuniquen entre sí y con el mundo exterior a través de puertos de red, direcciones IP y resolución de DNS. Docker Daemon también desempeña un papel fundamental en la gestión del almacenamiento, ya que maneja volúmenes Docker, que se utilizan para conservar datos más allá de la vida útil de los contenedores y administra la creación, la conexión y la limpieza de volúmenes, lo que permite que los contenedores compartan o almacenen datos de forma independiente.
Clientes Docker
Cuando interactuamos con Docker, emitimos comandos a través de Docker Client
, que se comunica con el Docker Daemon (a través de un RESTful API
o un Unix socket
) y sirve como nuestro principal medio de interacción con Docker. También tenemos la capacidad de crear, iniciar, detener, administrar, eliminar contenedores, buscar y descargar imágenes de Docker. Con estas opciones, podemos extraer imágenes existentes para usarlas como base para nuestros contenedores o crear nuestras imágenes personalizadas utilizando Dockerfiles. Tenemos la flexibilidad de enviar nuestras imágenes a repositorios remotos, lo que facilita la colaboración y el uso compartido dentro de nuestros equipos o con la comunidad en general.
En comparación, el Daemon, por otro lado, lleva a cabo las acciones solicitadas, garantizando que los contenedores se creen, inicien, detengan y eliminen según sea necesario.
Otro cliente para Docker es Docker Compose
. Es una herramienta que simplifica la orquestación de múltiples contenedores Docker como una única aplicación. Nos permite definir la arquitectura multicontenedor de nuestra aplicación mediante un archivo declarativo YAML
(.yaml
/ .yml
). Con él, podemos especificar los servicios que componen nuestra aplicación, sus dependencias y sus configuraciones. Definimos imágenes de contenedores, variables de entorno, redes, enlaces de volúmenes y otras configuraciones. Docker Compose luego se asegura de que todos los contenedores definidos se inicien e interconecten, creando una pila de aplicaciones cohesiva y escalable.
Docker Desktop
Docker Desktop
está disponible para sistemas operativos MacOS, Windows y Linux y nos proporciona una interfaz gráfica de usuario fácil de usar que simplifica la gestión de los contenedores y sus componentes. Esto nos permite monitorear el estado de nuestros contenedores, inspeccionar registros y administrar los recursos asignados a Docker. Proporciona una forma intuitiva y visual de interactuar con el ecosistema Docker, haciéndolo accesible para desarrolladores de todos los niveles de experiencia y, además, es compatible con Kubernetes.
Imágenes y contenedores de Docker
Piense en un archivo Docker image
como un plano o una plantilla para crear contenedores. Encapsula todo lo necesario para ejecutar una aplicación, incluido el código de la aplicación, las dependencias, las bibliotecas y las configuraciones. Una imagen es un paquete autónomo de solo lectura que garantiza la coherencia y la reproducibilidad en diferentes entornos. Podemos crear imágenes utilizando un archivo de texto llamado Dockerfile
, que define los pasos y las instrucciones para crear la imagen.
Un Docker container
es una instancia de una imagen de Docker. Es un entorno ligero, aislado y ejecutable que ejecuta aplicaciones. Cuando lanzamos un contenedor, este se crea a partir de una imagen específica y el contenedor hereda todas las propiedades y configuraciones definidas en esa imagen. Cada contenedor opera de forma independiente, con su propio sistema de archivos, procesos e interfaces de red. Este aislamiento garantiza que las aplicaciones dentro de los contenedores permanezcan separadas del sistema host subyacente y de otros contenedores, lo que evita conflictos e interferencias.
Mientras que las imágenes
son inmutables y de solo lectura
, los contenedores
son mutables y pueden ser modificados
durante el tiempo de ejecución. Podemos interactuar con los contenedores, ejecutar comandos dentro de ellos, monitorear sus registros e incluso realizar cambios en su sistema de archivos o entorno. Sin embargo, las modificaciones realizadas en el sistema de archivos de un contenedor no se conservan a menos que se guarden explícitamente como una nueva imagen o se almacenen en un volumen persistente.
Escalada de privilegios de Docker
Lo que puede pasar es que accedamos a un entorno donde encontraremos usuarios que puedan gestionar contenedores docker. Con esto, podríamos buscar formas de cómo utilizar esos contenedores docker para obtener mayores privilegios en el sistema de destino. Podemos utilizar varias formas y técnicas para escalar nuestros privilegios o escapar del contenedor docker.
Directorios compartidos de Docker
Al utilizar Docker, los directorios compartidos (montajes de volumen) pueden salvar la brecha entre el sistema host y el sistema de archivos del contenedor. Con los directorios compartidos, se puede acceder a directorios o archivos específicos del sistema host dentro del contenedor. Esto es increíblemente útil para conservar datos, compartir código y facilitar la colaboración entre los entornos de desarrollo y los contenedores Docker. Sin embargo, siempre depende de la configuración del entorno y de los objetivos que los administradores quieran alcanzar. Para crear un directorio compartido, se especifica una ruta en el sistema host y una ruta correspondiente dentro del contenedor, lo que crea un vínculo directo entre las dos ubicaciones.
Los directorios compartidos ofrecen varias ventajas, entre ellas, la capacidad de conservar datos más allá de la vida útil de un contenedor, simplificar el uso compartido y el desarrollo de código y permitir la colaboración dentro de los equipos. Es importante tener en cuenta que los directorios compartidos se pueden montar como de solo lectura o de lectura y escritura, según los requisitos específicos del administrador. Cuando se montan como de solo lectura, las modificaciones realizadas dentro del contenedor no afectarán al sistema host, lo que resulta útil cuando se prefiere el acceso de solo lectura para evitar modificaciones accidentales.
Cuando obtenemos acceso al contenedor Docker y lo enumeramos localmente, podríamos encontrar directorios adicionales (no estándar) en el sistema de archivos del contenedor Docker.
A partir de aquí, podríamos copiar el contenido de la clave SSH privada al archivo cry0l1t3.priv
y usarlo para iniciar sesión como usuario cry0l1t3
en el sistema host.
Docker Sockets
Un socket Docker
o socket Docker daemon es un archivo especial que nos permite a nosotros y a los procesos comunicarnos con el demonio Docker. Esta comunicación se produce a través de un socket Unix o de un socket de red, dependiendo de la configuración de nuestra instalación Docker. Actúa como un puente, facilitando la comunicación entre el cliente Docker y el demonio Docker. Cuando emitimos un comando a través de la CLI de Docker, el cliente Docker envía el comando al socket Docker y el demonio Docker, a su vez, procesa el comando y lleva a cabo las acciones solicitadas.
Sin embargo, los sockets de Docker requieren los permisos adecuados para garantizar una comunicación segura y evitar el acceso no autorizado. El acceso al socket de Docker suele estar restringido a usuarios o grupos de usuarios específicos, lo que garantiza que solo las personas de confianza puedan emitir comandos e interactuar con el demonio de Docker. Al exponer el socket de Docker a través de una interfaz de red, podemos administrar de forma remota los hosts de Docker, emitir comandos y controlar los contenedores y otros recursos. Este acceso remoto a la API amplía las posibilidades de las configuraciones distribuidas de Docker y los escenarios de gestión remota. Sin embargo, según la configuración, existen muchas formas de almacenar los procesos o tareas automatizados. Esos archivos pueden contener información muy útil para nosotros que podemos usar para escapar del contenedor de Docker.
A partir de aquí, podemos utilizar el docker
para interactuar con el socket y enumerar qué contenedores Docker ya están en ejecución. Si no está instalado, podemos descargarlo aquí y subirlo al contenedor Docker.
Podemos crear nuestro propio contenedor Docker que asigne el directorio raíz del host (/
) al directorio /hostsystem
del contenedor. Con esto, obtendremos acceso completo al sistema host. Por lo tanto, debemos asignar estos directorios en consecuencia y usar la imagen main_app
.
Ahora, podemos iniciar sesión en el nuevo contenedor Docker privilegiado con el ID 7ae3bcc818af
y navegar hasta /hostsystem
.
Desde allí, podemos intentar nuevamente obtener la clave SSH privada e iniciar sesión como root o como cualquier otro usuario en el sistema con una clave SSH privada en su carpeta.
Grupo Docker
Para obtener privilegios de root a través de Docker, el usuario con el que iniciamos sesión debe estar en el grupo docker
. Esto le permite usar y controlar el demonio de Docker.
Como alternativa, Docker puede tener configurado el SUID o estamos en el archivo Sudoers, lo que nos permite ejecutar docker
como root. Las tres opciones nos permiten trabajar con Docker para aumentar nuestros privilegios.
La mayoría de los hosts tienen una conexión directa a Internet porque es necesario descargar las imágenes y los contenedores base. Sin embargo, muchos hosts pueden estar desconectados de Internet por la noche y fuera del horario laboral por razones de seguridad. Sin embargo, si estos hosts se encuentran en una red por la que, por ejemplo, tiene que pasar un servidor web, aún se puede acceder a ellos.
Para ver qué imágenes existen y a cuáles podemos acceder, podemos utilizar el siguiente comando:
Socket Docker escribible
Un caso que también puede ocurrir es cuando el socket de Docker es escribible. Por lo general, este socket se encuentra en /var/run/docker.sock
. Sin embargo, la ubicación puede ser comprensiblemente diferente. Porque básicamente, esto solo puede ser escrito por el grupo root o docker. Si actuamos como un usuario, no en uno de estos dos grupos, y el socket de Docker aún tiene los privilegios para ser escribible, entonces aún podemos usar este caso para escalar nuestros privilegios.
Caso práctico
Aumenta los privilegios en el objetivo y obtén el archivo flag.txt
en el directorio raíz. Envía el contenido como respuesta.
Lo primero que observamos al conectarnos es que el usuario pertenece al grupo docker:
Vamos a ver nlas imágenes de Docker existentes:
Vamos a ejecutar este contenedor con permisos elevados de la siguiente manera:
Buum! Ya somos el usuario root y obtenemos la flag!
Última actualización