¿Por qué debería hacer chroot para sandboxing por seguridad si mi aplicación puede ejecutarse desde un principio en un nivel inferior?

14

Estoy escribiendo un demonio de servidor HTTP en C (hay razones por las cuales), administrándolo con el archivo de unidad systemd.

Estoy reescribiendo una aplicación diseñada hace 20 años, alrededor de 1995. Y el sistema que utilizan es que se procesan y luego se configuran, y el procedimiento estándar.

Ahora, en mi trabajo anterior, la política habitual era que nunca ejecutara ningún proceso como root. Usted crea un usuario / grupo para ello y se ejecuta desde allí. Por supuesto, el sistema ejecutó algunas cosas como root, pero podríamos lograr todo el procesamiento de la lógica de negocios sin ser root.

Ahora, para el demonio HTTP, puedo ejecutarlo sin root si no hago un chroot dentro de la aplicación. Entonces, ¿no es más seguro que la aplicación nunca se ejecute como root?

¿No es más seguro ejecutarlo como mydaemon-user desde el principio? En lugar de comenzar con root, chrooting, luego setuid a mydaemon-user?

mur
fuente
3
debe ejecutarse como root para usar el puerto 80 o 443. De lo contrario, puede hacer lo que Tomcat y otros programas de aplicaciones web / servidores web hacen y ejecutar en un puerto superior (por ejemplo, 8080, 9090, etc.) y luego usar apache / nginx para proxy la conexión al software de su servidor web, o use el firewall del sistema para NAT / reenviar el tráfico a su servidor web desde el puerto 80. Si no necesita el puerto 80 o 443, o puede proxy o reenviar la conexión, entonces no tiene necesidad de ejecutar como root, en un chroot o de otra manera
SnakeDoc
3
@SnakeDoc en Linux ya no es cierto. Gracias a capabilities(7).
0xC0000022L
@SnakeDoc puede utilizar authbind así
Abdul Ahad

Respuestas:

27

Parece que otros han perdido su punto, lo cual no fue una razón por la cual usar raíces cambiadas, lo que, por supuesto, claramente ya sabe, ni qué más puede hacer para poner límites a los demonios, cuando también sabe claramente acerca de correr bajo los auspicios de cuentas de usuario sin privilegios; pero por qué hacer esto dentro de la aplicación . En realidad, hay un ejemplo bastante claro de por qué.

Considere el diseño del httpdprograma dæmon en el paquete de archivos públicos de Daniel J. Bernstein. Lo primero que hace es cambiar la raíz al directorio raíz que se le dijo que usara con un argumento de comando, luego soltar los privilegios a la ID de usuario sin privilegios y la ID de grupo que se pasan en dos variables de entorno.

Los conjuntos de herramientas de administración de Dæmon tienen herramientas dedicadas para cosas como cambiar el directorio raíz y soltar las identificaciones de usuarios y grupos sin privilegios. La runa de Gerrit Pape tiene chpst. Mi conjunto de herramientas nosh tiene chrooty setuidgid-fromenv. El s6 de Laurent Bercot tiene s6-chrooty s6-setuidgid. Wayne Marshall Perp tiene runtooly runuid. Etcétera. De hecho, todos tienen el propio conjunto de herramientas daemontools de M. Bernstein setuidgidcomo antecedente.

Se podría pensar que se podría extraer la funcionalidad httpdy utilizar herramientas tan dedicadas. Luego, como imagina, ninguna parte del programa del servidor se ejecuta con privilegios de superusuario.

El problema es que uno, como consecuencia directa, tiene que hacer mucho más trabajo para configurar la raíz modificada, y esto expone nuevos problemas.

Con Bernstein httpdcomo está, los únicos archivos y directorios que están en el árbol de directorios raíz son los que se publicarán en el mundo. No hay nada más en el árbol en absoluto. Además, no hay ninguna razón para que exista ningún archivo de imagen de programa ejecutable en ese árbol.

Pero mover el cambio de directorio raíz en un programa cargado en cadena (o systemd), y de repente el archivo de la imagen de programa para httpd, ninguna biblioteca compartida que se cargue, y cualquier archivo especial en /etc, /runy /devque el cargador de programa o tiempo de ejecución C acceso a la biblioteca durante la inicialización del programa (que puede ser bastante sorprendente si usted truss/ straceun programa C o C ++), también tiene que estar presente en la raíz modificada. De httpdlo contrario , no se puede encadenar y no se cargará / ejecutará.

Recuerde que este es un servidor de contenido HTTP (S). Potencialmente puede servir cualquier archivo (legible por el mundo) en la raíz modificada. Esto ahora incluye cosas como sus bibliotecas compartidas, su cargador de programas y copias de varios archivos de configuración de cargador / CRTL para su sistema operativo. Y si por algún medio (accidental) significa que el servidor de contenido tiene acceso para escribir cosas, un servidor comprometido posiblemente puede obtener acceso de escritura para la imagen del programa por httpdsí mismo, o incluso el cargador de programas de su sistema. (Recuerde que ahora tiene dos conjuntos paralelos de /usr, /lib, /etc, /run, y /devdirectorios para mantener seguro.)

