¿Cuál es la forma más segura de escribir mediante programación en un archivo con privilegios de root?

35

Una gran aplicación necesita, en un momento específico, realizar una pequeña cantidad de escrituras en un archivo que requiere permisos de root. No es realmente un archivo, sino una interfaz de hardware que está expuesta a Linux como un archivo.

Para evitar otorgar privilegios de root a toda la aplicación, escribí un script bash que realiza las tareas críticas. Por ejemplo, el siguiente script habilitará el puerto 17 de la interfaz de hardware como salida:

echo "17" > /sys/class/gpio/export
echo "out" > /sys/class/gpio/gpio17/direction

Sin embargo, como suidestá deshabilitado para los scripts de bash en mi sistema, me pregunto cuál es la mejor manera de lograrlo.

  1. Use alguna solución presentada aquí

  2. Llame al script sudodesde la aplicación principal y edite la lista de sudoers en consecuencia, para evitar requerir una contraseña al llamar al script. Me incomoda un poco dar privilegios de sudo echo.

  3. Simplemente escriba un programa en C, con fprintf, y configúrelo en suid root. Codifique las cadenas y los nombres de archivo y asegúrese de que solo root pueda editarlo. O lea las cadenas de un archivo de texto, de manera similar, asegurándose de que nadie pueda editar el archivo.

  4. ¿Alguna otra solución que no se me ocurrió y es más segura o más simple que las presentadas anteriormente?

vsz
fuente
10
¿Por qué no simplemente inicia su programa con privilegios de root, abre el archivo y suelta privilegios? Así es como cada servidor web o tal lo hace para sockets. Efectivamente, no se está ejecutando como root, ni tampoco es necesario un ayudante que lo haga.
Damon

Respuestas:

33

No necesita dar sudoacceso a echo. De hecho, eso no tiene sentido porque, por ejemplo, con sudo echo foo > bar, la redirección se realiza como el usuario original, no como root.

Llame al script pequeño con sudo, permitiendo el NOPASSWD:acceso a SOLO ese script (y cualquier otro script similar) por el usuario (s) que necesitan acceso a él.

Esta es siempre la mejor / más segura forma de usar sudo. Aísle la pequeña cantidad de comandos que necesitan privilegios de root en sus propios scripts separados y permita que el usuario no confiable o parcialmente confiable solo ejecute ese script como root.

El sudoscript (s) pequeño -o no debe tomar argumentos (o entradas) del usuario (es decir, cualquier otro programa que llame debe tener opciones codificadas y argumentos) o debe validar con mucho cuidado cualquier argumento / entrada que tenga que aceptar del usuario

Sea paranoico en la validación: en lugar de buscar cosas 'conocidas malas' para excluir, permita solo cosas 'conocidas buenas' y anule cualquier desajuste o error o cualquier cosa remotamente sospechosa.

La validación debe ocurrir tan pronto como sea posible en el script (preferiblemente antes de que haga otra cosa como root).


Yo realmente debería haber mencionado esto cuando empecé a escribir esta respuesta, pero si el script es un script de shell que DEBO citar correctamente todas las variables. Tenga especial cuidado en citar variables que contengan datos proporcionados por el usuario de alguna manera, pero no asuma que algunas variables son seguras, COTIZAR TODAS .

Eso incluye variables de entorno potencialmente controladas por el usuario (p "$PATH". Ej . "$HOME", "$USER"Etc. Y definitivamente incluye "$QUERY_STRING"y "HTTP_USER_AGENT"etc. en un script CGI). De hecho, solo cítelos a todos. Si tiene que construir una línea de comando con múltiples argumentos, use una matriz para construir la lista de argumentos y cite eso - "${myarray[@]}".

¿Ya he dicho "citarlos a todos" con bastante frecuencia? recuerdalo. hazlo.

