O bit setuid pode ser definido em um arquivo executável para que, quando executado, o programa tenha os privilégios do proprietário do arquivo em vez do usuário real, se eles forem diferentes. Essa é a diferença entre o uid efetivo (ID do usuário) e o uid real .
Alguns utilitários comuns, como passwd
, pertencem ao root e são configurados dessa maneira por necessidade ( passwd
precisa acessar o /etc/shadow
que só pode ser lido pelo root).
A melhor estratégia para fazer isso é fazer o que você precisa fazer como superusuário imediatamente e diminuir os privilégios para que menos erros ou uso indevido ocorram durante a execução do root. Para fazer isso, você define o uid efetivo do processo como uid real. No 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;
}
As funções relevantes, que devem ter um equivalente na maioria dos idiomas de nível superior, se forem usadas normalmente em sistemas POSIX:
getuid()
: Obtenha o uid real .
geteuid()
: Obtenha o uid eficaz .
seteuid()
: Defina o uid efetivo .
Você não pode fazer nada com o último inapropriado ao uid real, exceto na medida em que o bit setuid foi definido no executável . Então, para tentar isso, compile gcc test.c -o testuid
. Você precisa, com privilégios:
chown root testuid
chmod u+s testuid
O último define o bit setuid. Se você executar agora ./testuid
como um usuário normal, verá o processo por padrão executado com uid 0 eficaz, root.
E os scripts?
Isso varia de plataforma para plataforma , mas no Linux, coisas que exigem um intérprete, incluindo bytecode, não podem usar o bit setuid, a menos que esteja definido no intérprete (o que seria muito estúpido). Aqui está um script perl simples que imita o código C acima:
#!/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 às suas raízes * nixy, o perl tem variáveis especiais para uid ( $>
) e uid real ( $<
). Mas se você tentar o mesmo chown
e chmod
usado com o executável compilado (de C, exemplo anterior), não fará diferença. O script não pode obter privilégios.
A resposta para isso é usar um binário setuid para executar o 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);
}
Compile isso gcc --std=c99 whatever.c -o perlsuid
, então chown root perlsuid && chmod u+s perlsuid
. Agora você pode executar qualquer script perl com um uid efetivo de 0, independentemente de quem o possui.
Uma estratégia semelhante funcionará com php, python, etc. Mas ...
# Think hard, very important:
>_< # Genuine ASCII art "Oh tish!" face
POR FAVOR, POR FAVOR, NÃO deixe esse tipo de coisa por aí . Muito provavelmente, você realmente deseja compilar o nome do script como um caminho absoluto , ou seja, substitua todo o código main()
por:
const char *args[] = { "perl", "/opt/suid_scripts/whatever.pl" }
return execv("/usr/bin/perl", (char * const*)args);
Eles garantem que /opt/suid_scripts
tudo nele seja somente leitura para usuários não root. Caso contrário, alguém poderia trocar qualquer coisa por whatever.pl
.
Além disso, lembre-se de que muitas linguagens de script permitem que variáveis de ambiente alterem a maneira como executam um script . Por exemplo, uma variável de ambiente pode fazer com que uma biblioteca fornecida pelo chamador seja carregada, permitindo que o chamador execute código arbitrário como raiz. Portanto, a menos que você saiba que o intérprete e o próprio script são robustos em relação a todas as variáveis de ambiente possíveis, NÃO FAÇA ISSO .
Então, o que devo fazer?
Uma maneira mais segura de permitir que um usuário não root execute um script como root é adicionar uma regra sudo e fazer com que o usuário execute sudo /path/to/script
. O Sudo retira a maioria das variáveis de ambiente e também permite ao administrador selecionar com precisão quem pode executar o comando e com quais argumentos. Consulte Como executar um programa específico como root sem um prompt de senha? Por exemplo.
-privileged
shell e, se chamados de um script responsável, também podem ser invocadossuid root
dessa maneira com segurança . Receio que a noção de um script responsável seja um sonho no mundo real. De qualquer forma, é provavelmente vale a pena mencionar o quão importante é a gravações Evitar de todos os tipos, se possível, enquanto as permissões são elevados ...