Nada de esto es el caso donde httpdcambia la raíz y se caen los privilegios.

Por lo tanto, ha intercambiado tener una pequeña cantidad de código privilegiado, que es bastante fácil de auditar y que se ejecuta justo al inicio del httpdprograma, ejecutándose con privilegios de superusuario; por tener una superficie de ataque de archivos y directorios muy expandida dentro de la raíz modificada.

Es por eso que no es tan simple como hacer todo externamente al programa de servicio.

Tenga en cuenta que esto es, sin embargo, un mínimo de funcionalidad dentro de httpdsí mismo. Todo el código que hace cosas como buscar en la base de datos de cuentas del sistema operativo la ID de usuario y la ID de grupo para colocar en esas variables de entorno en primer lugar es externo al httpdprograma, en simples comandos auditables independientes como envuidgid. (Y, por supuesto, es una herramienta UCSPI, por lo que no contiene ninguna de código para que escuche en el puerto TCP correspondiente (s) o para aceptar conexiones, los que están siendo el dominio de los comandos tales como tcpserver, tcp-socket-listen, tcp-socket-accept, s6-tcpserver4-socketbinder, s6-tcpserver4d, y así sucesivamente).

Otras lecturas

JdeBP
fuente
+1, culpable de los cargos. Encontré el título y el último párrafo ambiguos, y si tienes razón, me perdí el punto. Esta respuesta da una interpretación muy práctica. Personalmente, señalaría explícitamente que tener que construir el entorno chroot como este es un esfuerzo adicional, que la mayoría de la gente querría evitar. Pero los 2 puntos de seguridad aquí ya están bien hechos.
sourcejedi
Otro punto a recordar es que si el servidor pierde privilegios antes de procesar cualquier tráfico de red, entonces el código privilegiado no está expuesto a ninguna explotación remota.
kasperd
5

Creo que muchos detalles de su pregunta podrían aplicarse igualmente avahi-daemon, lo que vi recientemente. (Sin embargo, podría haber perdido otro detalle que difiere). Ejecutar avahi-daemon en un chroot tiene muchas ventajas, en caso de que avahi-daemon se vea comprometido. Éstos incluyen:

  1. no puede leer el directorio de inicio de ningún usuario y extraer información privada.
  2. no puede explotar errores en otros programas escribiendo a / tmp. Hay al menos una categoría completa de tales errores. Por ejemplo, https://www.google.co.uk/search?q=tmp+race+security+bug
  3. no puede abrir ningún archivo de socket Unix que esté fuera del chroot, en el que otros demonios pueden estar escuchando y leyendo mensajes.

El punto 3 podría ser particularmente agradable cuando estás no usando dbus o similares ... Creo usos avahi-daemon dbus, por lo que se asegura de mantener el acceso al sistema de dbus incluso desde dentro del chroot. Si no necesita la capacidad de enviar mensajes en el sistema dbus, negar esa capacidad podría ser una buena característica de seguridad.

administrándolo con el archivo de unidad systemd

Tenga en cuenta que si se reescribe avahi-daemon, podría optar por confiar en systemd por seguridad, y usar, por ejemplo ProtectHome. Propuse un cambio a avahi-daemon para agregar estas protecciones como una capa adicional, junto con algunas protecciones adicionales que no están garantizadas por chroot. Puede ver la lista completa de opciones que propuse aquí:

https://github.com/lathiat/avahi/pull/181/commits/67a7b10049c58d6afeebdc64ffd2023c5a93d49a

Parece que hay más restricciones que podría haber usado si avahi-daemon no usara chroot, algunas de las cuales se mencionan en el mensaje de confirmación. Sin embargo, no estoy seguro de cuánto se aplica.

Tenga en cuenta que las protecciones que utilicé no habrían limitado el demonio de abrir archivos de socket Unix (punto 3 anterior).

Otro enfoque sería utilizar SELinux. Sin embargo, estaría vinculando su aplicación a ese subconjunto de distribuciones de Linux. La razón por la que pensé en SELinux positivamente aquí, es que SELinux restringe el acceso que los procesos tienen en dbus, de una manera muy precisa. Por ejemplo, creo que a menudo se puede esperar que systemdno esté en la lista de nombres de autobuses a los que necesita para poder enviar mensajes :-).

"Me preguntaba si usar sandboxing systemd es más seguro que chroot / setuid / umask / ..."

Resumen: ¿por qué no ambos? Decodifiquemos un poco lo anterior :-).

Si piensa en el punto 3, usar chroot proporciona más confinamiento. ProtectHome = y sus amigos ni siquiera intentan ser tan restrictivos como chroot. (Por ejemplo, ninguna de las listas negras de opciones de systemd nombradas /run, donde tendemos a colocar archivos de socket Unix).