cas
fuente
18
Olvidó mencionar que el script en sí debería ser propiedad de root, con permisos 500.
Comodín
13
Al menos elimine los permisos de escritura, por amor de Dios. Ese fue realmente mi punto. El resto solo se está endureciendo.
Comodín el
10
Un programa en C cuidadosamente escrito tendrá una superficie de ataque más pequeña que un script de shell.
user253751
77
@immibis, posiblemente. Pero tomará mucho más tiempo escribir y depurar que un script de shell. Y requiere tener un compilador de C (que está prohibido en algunos servidores de producción para reducir el riesgo de seguridad al dificultar que los atacantes compilen exploits). Además, es menos probable que un script de shell escrito por un programador o programador de nivel intermedio o novato sea explotable que un programa C escrito por alguien de habilidad similar, especialmente si tiene que aceptar y validar datos proporcionados por el usuario.
cas
3
@JonasWielicki: creo que ahora estamos bien y verdaderamente en el ámbito de la opinión en lugar de los hechos. Puede hacer argumentos válidos para que Shell o C (o perl o python o awk, etc.) sean más o menos propensos a errores explotables. Cuando, realmente, depende principalmente de la habilidad del programador y la atención al detalle (y cansancio, prisa, precaución, etc.). Sin embargo, es un hecho que los lenguajes de nivel inferior tienden a requerir que se escriba más código para lograr lo que se puede hacer en muchas menos líneas de código en lenguajes de nivel superior ... y cada LoC es otra oportunidad para un error que se cometerá.
cas
16

Verifique el propietario en los archivos gpio:

ls -l /sys/class/gpio/

Lo más probable es que descubras que son propiedad del grupo gpio:

-rwxrwx--- 1 root     gpio     4096 Mar  8 10:50 export
...

En ese caso, simplemente puede agregar su usuario al gpiogrupo para otorgar acceso sin sudo:

sudo usermod -aG gpio myusername

Deberá cerrar sesión y volver a iniciarla después de eso para que el cambio surta efecto.

