El bit de permiso setuid
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 solo 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
Wikipedia dice :
Debido a la mayor probabilidad de fallas de seguridad, muchos sistemas operativos ignoran el atributo setuid cuando se aplican a los scripts ejecutables de Shell.
Suponiendo que estoy dispuesto a aceptar esos riesgos, ¿hay alguna forma de decirle a Linux que trate el bit setuid de la misma manera en los scripts de Shell que en los ejecutables?
Si no, ¿hay una solución común para este problema? Mi solución actual es agregar una entrada sudoers
para permitir que ALL
ejecute un script dado como el usuario que quiero ejecutar, con NOPASSWD
para evitar la solicitud de contraseña. La desventaja principal de esto es la necesidad de una entrada sudoers
cada vez que quiero hacer esto, y la necesidad de que la persona que llama a Sudo some-script
en lugar de solo some-script
Linux ignora el bit setuid¹ en todos los ejecutables interpretados (es decir, ejecutables que comienzan con una línea #!
). El comp.unix.questions FAQ explica 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 núcleos 3.x, creo que debe agregar una llamada a install_exec_creds
en la función load_script
, antes de la llamada a open_exec
, Pero no lo he probado.
Hay una condición de carrera inherente a la forma en que se implementa Shebang (#!
):
#!
.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 equipos ignoran el bit setuid cuando detectan un Shebang.
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 solo desvincular o sobrescribir el archivo, sino también renombrar 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 sea 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) implementan setbanid seguro Shebang usando un adicional característica: la ruta /dev/fd/N
se refiere al archivo ya abierto en el descriptor de archivo [~ # ~] n [~ # ~] (¡entonces abrir /dev/fd/N
es aproximadamente equivalente a dup(N)
). Muchos sistemas unix (incluido Linux) tienen /dev/fd
Pero no scripts setuid.
#!
. Digamos que el descriptor de archivo para el ejecutable es 3./dev/fd/3
La lista de argumentos (como argv[1]
), Y ejecuta el intérprete.La página Shebang de Sven Mascheck tiene mucha información sobre Shebang a través de unidades, incluyendo soporte de setuid .
Supongamos que ha logrado hacer que su programa se ejecute como root, ya sea porque su sistema operativo admite Shebang setuid o porque ha utilizado un contenedor binario nativo (como Sudo
). ¿Has abierto un agujero de seguridad? Quizás . El problema aquí es ¡no sobre programas interpretados vs compilados. El problema es si su sistema de tiempo de ejecución se comporta de manera segura si se ejecuta con privilegios.
Cualquier ejecutable binario nativo vinculado dinámicamente es interpretado de alguna manera por el cargador dinámico (ej. /lib/ld.so
), Que carga las bibliotecas dinámicas requeridas por el programa. En muchos dispositivos, puede configurar la ruta de búsqueda para 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 un libc.so
Especialmente diseñado en $LD_LIBRARY_PATH
(Entre otras tácticas). Todos los sistemas sanos ignoran las variables LD_*
En los ejecutables setuid.
En shells como 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 para ejecutar código arbitrario en el contexto de los scripts de Shell. Algunos shells establecen estas variables en valores predeterminados razonables si detectan que el script ha sido invocado con privilegios, pero no sé si hay alguna implementación en particular en la que pueda confiar.
La mayoría de los entornos de tiempo de ejecución (ya sean nativos, bytecode o interpretados) 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 elegante que el enlace dinámico (que sí toma precauciones).
Perl es una notable excepción. Es explícitamente admite scripts setuid de forma segura. De hecho, su script puede ejecutar setuid incluso si su sistema operativo ignoró el bit setuid en los scripts. Esto se debe a que Perl se entrega con un ayudante raíz setuid que realiza las comprobaciones necesarias y revoca al intérprete en los scripts deseados con los privilegios deseados. Esto se explica en el manual perlsec . Solía ser que los scripts de Perl setuid necesitaban #!/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 el uso de un contenedor binario nativo no hace nada en sí mismo para evitar estos problemas . De hecho, puede empeorar la situación ¡peor, porque podría evitar que su entorno de tiempo 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 contenedor desinfecta el entorno . El script debe tener cuidado de no hacer demasiadas suposiciones (por ejemplo, sobre el directorio actual), pero esto continúa. Puede usar Sudo para esto siempre que esté configurado para desinfectar el medio ambiente. Las variables de la lista negra son propensas a errores, por lo que siempre debe incluirlas en la lista blanca. Con Sudo, asegúrese de que la opción env_reset
Esté activada, que setenv
esté desactivada y que env_file
Y env_keep
Solo contengan variables inocuas.
TL, DR:
env_reset
).¹ Esta discusión se aplica igualmente si sustituye "setgid" por "setuid"; ambos son ignorados por el kernel de Linux en los scripts
Una forma de resolver este problema es llamar al script de Shell desde un programa que puede 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ía poder ejecutarlo, y verá que su script se ejecuta sin permisos de nadie.
Pero aquí también necesita codificar la ruta del script o pasarla como argumento de línea de comando al exe anterior.
Prefiero algunos scripts que están en este barco así:
#!/bin/sh
[ "root" != "$USER" ] && exec Sudo $0 "[email protected]"
Tenga en cuenta que esto no usa setuid
sino que simplemente ejecuta el archivo actual con Sudo
.
Si quieres evitar llamar a Sudo some_script
solo puedes hacer:
#!/ust/bin/env sh
Sudo /usr/local/scripts/your_script
Los programas SETUID deben diseñarse con extremo cuidado ya que se ejecutan con privilegios de root y los usuarios tienen un gran control sobre ellos. Necesitan comprobar la cordura de todo. No puede hacerlo con scripts porque:
sed
, awk
, etc. también tendrían que verificarseTenga en cuenta que Sudo
proporciona algunas comprobaciones de cordura, pero no es suficiente: verifique cada línea en su propio código.
Como última nota: considere usar capacidades. Le permiten otorgar a un proceso que se ejecuta como usuario privilegios especiales que normalmente requerirían privilegios de root. Sin embargo, por ejemplo, aunque ping
necesita manipular la red, no necesita tener acceso a los archivos. Sin embargo, no estoy seguro si se heredan.
comando super [-r reqpath] [args]
Super permite que usuarios específicos ejecuten scripts (u otros comandos) como si fueran root; o puede establecer los grupos uid, gid y/o suplementarios por comando antes de ejecutar el comando. Está destinado a ser una alternativa segura a la creación de scripts de raíz setuid. Super también permite a los usuarios comunes proporcionar comandos para que otros los ejecuten; estos se ejecutan con el uid, gid y grupos del usuario que ofrece el comando.
Super consulta un archivo `` super.tab '' para ver si el usuario puede ejecutar el comando solicitado. Si se concede el permiso, super ejecutará pgm [args], donde pgm es el programa asociado con este comando. (La ejecución de raíz está permitida de manera predeterminada, pero aún se puede denegar si una regla excluye la raíz. Los usuarios normales no pueden ejecutar de manera predeterminada).
Si el comando es un enlace simbólico (o también un enlace duro) al superprograma, escribir% command args es equivalente a escribir% super command args (El comando no debe ser super o super no reconocerá que se invoca a través de un enlace.)
http://www.ucolick.org/~will/RUE/super/README
http://manpages.ubuntu.com/manpages/utopic/en/man1/super.1.html
Puede crear un alias para Sudo + el nombre del script. Por supuesto, eso es aún más trabajo de configurar, ya que también debes configurar un alias, pero te ahorra tener que escribir Sudo.
Pero si no le importan los horribles riesgos de seguridad, use un setuid Shell como intérprete para el script de Shell. No sé si eso funcionará para ti, pero supongo que podría funcionar.
Permítanme decir que desaconsejo hacer esto, sin embargo. Solo lo menciono con fines educativos ;-)
Si por alguna razón Sudo
no está disponible, puede escribir un script de envoltura delgada 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 compila, configúrelo como setuid
con chmod 4511 wrapper_script
.
Esto es similar a otra respuesta publicada, pero ejecuta el script con un entorno limpio y usa explícitamente /bin/bash
En lugar del Shell llamado por system()
, y cierra algunos posibles agujeros de seguridad.
Tenga en cuenta que esto descarta el entorno por completo. Si desea usar algunas variables ambientales sin abrir vulnerabilidades, realmente solo necesita usar Sudo
.
Obviamente, desea asegurarse de que el script solo se pueda escribir de raíz.