El bit setuid se puede establecer en un archivo ejecutable para que, cuando se ejecute, el programa tenga los privilegios del propietario del archivo en lugar del usuario real, si son diferentes. Esta es la diferencia entre uid efectivo (ID de usuario) y uid real .
Algunas utilidades comunes, tales como passwd
, son propiedad de root y se configuran de esta manera por necesidad ( passwd
necesita acceso /etc/shadow
que solo puede leerse desde root).
La mejor estrategia al hacer esto es hacer lo que sea necesario como superusuario de inmediato y luego reducir los privilegios para que sea menos probable que ocurran errores o mal uso mientras se ejecuta root. Para hacer esto, establezca el uid efectivo del proceso en su uid real. En POSIX C:
#define _POSIX_C_SOURCE 200112L // Needed with glibc (e.g., linux).
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
void report (uid_t real) {
printf (
"Real UID: %d Effective UID: %d\n",
real,
geteuid()
);
}
int main (void) {
uid_t real = getuid();
report(real);
seteuid(real);
report(real);
return 0;
}
Las funciones relevantes, que deberían tener un equivalente en la mayoría de los lenguajes de nivel superior si se usan comúnmente en los sistemas POSIX:
getuid()
: Obtén el uid real .
geteuid()
: Obtenga el uid efectivo .
seteuid()
: Establece el uid efectivo .
No puede hacer nada con el último inapropiado para el uid real, excepto en la medida en que el bit setuid esté configurado en el ejecutable . Para probar esto, compile gcc test.c -o testuid
. Entonces necesita, con privilegios:
chown root testuid
chmod u+s testuid
El último establece el bit setuid. Si ahora se ejecuta ./testuid
como un usuario normal, verá que el proceso se ejecuta por defecto con uid 0, root efectivo.
¿Qué pasa con los guiones?
Esto varía de una plataforma a otra , pero en Linux, las cosas que requieren un intérprete, incluido el código de bytes, no pueden usar el bit setuid a menos que esté configurado en el intérprete (lo cual sería muy, muy estúpido). Aquí hay una secuencia de comandos perl simple que imita el código C anterior:
#!/usr/bin/perl
use strict;
use warnings FATAL => qw(all);
print "Real UID: $< Effective UID: $>\n";
$> = $<; # Not an ASCII art greedy face, but genuine perl...
print "Real UID: $< Effective UID: $>\n";
Fiel a sus raíces * nixy, perl ha incorporado variables especiales para uid ( $>
) y uid real ( $<
) efectivos . Pero si intenta lo mismo chown
y lo chmod
utiliza con el ejecutable compilado (de C, ejemplo anterior), no habrá ninguna diferencia. El script no puede obtener privilegios.
La respuesta a esto es usar un binario setuid para ejecutar el script:
#include <stdio.h>
#include <unistd.h>
int main (int argc, char *argv[]) {
if (argc < 2) {
puts("Path to perl script required.");
return 1;
}
const char *perl = "perl";
argv[0] = (char*)perl;
return execv("/usr/bin/perl", argv);
}
Compila esto gcc --std=c99 whatever.c -o perlsuid
, entonces chown root perlsuid && chmod u+s perlsuid
. Ahora puede ejecutar cualquier script perl con un uid efectivo de 0, independientemente de quién sea el propietario.
Una estrategia similar funcionará con php, python, etc. Pero ...
# Think hard, very important:
>_< # Genuine ASCII art "Oh tish!" face
POR FAVOR, NO DEJES este tipo de cosas por ahí . Lo más probable es que realmente desee compilar el nombre del script como una ruta absoluta , es decir, reemplazar todo el código main()
con:
const char *args[] = { "perl", "/opt/suid_scripts/whatever.pl" }
return execv("/usr/bin/perl", (char * const*)args);
Asegúrate de que /opt/suid_scripts
todo lo que contiene sea de solo lectura para usuarios no root. De lo contrario, alguien podría cambiar cualquier cosa por whatever.pl
.
Además, tenga en cuenta que muchos lenguajes de script permiten que las variables de entorno cambien la forma en que ejecutan un script . Por ejemplo, una variable de entorno puede hacer que se cargue una biblioteca suministrada por la persona que llama, lo que le permite ejecutar código arbitrario como root. Por lo tanto, a menos que sepa que tanto el intérprete como el script en sí son robustos frente a todas las posibles variables de entorno, NO HAGA ESTO .
Entonces, ¿qué debo hacer en su lugar?
Una forma más segura de permitir que un usuario no root ejecute un script como root es agregar una regla sudo y hacer que el usuario se ejecute sudo /path/to/script
. Sudo elimina la mayoría de las variables de entorno y también permite al administrador seleccionar con precisión quién puede ejecutar el comando y con qué argumentos. Consulte ¿Cómo ejecutar un programa específico como root sin una solicitud de contraseña? para un ejemplo.
-privileged
shell y, si se llama desde un script responsable, también se puede invocar desuid root
esa manera de forma segura . Sin embargo, me temo que la noción de un guión responsable es una especie de sueño en el mundo real. De todos modos, es probable que vale la pena mencionar lo importante que es para las escrituras de evitarse de todo tipo, si es posible, mientras que los permisos son elevados ...