chroot muestra que restringir el acceso al sistema de archivos puede ser muy poderoso, pero no todo en Linux es un archivo :-). Hay opciones de systemd que pueden restringir otras cosas, que no son archivos. Esto es útil si el programa se ve comprometido, puede reducir las características del kernel disponibles, en las que podría intentar explotar una vulnerabilidad. Por ejemplo, avahi-daemon no necesita enchufes bluetooth y supongo que su servidor web tampoco :-). Por lo tanto, no le dé acceso a la familia de direcciones AF_BLUETOOTH. Simplemente incluya en la lista blanca AF_INET, AF_INET6 y quizás AF_UNIX, usando la RestrictAddressFamilies=opción.

Lea los documentos de cada opción que use. Algunas opciones son más efectivas en combinación con otras, y algunas no están disponibles en todas las arquitecturas de CPU. (No porque la CPU sea mala, sino porque el puerto de Linux para esa CPU no estaba tan bien diseñado. Creo).

(Hay un principio general aquí. Es más seguro si puedes escribir listas de lo que quieres permitir, no lo que quieres negar. Como definir un chroot te da una lista de archivos a los que puedes acceder, y esto es más robusto que decir que quieres bloquear /home).

En principio, puede aplicar las mismas restricciones usted mismo antes de setuid (). Todo es solo código que puedes copiar de systemd. Sin embargo, las opciones de la unidad systemd deberían ser significativamente más fáciles de escribir, y dado que están en un formato estándar, deberían ser más fáciles de leer y revisar.

Por lo tanto, le recomiendo leer la sección de sandboxing man systemd.execen su plataforma de destino. Pero si desea el diseño más seguro posible, no tendría miedo de probar chroot(y luego eliminar los rootprivilegios) en su programa también . Hay una compensación aquí. El uso chrootimpone algunas restricciones en su diseño general. Si ya tiene un diseño que usa chroot, y parece hacer lo que necesita, eso suena bastante bien.

sourcejedi
fuente
+1 especialmente para las sugerencias systemd.
mattdm
aprendí bastante de tu respuesta, si la pila sobre el flujo permitiera una respuesta múltiple, también aceptaría tu respuesta. Me preguntaba si usar sandboxing systemd es más seguro que chroot / setuid / umask / ...
mur
@mur me alegro de que te haya gustado :). Esa es una respuesta muy natural a mi respuesta. Así que lo actualicé nuevamente para intentar responder a su pregunta.
sourcejedi 01 de
1

Si puede confiar en systemd, entonces es más seguro (¡y más simple!) Dejar el sandboxing a systemd. (Por supuesto, la aplicación también puede detectar si se ha lanzado sandboxed por systemd o no, y sandbox en sí mismo si todavía es root). El equivalente del servicio que describe sería:

[Service]
ExecStart=/usr/local/bin/mydaemon
User=mydaemon-user
RootDirectory=...

Pero no tenemos que parar allí. systemd también puede hacer muchos otros sandboxing por usted; aquí hay algunos ejemplos:

[Service]
# allocate separate /tmp and /var/tmp for the service
PrivateTmp=yes
# mount / (except for some subdirectories) read-only
ProtectSystem=strict
# empty /home, /root
ProtectHome=yes
# disable setuid and other privilege escalation mechanisms
NoNewPrivileges=yes
# separate network namespace with only loopback device
PrivateNetwork=yes
# only unix domain sockets (no inet, inet6, netlink, …)
RestrictAddressFamilies=AF_UNIX

Consulte man 5 systemd.execmuchas más directivas y descripciones más detalladas. Si hace que su demonio sea activable por socket ( man 5 systemd.socket), incluso puede usar las opciones relacionadas con la red: el único enlace del servicio al mundo exterior será el socket de red que recibió de systemd, no podrá conectarse a nada más. Si es un servidor simple que solo escucha en algunos puertos y no necesita conectarse a otros servidores, esto puede ser útil. (Las opciones relacionadas con el sistema de archivos también pueden hacer RootDirectoryobsoleto, en mi opinión, por lo que quizás ya no tenga que molestarse en configurar un nuevo directorio raíz con todos los binarios y bibliotecas necesarios).

Las versiones más recientes de systemd (desde v232) también son compatibles DynamicUser=yes, donde systemd asignará automáticamente el usuario del servicio para usted solo durante el tiempo de ejecución del servicio. Esto significa que no tienen que registrar un usuario permanente para el servicio, y funciona bien siempre y cuando el servicio no escribe en ningún ubicaciones del sistema de archivos que no StateDirectory, LogsDirectoryy CacheDirectory(que también se puede declarar en el archivo de la unidad - vea man 5 systemd.exec, nuevamente, y qué systemd administrará, teniendo cuidado de asignarlos correctamente al usuario dinámico).

Lucas Werkmeister
fuente