1 puntos por GN⁺ 2 시간 전 | 1 comentarios | Compartir por WhatsApp
  • CVE-2026-31431 Copy Fail permite que un usuario local sin privilegios obtenga una shell root, y dentro de contenedores rootless de Podman también puede elevar privilegios a root dentro del contenedor
  • Los contenedores rootless de Podman combinan namespaces de usuario, separación de UID y Linux capabilities para mapear el root dentro del contenedor a un usuario sin privilegios en el host y restringir los permisos en el host
  • En las pruebas, el usuario foo de un contenedor rootless non-root pudo convertirse en root dentro del contenedor tras ejecutar Copy Fail, pero sus permisos quedaron limitados al alcance permitido para el usuario sin privilegios bar del host, y no pudo leer archivos del host propiedad de root
  • Si se aplica --security-opt=no-new-privileges o --cap-drop=all, incluso después de ejecutar Copy Fail la shell se mantiene como foo y con capabilities none, lo que impide obtener de inmediato una shell root y elevar capabilities
  • El efecto de Copy Fail puede persistir más allá del ciclo de vida del contenedor, por lo que se requiere aplicar el parche del kernel y reiniciar; además, se debe implementar defensa en profundidad con sistema de archivos raíz de solo lectura, límites de recursos con cgroups, imágenes runtime ligeras y firewall

Alcance de exposición de Copy Fail y los contenedores rootless de Podman

  • CVE-2026-31431 fue divulgada el 29 de abril en copy.fail, y al ejecutar el script de Python publicado, un usuario local sin privilegios puede obtener una shell root
  • Copy Fail también puede explotarse dentro de contenedores Linux, y en contenedores rootless de Podman es posible obtener una shell root dentro del contenedor
  • En las pruebas, el root del contenedor quedó limitado, a nivel del host, al rango de permisos del usuario sin privilegios bar que ejecutó el contenedor
  • La implementación rootless de Podman combina namespaces de usuario, separación de UID y Linux capabilities para restringir los permisos en el host de los procesos del contenedor
  • Copy Fail demuestra que los contenedores rootless no son inmunes a la vulnerabilidad, pero que la configuración de Podman puede reducir el alcance del ataque después del compromiso

