Setuid equivalente para usuarios no root

¿Tiene Linux alguna interfaz C similar a setuid , que permite que un progtwig cambie a un usuario diferente utilizando, por ejemplo, el nombre de usuario / contraseña? El problema con setuid es que solo puede ser usado por superusuarios.

Estoy ejecutando un servicio web simple que requiere que los trabajos se ejecuten como el usuario que inició sesión. Por lo tanto, el proceso principal se ejecuta como root, y después de que el usuario inicia sesión ingresa a setuid y lo llama para cambiar al uid apropiado. Sin embargo, no estoy del todo cómodo con el proceso principal ejecutándose como root. Preferiría que se ejecute como otro usuario, y tenga algún mecanismo para cambiar a otro usuario similar a su (pero sin iniciar un nuevo proceso).

No, no hay forma de cambiar el UID usando solo un nombre de usuario y contraseña. (El concepto de una “contraseña” no es reconocido por el kernel de ninguna manera, solo existe en el espacio de usuario). Para cambiar de un UID no root a otro, debe convertirse en root como paso intermedio, generalmente por exec() -utilizando un binario setuid.

Otra opción en su situación puede ser que el servidor principal se ejecute como un usuario sin privilegios, y que se comunique con un proceso de back-end que se ejecute como root.

Primero, setuid() definitivamente puede ser usado por no superusuarios. Técnicamente, todo lo que necesita en Linux es la CAP_SETUID (y / o CAP_SETGID ) para cambiar a cualquier usuario. En segundo lugar, setuid() y setgid() pueden cambiar la identidad del proceso entre el real (el usuario que ejecutó el proceso ), el efectivo ( propietario del binario de setuid / setgid ) y las identidades guardadas.

Sin embargo, nada de eso es realmente relevante para su situación.

Existe una solución relativamente sencilla, pero extremadamente robusta: tener un ayudante root de setuid, bifurcado y ejecutado por su demonio de servicio antes de crear cualquier subproceso, y usar un par de socket de dominio Unix para comunicarse entre el ayudante y el servicio, el servicio pasa ambos sus credenciales y los descriptores de archivo de punto final de tubería al ayudante cuando se ejecutan los binarios de usuario. El ayudante verificará todo de forma segura y, si todo está en orden, se dividirá y ejecutará el ayudante de usuario deseado, con los puntos finales de tubería especificados conectados a la entrada estándar, la salida estándar y el error estándar.

El procedimiento para que el servicio inicie el ayudante, lo antes posible, es el siguiente:

  1. Cree un par de sockets de dominio Unix, utilizado para comunicaciones privilegiadas entre el servicio y el ayudante.

  2. Tenedor.

  3. En el secundario, cierre todos los descriptores de archivos en exceso, manteniendo solo un extremo del par de sockets. Redirigir la entrada estándar, la salida y el error a /dev/null .

  4. En el elemento primario, cierre el extremo secundario del par de sockets.

  5. En el niño, ejecute el binario auxiliar ayudante.

  6. El padre envía un mensaje simple, posiblemente uno sin ningún tipo de datos, pero con un mensaje auxiliar que contiene sus credenciales.

  7. El progtwig auxiliar espera el mensaje inicial del servicio. Cuando lo recibe, comprueba las credenciales. Si las credenciales no pasan el examen, se cierra inmediatamente.

Las credenciales en el mensaje auxiliar definen el UID , GID y PID proceso de origen. Aunque el proceso necesita completar estos, el kernel verifica que son verdaderos. El ayudante, por supuesto, verifica que el UID y el GID son los esperados (corresponden a la cuenta en la que se debe ejecutar el servicio), pero el truco es obtener las estadísticas sobre el archivo al que apunta el enlace simbólico /proc/PID/exe . Ese es el auténtico ejecutable del proceso que envió las credenciales. Debe verificar que sea el mismo que el daemon de servicio del sistema instalado (propiedad de root: root, en el directorio binario del sistema).

Hay un ataque muy simple que puede derrotar la seguridad hasta este punto. Un usuario nefasto puede crear su propio progtwig, que bifurca y ejecuta el binario auxiliar correctamente, envía el mensaje inicial con sus verdaderas credenciales, pero se reemplaza a sí mismo con el sistema binario correcto antes de que el ayudante tenga la oportunidad de verificar a qué se refieren realmente las credenciales !

Ese ataque es trivialmente derrotado por tres pasos adicionales:

  1. El progtwig auxiliar genera un número pseudoaleatorio (criptográficamente seguro), digamos 1024 bits, y lo envía de vuelta al padre.

  2. El padre devuelve el número, pero nuevamente agrega sus credenciales en un mensaje auxiliar.

  3. El progtwig auxiliar verifica que el UID , el GID y el PID no hayan cambiado, y que /proc/PID/exe aún apunte al binario del daemon de servicio correcto. (Yo solo repito los cheques completos.)

En el paso 8, el ayudante ya ha comprobado que el otro extremo del zócalo está ejecutando el binario que debería estar ejecutando. Al enviarle una cookie aleatoria que debe enviar, significa que el otro extremo no puede haber “rellenado” el socket con los mensajes de antemano. Por supuesto, esto supone que el atacante no puede adivinar el número pseudoaleatorio de antemano. Si desea tener cuidado, puede leer una cookie adecuada de /dev/random , pero recuerde que es un recurso limitado (puede bloquearse si no hay suficiente aleatoriedad disponible para el kernel). Personalmente, acabo de leer, digamos 1024 bits (128 bytes) de /dev/urandom , y uso eso.

En este punto, el ayudante ha comprobado que el otro extremo del par de sockets es su daemon de servicio, y el ayudante puede confiar en los mensajes de control en la medida en que puede confiar en el daemon de servicio. (Supongo que este es el único mecanismo que el demonio de servicio generará los procesos de usuario; de lo contrario, deberá volver a pasar las credenciales en cada mensaje adicional y volver a verificarlas cada vez que se encuentre en el ayudante).

Siempre que el daemon del servicio desee ejecutar un binario de usuario,

  1. Crea los tubos necesarios (uno para alimentar la entrada estándar al binario del usuario, uno para recuperar la salida estándar del binario del usuario)

  2. Envía un mensaje al ayudante que contiene

    • Identidad para ejecutar el binario como; nombres de usuario (y grupo) o UID y GID (s)
    • Camino al binario
    • Parámetros de línea de comando dados al binario
    • Un mensaje auxiliar que contiene los descriptores de archivo para los puntos finales binarios del usuario de las tuberías de datos

Cada vez que el ayudante recibe un mensaje así, se bifurca. En el secundario, reemplaza la entrada y salida estándar con los descriptores de archivo en el mensaje auxiliar, cambia la identidad con setresgid() y setresuid() y / o initgroups() , cambia el directorio de trabajo a algún lugar apropiado y ejecuta el binario del usuario. El proceso de ayuda principal cierra los descriptores de archivo en el mensaje auxiliar y espera el mensaje siguiente.

Si el ayudante sale cuando no va a haber más entrada desde el zócalo, entonces se cerrará automáticamente cuando salga el servicio.

Podría proporcionar algún código de ejemplo, si hay suficiente interés. Hay muchos detalles para corregir, por lo que el código es un poco tedioso de escribir. Sin embargo, correctamente escrito, es más seguro que, por ejemplo, Apache SuEXEC.