Permitir setuid en scripts de shell

185

El setuidbit 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 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 de shell ejecutables.

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 sudoersentrada para permitir ALLejecutar un script dado como el usuario que quiero que se ejecute, con el NOPASSWDfin de evitar la solicitud de contraseña. Las principales desventajas de eso es la necesidad de una sudoersentrada cada vez que quiero hacer esto, y la necesidad de que la persona que llama en sudo some-scriptlugar de solosome-script

Michael Mrozek
fuente

Respuestas:

203

Linux ignora el bit setuid¹ en todos los ejecutables interpretados (es decir, ejecutables que comienzan con una #!línea). Las preguntas frecuentes de comp.unix.questions explican 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 secuencias de comandos setuid, en Linux, deberá parchear el núcleo. A partir de los núcleos 3.x, creo que debe agregar una llamada a install_exec_credsla load_scriptfunción, antes de la llamada a open_exec, pero no lo he probado.


Setuid shebang

Hay una condición de carrera inherente a la forma en que #!se implementa shebang ( ):

  1. El núcleo abre el ejecutable y descubre que comienza con #!.
  2. El kernel cierra el ejecutable y abre el intérprete en su lugar.
  3. El kernel inserta la ruta del script a 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 núcleo 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 shebang setuid seguro utilizando una característica adicional: la ruta se refiere al archivo ya abierto en el descriptor de archivo N (por lo que la apertura es aproximadamente equivalente a ). Muchos sistemas unix (incluido Linux) tienen scripts setuid pero no./dev/fd/N/dev/fd/Ndup(N)/dev/fd

  1. El núcleo abre el ejecutable y descubre que comienza con #!. Digamos que el descriptor de archivo para el ejecutable es 3.
  2. El núcleo abre el intérprete.
  3. El núcleo inserta /dev/fd/3la lista de argumentos (como argv[1]) y ejecuta el intérprete.

La página de shebang de Sven Mascheck tiene mucha información sobre shebang a través de unidades, incluido el soporte de setuid .


Intérpretes setuid

Supongamos que ha logrado hacer que su programa se ejecute como root, ya sea porque su sistema operativo admite setbanid shebang o porque ha utilizado un contenedor binario nativo (como sudo). ¿Has abierto un agujero de seguridad? Tal vez . El problema aquí no es sobre programas interpretados o 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 (por ejemplo /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_PATHes 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 especialmente diseñado libc.soen $LD_LIBRARY_PATH(entre otras tácticas). Todos los sistemas sanos ignoran las LD_*variables 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, IFSy 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, de bytes 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 toma precauciones).

  • Perl es una notable excepción. Es explícitamente compatible con 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 envía 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 de Perlsec . Solía ​​ser que los scripts de perl setuid necesarios en #!/usr/bin/suidperl -wTlugar de #!/usr/bin/perl -wT, pero en la mayoría de los sistemas modernos, #!/usr/bin/perl -wTson suficientes.

Tenga en cuenta que el uso de un contenedor binario nativo no hace nada en sí mismo para evitar estos problemas . De hecho, puede hacer que la situación es peor , ya que podría evitar que el entorno de ejecución de detectar que se invoca con privilegios y sin pasar por su capacidad de configuración 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 env_resetopción está activada, que setenves apagado, y que env_filey env_keepsólo contienen las variables inocuos.


TL, DR:

  • Setuid shebang es inseguro pero generalmente ignorado.
  • Si ejecuta un programa con privilegios (ya sea a través de 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_resetopción).

¹ Esta discusión se aplica igualmente si sustituye "setgid" por "setuid"; ambos son ignorados por el kernel de Linux en los scripts

Gilles
fuente
2
@ Josh: los scripts de shell setuid seguros son posibles, pero solo si tanto el implementador del shell como el escritor del script son muy cuidadosos. En lugar de código nativo, recomiendo Perl, donde los implementadores se han encargado de que los guiones setuid sean seguros con poco esfuerzo por parte del escritor de guiones.
Gilles
3
aparentemente, el suidperlmaterial ha sido desaprobado y marcado para su eliminación durante años (pero persiste de todos
modos
77
En realidad suidperlse ha eliminado a partir de perl 5.11 (5.12 estable): perl5110delta:> "suidperl" se ha eliminado. Solía ​​proporcionar un mecanismo para emular bits de permisos setuid en sistemas que no lo admiten correctamente. perl5120delta:> "suidperl" ya no es parte de Perl. Solía ​​proporcionar un mecanismo para emular bits de permisos setuid en sistemas que no lo admiten correctamente.
Randy Stauner
55
También tenga en cuenta esta línea de perl 5.6.1 docs (hace casi una década) ... perl561delta:> Tenga en cuenta que suidperl no está construido ni instalado por defecto en ninguna versión reciente de perl. El uso de suidperl es altamente desaconsejado . Si cree que lo necesita, pruebe primero alternativas como sudo. Ver courtesan.com/sudo .
Randy Stauner
3
No entiendo: esto parece ser una explicación de las razones de los problemas, pero ¿hay una respuesta real a la pregunta del OP aquí? ¿Hay alguna manera de decirle a mi sistema operativo que ejecute scripts de shell setuid?
Tom
55

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ías 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 debe codificar la ruta del script o pasarla como argumento de línea de comando al exe anterior.

Hemant
fuente
25
Aconsejaría en contra de la sugerencia de permitir el paso del script como argumento de línea de comando, ya que eso esencialmente le da a cualquiera que pueda ejecutar el programa la capacidad de ejecutar cualquier script como ese usuario definido.
dsp
40
Tenga en cuenta que ESTO ES INSEGURO incluso si la ruta completa al script está codificada. El shell heredará variables del entorno, y muchas de ellas permiten que el invocador inyecte código arbitrario. PATHy LD_LIBRARY_PATHson vectores obvios. Algunas conchas ejecutar $ENVo $BASHENV, o ~/.zshenvincluso antes de que comience a ejecutar el guión adecuado, por lo que no puede protegerse de estos en todo desde el guión. La única forma segura de invocar un script de shell con privilegios es limpiar el entorno . Sudo sabe cómo hacerlo de manera segura. Así que no escriba su propio contenedor, use sudo .
Gilles
28
Me siento mal porque de repente se le ha rechazado por esto: dije específicamente que también quería escuchar versiones inseguras, e imaginaba un ejecutable que tomó un argumento de script de shell cuando lo dije. Obviamente es muy inseguro, pero quería saber qué posibilidades existen
Michael Mrozek
8
@Gilles: FYI, Linux se desarma LD_LIBRARY_PATHentre otras cosas cuando encuentra el bit setuid.
Grawity
3
En lugar de usar system, puede encontrar más simple (y más eficiente) usar uno de la execfamilia, lo más probable execve. De esa manera, no crea un nuevo proceso ni inicia un shell, y puede reenviar argumentos (suponiendo que su script privilegiado pueda manejar argumentos de manera segura).
Toby Speight
23

Prefiero algunos scripts que están en este barco así:

#!/bin/sh
[ "root" != "$USER" ] && exec sudo $0 "$@"

Tenga en cuenta que esto no se usa setuidsino que simplemente ejecuta el archivo actual con sudo.

rcrowley
fuente
55
Esto no usa setuid pero solo le da un sudoaviso. (Para mí, el objetivo de setuid es permitir que las cosas se ejecuten como root sin necesidad de sudo.)
Luc
12

Si desea evitar llamar sudo some_script, simplemente puede 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:

  • Los shells son grandes piezas de software que interactúan fuertemente con el usuario. Es casi imposible verificar todo correctamente, especialmente porque la mayoría del código no está destinado a ejecutarse en ese modo.
  • Las secuencias de comandos son en su mayoría una solución rápida y sucia y, por lo general, no están preparadas con tanto cuidado como para permitir setuid. Tienen muchas características potencialmente peligrosas.
  • Dependen en gran medida de otros programas. No es suficiente que se haya verificado el shell. sed, awketc. también necesitarían ser verificados

Tenga en cuenta que sudoproporciona 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 pingnecesita manipular la red, no necesita tener acceso a los archivos. Sin embargo, no estoy seguro si se heredan.

Maciej Piechotka
fuente
2
Supongo que /ust/bin/envdebería ser /usr/bin/env.
Bob
4

Puede crear un alias para sudo + el nombre del script. Por supuesto, eso es aún más trabajo para configurar, ya que también debe configurar un alias, pero le ahorra tener que escribir sudo.

Pero si no le importan los horribles riesgos de seguridad, use un shell setuid como intérprete para el script de shell. No sé si eso funcionará para ti, pero supongo que podría funcionar.

Sin embargo, permítanme decir que les aconsejo no hacerlo. Solo lo menciono con fines educativos ;-)

wzzrd
fuente
55
Funcionará. Horriblemente como dijiste. El bit SETUID permite la ejecución con el derecho del propietario. El shell Setuid (a menos que haya sido diseñado para funcionar con setuid) se ejecutará como root para cualquier usuario. Es decir, cualquiera puede ejecutar rm -rf /(y otros comandos de la serie NO LO HAGAS EN CASA ).
Maciej Piechotka
99
@MaciejPiechotka por NO LO HAGAS EN CASA ¿ Quieres decir que puedes hacerlo en el trabajo? :)
Peter
4

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. Se pretende que sea 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 otorga 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 ordinarios 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 enlazar.)