Cómo funcionan los contenedores rootless

  • Ejemplo básico: el usuario sin privilegios bar ejecuta un servidor HTTP

    • El entorno de ejemplo consiste en un usuario sin privilegios bar, con UID 1001, que construye con Podman una imagen basada en ubuntu:latest y ejecuta python3 -m http.server
    • Visto desde el host con ps, el proceso python3 se ejecuta como propiedad del usuario bar
    • Podman usa un modelo fork/exec, por lo que los procesos del contenedor se convierten en descendientes del proceso podman run, y la separación de UID habitual permite aislar los procesos del contenedor del root del host o de otros usuarios
    • En una configuración típica de Docker, aunque un usuario sin privilegios ejecute docker run, el cliente de Docker se comunica con un daemon con privilegios de root, y como ese daemon finalmente crea los procesos del contenedor, estos pueden verse como root en el host
  • Rootless rootful

    • Si la imagen del contenedor no tiene una instrucción USER explícita ni un flag --user, normalmente ejecuta el comando del contenedor como root interno
    • En la salida de podman top, el proceso del servidor HTTP aparece mapeado al usuario 1001 del host, pero dentro del contenedor se ejecuta como root
    • Esta configuración se ejecuta como usuario sin privilegios en el host, pero como root dentro del contenedor, es decir, un estado rootless rootful
  • Namespaces de usuario

    • Los contenedores rootless de Podman usan namespaces de usuario para mapear de forma distinta los UID/GID dentro y fuera del contenedor
    • En el ejemplo, el root con UID 0 dentro del contenedor se mapea al UID 1001 del host, correspondiente a bar
    • La configuración bar:165536:65536 en /etc/subuid define el rango de UID que pueden asignarse a los procesos en el namespace de bar
    • En el ejemplo, además del UID 1001 de bar, se pueden asignar a sus procesos UID desde 165536 hasta 231072
    • Si se ejecuta sleep como el usuario www-data dentro del contenedor, internamente aparece como www-data, pero en el host se muestra como 165568
    • Al entrar al namespace de usuario con podman unshare, el directorio home que en el host pertenece a bar:bar se ve como root:root dentro del namespace
    • Docker también soporta namespaces de usuario, pero requiere configuración adicional y solo permite un namespace de usuario, mientras que Podman ejecuta los contenedores rootless de cada usuario UNIX dentro del namespace de ese usuario
  • Operaciones privilegiadas y Linux capabilities

    • Podman usa Linux capabilities para otorgar a los procesos del contenedor privilegios de root de forma granular
    • Durante la construcción de la imagen, tareas como apt install se vuelven posibles mediante una combinación de capabilities como chown, dac_override, fowner, setgid, setuid, net_bind_service y sys_chroot
    • Si se eliminan todas las capabilities con podman build --cap-drop=all, apt falla en operaciones como setgroups, setegid, seteuid y chown, y la construcción de la imagen no se completa
    • También es posible agregar solo las capabilities necesarias; en el ejemplo, se añaden CAP_SETUID,CAP_SETGID,CAP_CHOWN,CAP_DAC_OVERRIDE,CAP_FOWNER para realizar la instalación de paquetes
    • En su estado de ejecución predeterminado, el servidor HTTP se ejecuta como root dentro del contenedor y tiene muchas effective capabilities, como CHOWN,DAC_OVERRIDE,FOWNER,FSETID,KILL,NET_BIND_SERVICE,SETFCAP,SETGID,SETPCAP,SETUID,SYS_CHROOT
    • Como el servidor HTTP no necesita esos privilegios, se pueden eliminar todas las capabilities con podman run --cap-drop=all; en ese caso, podman top muestra las effective capabilities como none
  • Rootless non-root

    • Para ejecutar el servidor HTTP también como usuario sin privilegios dentro del contenedor, se puede usar un usuario existente en /etc/passwd, como www-data, o crear un usuario dedicado durante la construcción de la imagen
    • En el ejemplo, se crea el usuario y grupo foo con UID 1002, se otorgan permisos de lectura a /var/www/html y luego se configura USER foo:foo
    • Si esta imagen se ejecuta con --cap-drop=all, el proceso queda como foo dentro del contenedor, UID 166537 en el host y effective capabilities none
    • Los procesos del contenedor deben ejecutarse con el mínimo privilegio necesario; por ejemplo, si foo necesita enlazarse al puerto privilegiado 80, se debe añadir --cap-add=CAP_NET_BIND_SERVICE
    • Las formas de ejecutar un contenedor se dividen en cuatro tipos
      • usuario root del host + contenedor root: root rootful
      • usuario root del host + usuario sin privilegios en el contenedor: root non-root
      • usuario sin privilegios del host + contenedor root: rootless rootful
      • usuario sin privilegios del host + usuario sin privilegios en el contenedor: rootless non-root
    • Podman facilita la ejecución de contenedores rootless rootful, y si es posible ejecutar los procesos del contenedor como usuario sin privilegios, también es relativamente fácil crear una configuración rootless non-root

Montajes bind y aislamiento de UID

  • Cuando se monta un directorio del host en el contenedor, la posibilidad de acceder a archivos propiedad de root del host, bar del host y foo del namespace cambia según el mapeo de UID
  • En el ejemplo, se crean en el directorio /var/lib/bar/test los archivos root.txt, propiedad de root del host, y bar.txt, propiedad de bar del host, y se montan en el contenedor como /test con permisos de lectura/escritura
  • Si el contenedor se ejecuta como foo, el archivo propiedad de bar del host se ve dentro del contenedor como root:root, y el archivo propiedad de root del host aparece como nobody:nogroup porque no está mapeado al namespace
  • foo dentro del contenedor no puede leer bar.txt ni root.txt, y un rootless non-root ofrece aislamiento adicional frente a un rootless rootful
  • foo.txt, creado por foo en el directorio montado, aparece en el host como propiedad del UID 166537, y el usuario bar del host no puede leer su contenido
  • Si el contenedor se ejecuta como root interno, el root del namespace puede leer el archivo propiedad de bar del host y el archivo propiedad de foo, pero no puede leer el archivo root.txt propiedad de root del host
  • Si se ejecuta como root interno con --cap-drop=all, tampoco puede leer el archivo de foo y solo puede leer el archivo propiedad de bar del host

