
El setuid
bit de permiso le dice a Linux que ejecute un programa con la identificación de usuario efectiva del propietario en lugar del ejecutor:
> cat setuid-test.c
#include <stdio.h>
#include <unistd.h>
int main(int argc, char** argv) {
printf("%d", geteuid());
return 0;
}
> gcc -o setuid-test setuid-test.c
> ./setuid-test
1000
> sudo chown nobody ./setuid-test; sudo chmod +s ./setuid-test
> ./setuid-test
65534
Sin embargo, esto sólo se aplica a los ejecutables; Los scripts de Shell ignoran el bit setuid:
> cat setuid-test2
#!/bin/bash
id -u
> ./setuid-test2
1000
> sudo chown nobody ./setuid-test2; sudo chmod +s ./setuid-test2
> ./setuid-test2
1000
Wikipediadice:
Debido a la mayor probabilidad de fallos de seguridad, muchos sistemas operativos ignoran el atributo setuid cuando se aplica a scripts de shell ejecutables.
Suponiendo que estoy dispuesto a aceptar esos riesgos, ¿hay alguna manera de decirle a Linux que trate el bit setuid de la misma manera en los scripts de shell que en los ejecutables?
Si no es así, ¿existe una solución común para este problema? Mi solución actual es agregar una sudoers
entrada que permita ALL
ejecutar un script determinado como el usuario con el que quiero que se ejecute, para NOPASSWD
evitar que se solicite la contraseña. La principal desventaja de esto es la necesidad de una sudoers
entrada cada vez que quiero hacer esto, y la necesidad de que la persona que llama, sudo some-script
en lugar de simplementesome-script
Respuesta1
Linux ignora el bit setuid¹ en todos los ejecutables interpretados (es decir, ejecutables que comienzan con una #!
línea). ElPreguntas frecuentes sobre comp.unix.questionsexplica los problemas de seguridad con los scripts de shell setuid. Estos problemas son de dos tipos: relacionados con shebang y relacionados con shell; Entro en más detalles a continuación.
Si no le importa la seguridad y desea permitir scripts setuid, en Linux, necesitará parchear el kernel. A partir de los kernels 3.x, creo que es necesario agregar una llamada ainstall_exec_creds
en elload_script
función, antes de la llamada a open_exec
, pero no lo he probado.
asunto setuid
Existe una condición de carrera inherente a la forma en que #!
normalmente se implementa shebang ():
- El kernel abre el ejecutable y descubre que comienza con
#!
. - El kernel cierra el ejecutable y en su lugar abre el intérprete.
- El kernel inserta la ruta al script en la lista de argumentos (como
argv[1]
) y ejecuta el intérprete.
Si se permiten scripts setuid con esta implementación, un atacante puede invocar un script arbitrario creando un enlace simbólico a un script setuid existente, ejecutándolo y organizando el cambio del enlace después de que el kernel haya realizado el paso 1 y antes de que el intérprete llegue a abriendo su primer argumento. Por esta razón,la mayoría de los unices ignoran el bit setuidcuando detectan un tinglado.
Una forma de asegurar esta implementación sería que el kernel bloquee el archivo de script hasta que el intérprete lo haya abierto (tenga en cuenta que esto debe evitar no sólo desvincular o sobrescribir el archivo, sino también cambiar el nombre de cualquier directorio en la ruta). Pero los sistemas Unix tienden a evitar los bloqueos obligatorios, y los enlaces simbólicos harían que una función de bloqueo correcta fuera especialmente difícil e invasiva. No creo que nadie lo haga de esta manera.
Algunos sistemas Unix (principalmente OpenBSD, NetBSD y Mac OS X, todos los cuales requieren que se habilite una configuración del kernel) implementanseguro setuid shebangusando una característica adicional: la ruta se refiere al archivo ya abierto en el descriptor de archivo/dev/fd/N
norte(por lo que la apertura es aproximadamente equivalente a ). Muchos sistemas Unix (incluido Linux) tienen scripts setuid, pero no./dev/fd/N
dup(N)
/dev/fd
- El kernel abre el ejecutable y descubre que comienza con
#!
. Digamos que el descriptor de archivo del ejecutable es 3. - El núcleo abre el intérprete.
- El kernel inserta
/dev/fd/3
la lista de argumentos (comoargv[1]
) y ejecuta el intérprete.
La página de Sven Maschecktiene mucha información sobre shebang en unices, incluidosoporte setuid.
Intérpretes setuidos
Supongamos que ha logrado que su programa se ejecute como root, ya sea porque su sistema operativo admite setuid shebang o porque ha utilizado un contenedor binario nativo (como sudo
). ¿Has abierto un agujero de seguridad?Tal vez. El problema aquí esnosobre programas interpretados versus programas compilados. La cuestión es si susistema de ejecuciónse comporta de forma segura si se ejecuta con privilegios.
Cualquier ejecutable binario nativo vinculado dinámicamente es interpretado de alguna manera por elcargador dinámico(p. ej.
/lib/ld.so
), que carga las bibliotecas dinámicas requeridas por el programa. En muchos Unices, puede configurar la ruta de búsqueda de bibliotecas dinámicas a través del entorno (LD_LIBRARY_PATH
es un nombre común para la variable de entorno) e incluso cargar bibliotecas adicionales en todos los archivos binarios ejecutados (LD_PRELOAD
). El invocador del programa puede ejecutar código arbitrario en el contexto de ese programa colocando unlibc.so
código especialmente diseñado$LD_LIBRARY_PATH
(entre otras tácticas). Todos los sistemas sensatos ignoran lasLD_*
variables en los ejecutables setuid.EnconchasComo sh, csh y derivados, las variables de entorno se convierten automáticamente en parámetros de shell. A través de parámetros como
PATH
,IFS
y muchos más, el invocador del script tiene muchas oportunidades de ejecutar código arbitrario en el contexto del script de shell. Algunos shells configuran estas variables con valores predeterminados sensatos si detectan que el script ha sido invocado con privilegios, pero no sé si existe alguna implementación particular en la que pueda confiar.La mayoría de los entornos de ejecución(ya sea nativo, de código de bytes o interpretado) tienen características similares. Pocos toman precauciones especiales en los ejecutables setuid, aunque los que ejecutan código nativo a menudo no hacen nada más sofisticado que el enlace dinámico (que sí toma precauciones).
perlaes una notable excepción. Éladmite explícitamente scripts setuidde forma segura. De hecho, su secuencia de comandos puede ejecutar setuid incluso si su sistema operativo ignoró el bit setuid en las secuencias de comandos. Esto se debe a que Perl viene con un asistente raíz setuid que realiza las comprobaciones necesarias y vuelve a invocar al intérprete en los scripts deseados con los privilegios deseados. Esto se explica en elperlsecmanual. Solía ser que se necesitaban scripts setuid perl
#!/usr/bin/suidperl -wT
en lugar de#!/usr/bin/perl -wT
, pero en la mayoría de los sistemas modernos,#!/usr/bin/perl -wT
es suficiente.
Tenga en cuenta que usar un binario nativoEl contenedor no hace nada por sí solo para prevenir estos problemas.. De hecho, puede hacer que la situaciónpeor, porque podría impedir que su entorno de ejecución detecte que se invoca con privilegios y omita su configurabilidad de tiempo de ejecución.
Un contenedor binario nativo puede hacer que un script de shell sea seguro si el contenedordesinfecta el medio ambiente. El script debe tener cuidado de no hacer demasiadas suposiciones (por ejemplo, sobre el directorio actual), pero así es. Puedes usar sudo para esto siempre que esté configurado para desinfectar el ambiente. Incluir variables en la lista negra es propenso a errores, por lo que siempre debe incluirse en la lista blanca. Con sudo, asegúrese de que la env_reset
opción esté activada, desactivada setenv
y que env_file
solo env_keep
contenga variables inocuas.
TL,DR:
- Setuid shebang es inseguro pero generalmente se ignora.
- Si ejecuta un programa con privilegios (ya sea mediante sudo o setuid), escriba código nativo o perl, o inicie el programa con un contenedor que desinfecte el entorno (como sudo con la
env_reset
opción).
¹ Esta discusión se aplica igualmente si sustituye “setgid” por “setuid”;Ambos son ignorados por el kernel de Linux en los scripts.
Respuesta2
Una forma de resolver este problema es llamar al script de shell desde un programa que pueda usar el bit setuid.
es algo así como sudo. Por ejemplo, así es como lograría esto en un programa en C:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
setuid( 0 ); // you can set it at run time also
system( "/home/pubuntu/setuid-test2.sh" );
return 0;
}
Guárdelo como setuid-test2.c.
compilar
Ahora haga el setuid en este programa binario:
su - nobody
[enter password]
chown nobody:nobody a.out
chmod 4755 a.out
Ahora deberías poder ejecutarlo y verás que tu script se ejecuta sin permisos de nadie.
Pero aquí también es necesario codificar la ruta del script o pasarla como línea de comando arg al exe anterior.
Respuesta3
Prefijo algunos scripts que se encuentran en este barco de la siguiente manera:
#!/bin/sh
[ "root" != "$USER" ] && exec sudo $0 "$@"
Tenga en cuenta que esto no utiliza setuid
sino que simplemente ejecuta el archivo actual con sudo
.
Respuesta4
Si por alguna razón sudo
no está disponible, puede escribir un script contenedor delgado en C:
#include <unistd.h>
int main() {
setuid(0);
execle("/bin/bash","bash","/full/path/to/script",(char*) NULL,(char*) NULL);
}
Y una vez que lo compilas, configúralo como setuid
con chmod 4511 wrapper_script
.
Esto es similar a otra respuesta publicada, pero ejecuta el script con un entorno limpio y lo usa explícitamente /bin/bash
en lugar del shell llamado por system()
, por lo que cierra algunos posibles agujeros de seguridad.
Tenga en cuenta que esto descarta el medio ambiente por completo. Si desea utilizar algunas variables ambientales sin abrir vulnerabilidades, en realidad solo necesita usar sudo
.
Obviamente, desea asegurarse de que el script en sí solo pueda escribirlo el usuario root.