http://www.ucolick.org/~will/RUE/super/README

http://manpages.ubuntu.com/manpages/utopic/en/man1/super.1.html

Nizam Mohamed
fuente
Hola y bienvenidos al sitio! Esperamos que las respuestas sean más detalladas aquí. ¿Podría editar su respuesta y explicar qué es este programa y cómo podría ayudar a resolver el problema del OP?
terdon
Gracias Nizam, por horas tratando de encontrar algo como super para que otro usuario pueda ejecutar cuentas como el usuario del archivo.
Chad
2

Si por alguna razón sudono 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 setuidcon chmod 4511 wrapper_script.

Esto es similar a otra respuesta publicada, pero ejecuta el script con un entorno limpio y usa explícitamente en /bin/bashlugar 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 usarlas sudo.

Obviamente, desea asegurarse de que el script solo se pueda escribir de raíz.

Chris
fuente
-3

Primero encontré esta pregunta, no convencido por todas las respuestas, aquí hay una mucho mejor, que también le permite ofuscar completamente su script de bash, si está tan inclinado.

Debería explicarse por sí mismo.

#include <string>
#include <unistd.h>

template <typename T, typename U>
T &replace (
          T &str, 
    const U &from, 
    const U &to)
{
    size_t pos;
    size_t offset = 0;
    const size_t increment = to.size();

    while ((pos = str.find(from, offset)) != T::npos)
    {
        str.replace(pos, from.size(), to);
        offset = pos + increment;
    }

    return str;
}

int main(int argc, char* argv[])
{
    // Set UUID to root
    setuid(0);

    std::string script = 
R"(#!/bin/bash
whoami
echo $1
)";

    // Escape single quotes.
    replace(script, std::string("'"), std::string("'\"'\"'"));

    std::string command;
    command = command + "bash -c '" + script + "'"; 

    // Append the command line arguments.
    for (int a = 0; a < argc; ++a)
    {
        command = command + " " + argv[a];
    }

    return system(command.c_str());
}

Entonces corres

g++ embedded.cpp -o embedded
sudo chown root embedded
sudo chmod u+s embedded
Theodore R. Smith
fuente
¿Por qué los votos negativos?
Theodore R. Smith
2
Probablemente porque es una solución demasiado complicada que manipula cadenas y tiene un código de shell incrustado. O haces un lanzador simple o usas sudo. Esta es la peor de todas las opciones.
siride