
Estoy escribiendo un demonio de servidor HTTP en C (hay razones para ello), 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 usan es que hacen chroot y luego setuid, y el procedimiento estándar.
Ahora bien, en mi trabajo anterior, la política habitual era que nunca ejecutabas ningún proceso como root. Creas un usuario/grupo para ello y lo ejecutas desde allí. Por supuesto, el sistema ejecutó algunas cosas como root, pero pudimos lograr todo el procesamiento de la lógica empresarial sin ser root.
Ahora, para el demonio HTTP, puedo ejecutarlo sin root si no hago 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 iniciarlo con root, hacer chroot y luego configurarlo en mydaemon-user?
Respuesta1
Parece que otros no han entendido tu punto, lo cual no fue razones para usar raíces cambiadas, lo cual por supuesto ya sabes claramente, ni qué más puedes hacer para poner límites a los demonios, cuando también sabes claramente cómo correr bajo los auspicios de cuentas de usuario sin privilegios; pero ¿por qué hacer estas cosas?dentro de la aplicación. En realidad, hay un ejemplo bastante claro de por qué.
Considere el diseño del httpd
programa demonio en el paquete publicfile de Daniel J. Bernstein. Lo primero que hace es cambiar la raíz al directorio raíz que se le indicó que usara con un argumento de comando, luego otorgar privilegios al ID de usuario sin privilegios y al 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 acceder a ID de usuarios y grupos sin privilegios. El runit de Gerrit Pape tienechpst
. Mi conjunto de herramientas Nosh tienechroot
ysetuidgid-fromenv
. El s6 de Laurent Bercot tienes6-chroot
ys6-setuidgid
. El perpetrador de Wayne Marshall tieneruntool
yrunuid
. Etcétera. De hecho, todos tienen el propio conjunto de herramientas daemontools de M. Bernstein consetuidgid
como antecedente.
Uno pensaría que se podría extraer la funcionalidad de httpd
este tipo de herramientas dedicadas y utilizarlas. Entonces, como imaginas,Noparte del programa del servidor se ejecuta alguna vez con privilegios de superusuario.
El problema es que, como consecuencia directa, uno tiene que trabajar mucho más para configurar la raíz modificada, y esto expone nuevos problemas.
httpd
Tal como está Bernstein , elsoloLos archivos y directorios que se encuentran en el árbol de directorios raíz son los que se publicarán en todo el mundo. Haynada másen el árbol en absoluto. Además, no hay ninguna razón paracualquierarchivo de imagen del programa ejecutable para existir en ese árbol.
Pero mueva el directorio raíz a un programa de carga en cadena (o systemd) y, de repente, el archivo de imagen del programa para httpd
, cualquier biblioteca compartida que cargue y cualquier archivo especial en /etc
, /run
y /dev
al que acceda el cargador de programas o la biblioteca de tiempo de ejecución de C. durante la inicialización del programa (lo que puede resultarle bastante sorprendente si tiene truss
un strace
programa C o C++),tambiéntiene que estar presente en la raíz modificada. De lo contrario, httpd
no se puede encadenar y no se cargará ni ejecutará.
Recuerde que este es un servidor de contenidos HTTP(S). Potencialmente, puede mostrar cualquier archivo (legible en todo 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 del cargador/CRTL para su sistema operativo. Y si por algún medio (accidental) el servidor de contenidos tiene acceso aescribircosas, un servidor comprometido posiblemente pueda obtener acceso de escritura a la imagen del programa para httpd
sí mismo, o incluso al cargador de programas de su sistema. (Recuerde que ahora tiene dos conjuntos paralelos de directorios /usr
, /lib
, /etc
, /run
y /dev
que debe mantener seguros).
Nada de esto es el caso cuando httpd
se cambia la raíz y se eliminan los privilegios.
Así que 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 httpd
programa, ejecutándose con privilegios de superusuario; por tener una superficie de ataque muy ampliada de archivos y directorios dentro de la raíz modificada.
Por eso no es tan sencillo como hacer todo externamente al programa de servicio.
Tenga en cuenta que, no obstante, esto es una funcionalidad mínima en httpd
sí misma. Todo el código que hace cosas como buscar en la base de datos de cuentas del sistema operativo el ID de usuario y el ID de grupo para colocarlos en esas variables de entorno en primer lugar.esexterno al httpd
programa, en comandos auditables simples e independientes como envuidgid
. (Y, por supuesto, es una herramienta UCSPI, por lo que no contiene ningún código para escuchar en los puertos TCP relevantes o para aceptar conexiones, siendo esos el dominio de comandos comotcpserver
,tcp-socket-listen
,tcp-socket-accept
,s6-tcpserver4-socketbinder
,s6-tcpserver4d
, etcétera.)
Otras lecturas
- Daniel J. Bernstein (1996).
httpd
.archivo publico. cr.yp.to. httpd
.Los softwares de Daniel J. Bernstein todo en uno. Softwares. Jonathan de Boyne Pollard. 2016.gopherd
.Los softwares de Daniel J. Bernstein todo en uno. Softwares. Jonathan de Boyne Pollard. 2017.- https://unix.stackexchange.com/a/353698/5132
- https://github.com/janmojzis/httpfile/blob/master/droproot.c
Respuesta2
Creo que muchos detalles de su pregunta podrían aplicarse igualmente a avahi-daemon
, que examiné recientemente. (Aunque es posible que me haya perdido otro detalle que difiere). Ejecutar avahi-daemon en un chroot tiene muchas ventajas, en caso de que avahi-daemon esté comprometido. Éstas incluyen:
- no puede leer el directorio de inicio de ningún usuario ni filtrar información privada.
- no puede explotar errores en otros programas escribiendo en /tmp. Existe al menos una categoría completa de errores de este tipo. P.ejhttps://www.google.co.uk/search?q=tmp+race+security+bug
- no puede abrir ningún archivo de socket Unix que esté fuera del chroot, en el que otros demonios puedan estar escuchando y leyendo mensajes.
El punto 3 podría ser particularmente bueno cuando estásnousando dbus o similar... Creo que avahi-daemon usa dbus, por lo que se asegura de mantener el acceso al dbus del sistema incluso desde dentro del chroot. Si no necesita la capacidad de enviar mensajes en el dbus del sistema, negar esa capacidad podría ser una característica de seguridad bastante buena.
administrándolo con el archivo de unidad systemd
Tenga en cuenta que si se reescribiera avahi-daemon, podría optar por confiar en systemd por motivos de seguridad y utilizar, por ejemplo ProtectHome
, . Propuse un cambio en avahi-daemon para agregar estas protecciones como una capa adicional, junto con algunas protecciones adicionales que no están garantizadas por chroot. Puedes 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 lo hubiera hecho.nouse el propio chroot, algunos de los cuales se mencionan en el mensaje de confirmación. Sin embargo, no estoy seguro de hasta qué punto se aplica esto.
Tenga en cuenta que las protecciones que utilicé no habrían limitado al demonio a 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é positivamente en SELinux aquí es que SELinux restringe el acceso que los procesos tienen en dbus, de una manera detallada. Por ejemplo, creo que a menudo se podría esperar que eso systemd
no estuviera en la lista de nombres de autobuses a los que necesitaba poder enviar mensajes :-).
"Me preguntaba si usar el sandboxing de systemd es más seguro que chroot/setuid/umask/..."
Resumen: ¿por qué no ambos? Descifremos un poco lo anterior :-).
Si piensas 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 una herramienta muy poderosa, pero notodoen Linux es un archivo :-). Hay opciones de systemd que pueden restringir otras cosas, que no son archivos. Esto es útil si el programa está comprometido, puede reducir las funciones del kernel disponibles, en las que podría intentar explotar una vulnerabilidad. Por ejemplo, avahi-daemon no necesita conectores bluetooth y supongo que su servidor web tampoco. :-). Así que no le dé acceso a la familia de direcciones AF_BLUETOOTH. Simplemente incluya en la lista blanca AF_INET, AF_INET6 y tal vez AF_UNIX, usando la RestrictAddressFamilies=
opción.
Lea los documentos de cada opción que utilice. 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 creo que el puerto Linux para esa CPU no estaba tan bien diseñado).
(Hay un principio general aquí. Es más seguro si puedes escribir listas de lo que quieres permitir, no de lo que quieres negar. Por ejemplo, definir un chroot te brinda una lista de archivos a los que puedes acceder, y esto es más sólido. que decir que quieres bloquear /home
).
En principio, usted mismo podría aplicar las mismas restricciones antes de setuid(). Todo es solo código que puedes copiar desde systemd. Sin embargo, las opciones de la unidad systemd deberían ser mucho 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, recomiendo encarecidamente leer la sección de sandboxing de man systemd.exec
su plataforma de destino. Pero si desea el diseño más seguro posible, no tendría miedo de intentarlo chroot
(y luego eliminar root
privilegios) en su programa.también. Aquí hay una compensación. El uso chroot
impone algunas limitaciones en su diseño general. Si ya tiene un diseño que usa chroot y parece hacer lo que necesita, suena bastante bien.
Respuesta3
Si puede confiar en systemd, entonces es más seguro (¡y más sencillo!) dejar el sandboxing a systemd. (Por supuesto, la aplicación también puede detectar si ha sido iniciada en el entorno aislado de systemd o no, y en el entorno aislado si todavía es root). El equivalente del servicio que usted describe sería:
[Service]
ExecStart=/usr/local/bin/mydaemon
User=mydaemon-user
RootDirectory=...
Pero no tenemos que detenernos ahí. systemd también puede realizar muchas otras aplicaciones de 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.exec
para obtener muchas más directivas y descripciones más detalladas. Si activa el socket de su demonio ( man 5 systemd.socket
), puede incluso usar las opciones relacionadas con la red: el único vínculo del servicio con el mundo exterior será el socket de red que recibió de systemd, no podrá conectarse a nada más. Si se trata de un servidor simple que solo escucha en algunos puertos y no necesita conectarse a otros servidores, esto puede resultar útil. (En mi opinión, las opciones relacionadas con el sistema de archivos también pueden hacer que el archivo quede RootDirectory
obsoleto, por lo que quizás ya no necesite molestarse en configurar un nuevo directorio raíz con todos los binarios y bibliotecas necesarios).
Las versiones más nuevas de systemd (desde v232) también admiten DynamicUser=yes
, donde systemd asignará automáticamente el usuario del servicio solo durante el tiempo de ejecución del servicio. Esto significa que no tiene que registrar un usuario permanente para el servicio y funciona bien siempre y cuando el servicio no escriba en ninguna ubicación del sistema de archivos que no sea StateDirectory
, LogsDirectory
y CacheDirectory
(que también puede declarar en el archivo de la unidad). ver man 5 systemd.exec
, nuevamente – y qué systemd administrará luego, cuidando de asignarlos correctamente al usuario dinámico).