jpa
fuente
No funciona De hecho, el propietario del grupo de todo en /sys/class/gpio/gpio es, pero incluso después de agregarme a ese grupo, todavía recibo "permiso denegado" cada vez que intento escribir algo allí.
vsz
1
El problema es que los archivos en /sys/class/gpio/realidad son solo enlaces simbólicos a /sys/devices/platform/soc/<some temporary name>/gpiodonde tanto el propietario como el grupo son root.
vsz
44
@vsz ¿Lo intentaste chgrp gpio /sys/devices/platform/soc/*/gpio? Quizás algo así podría colocarse en un archivo de inicio.
jpa
Sí, pero no es tan simple. Como siempre se generan de una manera diferente, tuve que usar algo comochgrp gpio `readlink -f /sys/class/gpio/gpio18`/*
vsz
7

Una solución para esto (usada particularmente en el escritorio de Linux pero también aplicable en otros casos) es usar D-Bus para activar un pequeño servicio que se ejecuta como root y polkit para hacer la autorización. Esto es fundamentalmente para lo que fue diseñado polkit ; de su documentación introductoria :

polkit proporciona una API de autorización destinada a ser utilizada por programas privilegiados ("MECANISMOS") que ofrece servicio a programas no privilegiados ("CLIENTES"). Consulte la página del manual de Polkit para ver la arquitectura del sistema y el panorama general.

En lugar de ejecutar su programa de ayuda, el programa grande y sin privilegios enviaría una solicitud en el bus. Su ayudante podría estar ejecutándose como un demonio iniciado en el arranque del sistema o, mejor aún, ser activado según sea necesario por systemd . Luego, ese ayudante usaría polkit para verificar que la solicitud proviene de un lugar autorizado. (O, en este caso, si eso parece excesivo, podría usar algún otro mecanismo de autenticación / autorización codificado).

Encontré un buen artículo básico sobre comunicación a través de D-Bus , y aunque no lo he probado, este parece ser un ejemplo básico de agregar polkit a la mezcla .

En este enfoque, nada necesita ser marcado como setuid.

mattdm
fuente
5

Una forma de hacer esto es hacer un programa setuid-root escrito en C que solo haga lo que se necesita y nada más. En su caso, no tiene que mirar ninguna entrada del usuario.

#include <unistd.h>
#include <string.h>
#include <stdio.h>  // for perror(3)
// #include ...  more stuff for open(2)

static void write_str_to_file(const char*fn, const char*str) {
    int fd = open(fn, O_WRONLY)
    if (-1 == fd) {
        perror("opening device file");  // make this a CPP macro instead of function so you can use string concat to get the filename into the error msg
        exit(1);
    }
    int err = write(fd, str, strlen(str));
    // ... error check
    err = close(fd);
    // ... error check
}

int main(int argc, char**argv) {
    write_string_to_file("/sys/class/gpio/export", "17");
    write_string_to_file("/sys/class/gpio/gpio17/direction", "out");
    return 0;
}

No hay forma de subvertir esto a través de variables de entorno ni nada, porque todo lo que hace es hacer un par de llamadas al sistema.

Desventaja: debe verificar el valor de retorno de cada llamada al sistema.

Al revés: la comprobación de errores es realmente fácil: si hay algún error, solo perrory rescatar: salga con un estado distinto de cero. Si hay un error, investigue con strace. No necesita este programa en sí mismo para dar mensajes de error realmente agradables.

Peter Cordes
fuente
2
Podría sentir la tentación de abrir ambos archivos antes de escribir cualquier cosa para proteger contra la ausencia del segundo. Y podría ejecutar este programa sudopara que no sea necesario configurarlo. Por cierto, es <fcntl.h>para open().
Jonathan Leffler
3

Bendice tee en lugar de echo para sudo es una forma común de abordar una situación en la que debes limitar las permanentes. La redirección a / dev / null es detener cualquier salida que se filtre: el tee hace lo que desea.

echo "17" | sudo tee /sys/class/gpio/export > /dev/null
echo "out" | sudo tee /sys/class/gpio/gpio17/direction > /dev/null
Miles Gillham
fuente
55
Si permite teeque se ejecute con sudo, está permitiendo que CUALQUIER archivo se sobrescriba o /etc/passwdanexe (incluyendo, por ejemplo, - solo agregue una nueva cuenta uid = 0). También puede permitir que se ejecuten todos los comandos sudo(BTW /etc/sudoerspertenece al conjunto de 'CUALQUIER archivo', por lo que puede sobrescribirse con sudo tee)
cas
2
Aparentemente, puede limitar los archivos en los que teepuede escribir, como se describe en esta respuesta en una pregunta separada. Solo asegúrese de leer los comentarios también, ya que algunas personas tuvieron problemas con la sintaxis original utilizada y sugieren soluciones a ese problema.
Alex
1
@alex, sí, puedes hacerlo con cualquier comando en sudo: restringe los argumentos permitidos. La configuración puede ser muy larga y complicada si desea permitirtee o lo que sea para operar en muchos archivos sudo.
cas
0

Puede hacer un script que realice su tarea deseada. Luego, cree una nueva cuenta de usuario que solo pueda iniciar sesión proporcionando una clave utilizada por OpenSSH.

Nota: Cualquiera podrá ejecutar ese script si tiene el archivo de clave, así que asegúrese de que el archivo de clave de OpenSSH no sea legible por nadie que desee evitar que realice la tarea.

En la configuración de OpenSSH (en el archivo autorizado_claves), antes de especificar la clave, especifique un comando (seguido de un espacio), como se describe en este texto de "ejemplo":

command="myscript" keydata optionalComment

Esta opción de configuración puede restringir esa clave OpenSSH para que solo ejecute un comando específico. Ahora tiene sudo otorgando los permisos, pero la configuración de OpenSSH es la pieza de la solución que realmente se está utilizando para restringir / limitar lo que ese usuario puede hacer, de modo que el usuario no esté ejecutando otros comandos. Esto tampoco tiene un archivo de configuración "sudo" tan complicado, así que si usa OpenBSD o si las nuevas "doas" ("do as") de OpenBSD comienzan a ser más populares (con futuras versiones de cualquier sistema operativo que use) , no necesitará ser desafiado por mucha complejidad en la configuración de sudo.

TOOGAM
fuente