Prueba de Copy Fail

  • Condiciones de prueba

    • En la prueba de Copy Fail se usa la versión del exploit del commit publicado originalmente 8e918b5
    • La imagen de contenedor de ejemplo agrega curl a una imagen existente de servidor HTTP para poder descargar el script del exploit desde dentro del contenedor
    • La imagen se construye con el nombre copyfail
    • El kernel de prueba es 6.12.74+deb13+1-amd64 de Debian, y en Debian se considera que las versiones recientes por debajo de 6.12.85 aún pueden usarse como kernels no parchados
    • Normalmente, si el usuario sin privilegios foo llama a su, se le solicita la contraseña de root
    • En cada prueba, el usuario del contenedor descarga el script de Copy Fail en /tmp y lo ejecuta; si obtiene una shell de root, llama a sleep
    • Como Copy Fail persiste más allá del ciclo de vida del contenedor, la VM se reinicia antes de cada prueba
  • Resultados en rootless rootful

    • Si el contenedor se ejecuta con --user=root, el proceso dentro del contenedor ya es root
    • En este estado, al ejecutar el script de Copy Fail y llamar a su, se obtiene una shell uid=0(root), pero como el usuario root ya puede abrir otra shell de root con su sin contraseña, en la práctica Copy Fail no agrega nada
    • En podman top, /bin/bash, python3 copy_fail_exp.py, su y sleep aparecen todos como root dentro del contenedor y como usuario 1001 en el host
    • Se mantiene el mismo conjunto de capabilities, y aparecen CHOWN,DAC_OVERRIDE,FOWNER,FSETID,KILL,NET_BIND_SERVICE,SETFCAP,SETGID,SETPCAP,SETUID,SYS_CHROOT
    • El root interno puede leer bar.txt y foo.txt en el /test montado, pero no puede leer root.txt, propiedad de root del host
  • Resultados en rootless non-root

    • Después de ejecutar el contenedor como foo, al correr el script de Copy Fail y llamar a su, se elevan privilegios a root dentro del contenedor
    • El id de la shell resultante aparece como uid=0(root) gid=1002(foo) groups=1002(foo)
    • En podman top, el /bin/bash inicial, el proceso que ejecuta el exploit y la llamada a su aparecen con UID 166537 en el host, usuario foo en el contenedor y capabilities none
    • Tras la elevación de privilegios, [sh] y sleep aparecen como usuario 1001 en el host y usuario root en el contenedor, obteniendo el mismo conjunto de capabilities que en rootless rootful
    • Incluso el root del contenedor con privilegios elevados no puede leer root.txt, propiedad de root del host
    • En este estado, el contenedor queda comprometido, pero el alcance del ataque se limita al contenedor y a lo que puede hacer el usuario sin privilegios bar en el host
  • Resultados al aplicar no-new-privileges

    • Podman permite usar --security-opt=no-new-privileges para impedir que el proceso del contenedor obtenga más privilegios que los que tenía al iniciarse
    • Si se aplica esta opción a un contenedor rootless non-root y se ejecuta Copy Fail, la shell se abre, pero sigue en estado uid=1002(foo)
    • En podman top, todos los procesos se mantienen con UID 166537 en el host, usuario foo en el contenedor y capabilities none
    • También en el /test montado, foo solo puede leer su propio archivo y no puede leer bar.txt ni root.txt
    • El contenedor queda comprometido, pero sigue limitado al usuario interno sin privilegios foo y a un estado sin capabilities
  • Resultados al aplicar --cap-drop=all

    • Aunque se ejecute un contenedor rootless non-root con --cap-drop=all, foo originalmente no tiene capabilities
    • En este estado, al ejecutar Copy Fail y llamar a su, la shell abierta sigue siendo uid=1002(foo)
    • En podman top, /bin/bash, la ejecución del exploit, su, la shell y sleep permanecen todos como foo y con capabilities none
    • El exploit no logra obtener una shell de root, y foo solo puede leer su propio archivo en /test
    • Este resultado es similar al de la prueba con no-new-privileges, y ambas medidas pueden usarse juntas para reducir eficazmente la exposición a capabilities
  • Persistencia del exploit

    • Aunque no-new-privileges o --cap-drop=all pudieron impedir la obtención inmediata de una shell de root y de capabilities, el efecto del exploit en sí permanece
    • Si después se ejecuta un contenedor nuevo sin restricciones de capabilities, el usuario sin privilegios foo del contenedor puede convertirse en root del contenedor con solo llamar a su
    • Por lo tanto, sigue siendo necesario aplicar el parche del kernel y reiniciar

Estrategias de defensa en profundidad

  • Imágenes de solo lectura

    • Si agregas --read-only a podman run, el sistema de archivos raíz del contenedor se monta como solo lectura
    • Por defecto, Podman monta algunos directorios como escribibles, como /tmp, /run y /var/tmp, así que para dejarlo completamente en solo lectura también hay que agregar --read-only-tmpfs=false
    • Si un contenedor de solo lectura es comprometido, no se permiten escrituras en el sistema, lo que puede limitar algunos ataques posteriores a la explotación
    • Aun así, como la salida de curl puede enviarse por tubería a python3, configurar solo lectura por sí sola no impide la ejecución del exploit
    • El servidor HTTP de python3 del ejemplo no necesita escribir en el sistema de archivos, por lo que esta opción puede usarse de forma segura
    • Muchas imágenes preconstruidas asumen acceso de escritura a ciertos directorios, por lo que pueden no funcionar correctamente con un sistema de archivos raíz de solo lectura
    • Un sistema de archivos raíz de solo lectura es independiente de los volúmenes escribibles conectados al contenedor, y en caso de compromiso aún se podrá escribir en esos directorios montados
  • Límites de recursos

    • Docker y Podman pueden usar cgroups para limitar los recursos disponibles para los contenedores
    • Un contenedor no necesita memoria, CPU ni PID ilimitados
    • Puedes revisar el uso de recursos del contenedor con podman stats y aplicar límites en consecuencia
  • Limitar los binarios disponibles

    • El ejemplo usa la imagen ubuntu por simplicidad, pero la imagen ubuntu incluye muchos binarios que un atacante podría usar si el contenedor es comprometido
    • Para ejecutar el servidor HTTP no se necesita la mayoría de esos binarios
    • Conviene que la imagen de runtime sea lo más liviana posible
    • Puedes usar compilaciones multietapa para separar el entorno de build del entorno de runtime
    • También puedes basarte en imágenes específicas para un propósito como python3, variantes -slim de Debian o distribuciones más pequeñas como alpine
    • Si es compatible con el proceso del contenedor, puedes usar distroless images o scratch para crear un runtime sin shell, administrador de paquetes ni utilidades del sistema
  • Firewall

    • Puedes usar iptables o nftables para restringir con firewall los procesos del contenedor
    • Solo se deben permitir las conexiones entrantes y salientes estrictamente necesarias para el proceso del contenedor
    • En el ejemplo del servidor HTTP no se necesitan conexiones a DNS ni a servidores locales o remotos, así que se puede limitar para permitir solo paquetes tcp provenientes de conexiones entrantes ya establecidas

Implicaciones operativas

  • Los contenedores rootless estándar de Podman ofrecen por defecto un mejor aislamiento que una configuración estándar de contenedores Docker
  • Docker también permite ejecución rootless y uso de namespaces de usuario sin privilegios, pero requiere más trabajo de configuración que Podman y también influyen las diferencias de arquitectura
  • Docker sigue siendo ampliamente usado, y herramientas de self-hosting como Dokku, Kamal, Coolify y Dokploy también usan Docker por defecto
  • Si ejecutas imágenes de Docker Hub sin revisarlas lo suficiente o sin aplicar medidas de bloqueo, el servicio puede quedar expuesto con una superficie de ataque mayor a la necesaria
  • Hay que entender los detalles de implementación de las imágenes de contenedor
    • Debes saber con qué usuario o usuarios se ejecutan los procesos del contenedor
    • Debes saber de qué directorios del sistema de archivos raíz dependen los procesos del contenedor
    • Debes distinguir entre las capabilities de Linux necesarias y las que no lo son
  • Al combinar los distintos mecanismos que ofrecen Podman y los contenedores, se puede reforzar el contenedor y reducir el radio de impacto en caso de compromiso
  • Según la carga de trabajo, no se debe depender del contenedor como único límite de seguridad
  • Usar contenedores junto con máquinas físicas o virtuales separadas puede proporcionar un aislamiento efectivo
  • Podman ofrece una forma de aislar cargas de trabajo incluso dentro del mismo host ejecutando cada una con un usuario sin privilegios distinto y su propio namespace de usuario

Material adicional

1 comentarios

 
GN⁺ 2 시간 전
Opiniones en Lobste.rs
  • Hay que enfocarse en el comportamiento primitivo que hace posible la vulnerabilidad, más que en el exploit publicado
    Esta vulnerabilidad permite escribir en la page cache sin importar si es de solo lectura, así que un contenedor malicioso puede alterar páginas que pertenecen a archivos de la imagen base de overlayfs, y según cómo se desplieguen los contenedores, el impacto puede propagarse a otros contenedores
    En una configuración rootless como esta, los objetivos serían otros contenedores que se ejecuten con el mismo usuario en el sistema host
    Otra forma de explotación sería ejecutar o localizar un contenedor basado en una imagen base que ya se sepa que está en uso, alterar la page cache dentro de ese contenedor y luego hacer que otro contenedor que comparta el mismo runtime y los mismos datos de overlayfs ejecute ese código
    Rootless y los user namespaces son importantes, pero aquí no ayudan mucho y, como dice el sitio de copy.fail, en contenedores habría que considerar bloquear con seccomp la llamada al sistema socket(AF_ALG, ...)

    • No había pensado tan a fondo en el comportamiento primitivo subyacente, y me enfoqué más en ordenar los namespaces y capabilities que ofrece un contenedor rootless para evaluar el alcance de exposición de un contenedor comprometido
      Estaría bien explicar un poco más qué significa exactamente “según cómo se desplieguen los contenedores”
      Una ventaja de Podman rootless es que, dependiendo de la carga de trabajo, no hace falta ejecutar los contenedores con el mismo usuario en el host
      Si te refieres al caso de correr varios contenedores rootless con el usuario principal de la estación de trabajo, estoy de acuerdo, pero en servidores cada uno puede aislarse con un usuario distinto y la misma imagen de contenedor puede ejecutarse con diferentes usuarios no privilegiados
      Eso es bastante distinto del valor predeterminado de Docker, que ejecuta la mayoría de las cosas como root, pero también escribí al final del texto que eso no constituye el límite de seguridad definitivo, y que repartir contenedores rootless entre varios usuarios sin privilegios depende del caso de uso
      Algunas cargas de trabajo específicas sí las aíslo con VM
      Me pregunto si con que rootless y los user namespaces no ayudan aquí te refieres a impedir la explotación
      Aún no he usado seccomp con políticas explícitas en contenedores, así que no lo traté, pero es un buen motivo para investigarlo más
  • Me gustan Podman y los contenedores rootless, pero al ver CopyFail llegué a la misma conclusión que el comentario hermano
    Incluso con las ventajas extra de control de acceso de podman+rootless, al final vuelve a confirmarse el consejo clásico de que los contenedores no son un límite de seguridad, y un solo exploit del kernel puede abrirlo todo
    Solo administro sistemas como hobby, pero como tendencia nueva en este espacio vi el backend libkrun para crun con podman
    La promesa es que puede manejar la mayoría de las cargas de trabajo en contenedores tal cual, pero ejecutándolas internamente en una MicroVM con un kernel invitado aparte; no sé bien qué tan madura está, cuánto uso real tiene ni el nivel de auditoría de seguridad, y parte de eso se ve bastante de punta
    Como las MicroVM están siendo adoptadas activamente por herramientas de programación con LLM, puede que ese estado no dure mucho
    podman machine también me pareció prometedor, pero por desgracia parece pensado solo para estaciones de trabajo de desarrolladores y con un modelo de una sola VM de ejecución de contenedores por sistema host
    Aun así, me parece que decir “los contenedores no son un límite de seguridad” es demasiado simplista. Claramente sí son un límite de seguridad, solo que no tan fuerte como quisiéramos creer

    • Esa es también la razón por la que la mayoría de los despliegues de contenedores en la nube usan VM. Las VM son un límite defendible
      En despliegues locales esa línea es un poco más difusa
      Desde el punto de vista del hardware, una VM no es intrínsecamente más segura que un proceso, pero hay tres razones por las que el límite es más defendible
      Escapar de una VM es menos común que explotar una system call, así que hay más margen para aplicar mitigaciones de side channels sin castigar el rendimiento
      La interfaz host de una VM es mucho más simple. Un dispositivo de bloques tiene una interfaz de lectura y escritura por bloques, y un dispositivo de red envía y recibe frames
      La llamada setsockopt que Linux o *BSD exponen en sockets es una superficie de ataque mucho mayor que la mayoría de los drivers emulados o paravirtualizados, y aun así eso es solo una parte muy pequeña de toda la superficie de ataque del kernel
      La interfaz de una VM también tiene mucho menos estado. Hay transacciones en curso en anillos de un modelo de solicitud-respuesta, pero fuera de eso casi nada
      Cosas como credenciales, UID, GID y tablas de descriptores de archivo añaden complejidad basada en estado dentro del kernel, y si hay bugs un proceso puede explotarlos
      La dificultad de la variante de estación de trabajo es que vuelve a introducir esa complejidad
      Por ejemplo, la capa base del contenedor podría exponerse como un dispositivo de bloques con un sistema de archivos inmutable, pero los volúmenes y carpetas compartidas probablemente se montarían con 9pfs o VirtIO-FS, es decir, 9p o FUSE sobre VirtIO
      Entonces la superficie de ataque vuelve a crecer
      Con suerte, eso obligaría a una cadena de explotación
      Estoy más familiarizado con FreeBSD, y normalmente ahí los componentes que proveen dispositivos paravirtualizados o emulados se aíslan con sandboxing usando Capsicum, así que primero habría que comprometer un proceso del host y después romper también el kernel para acceder a algo a lo que la VM no tenía permiso
      Pero sin ese sandboxing adicional se vuelve al mundo en que escapar de un contenedor permite hacer todo lo que el usuario puede hacer, y en escritorio eso no es mucho mejor que comprometer root
    • Kata Containers y Firecracker ya son tecnologías bastante antiguas, y como investigadores las han revisado, me parecen razonablemente maduras
      En lo personal prefiero más gVisor. No es un runtime VMM, pero lleva años existiendo, también lo usan empresas como Tencent, y en mi entorno, donde todos los contenedores ya corren dentro de VM de Proxmox, encaja bien
      Otra cosa que estoy probando es syd-oci, que parece haber recibido algo menos de atención frente a las recomendaciones habituales de MicroVM o gVisor
    • Esa expresión también coincide con mi experiencia, y escribir este texto fue casi un ejercicio de aceptar ese hecho
      Gracias también por la referencia a libkrun, parece una posibilidad prometedora
    • La adopción activa del lado de las herramientas de programación con LLM probablemente hará que las MicroVM maduren más, y podría traducirse en más uso real y más endurecimiento
      También parece bastante probable que eso lleve a auditorías de seguridad