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 forem diferentes. Esta é a diferença entre eficaz uid (id do usuário) e real uid.
Alguns utilitários comuns, como passwd
, são de propriedade root e são configurados dessa forma por necessidade ( passwd
precisa acessar /etc/shadow
, que pode ser lido apenas pelo root).
A melhor estratégia ao fazer isso é fazer o que você precisa fazer imediatamente como superusuário e, em seguida, diminuir os privilégios para que bugs ou uso indevido sejam menos prováveis de acontecer enquanto estiver executando o root. Para fazer isso, você configura o efetivo uid do processo para seu 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 das linguagens de nível mais alto, se forem usadas comumente em sistemas POSIX:
-
getuid()
: obtenha o real uid. -
geteuid()
: obtenha o eficaz uid. -
seteuid()
: defina o efetivo uid.
Você não pode fazer nada com o último inapropriado para o 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 então, com privilégios:
chown root testuid
chmod u+s testuid
O último define o bit setuid. Se você agora executar ./testuid
como um usuário normal, verá que o processo, por padrão, é executado com uid efetivo 0, root.
E os scripts?
Isso varia de plataforma para plataforma , mas em Linux, coisas que requerem um intérprete, incluindo bytecode, não podem fazer uso do bit setuid a menos que ele esteja configurado no interpretador (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 foi construído em variáveis especiais para uid efetivo ( $>
) e uid real ( $<
). Mas se você tentar o mesmo chown
e chmod
usado com o executável compilado (do C, exemplo anterior), não fará diferença alguma. 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 este gcc --std=c99 whatever.c -o perlsuid
e, em seguida, chown root perlsuid && chmod u+s perlsuid
. Agora você pode executar qualquer script perl com um uid efetivo de 0, independentemente de quem seja o proprietário.
Uma estratégia semelhante funcionará com php, python, etc. Mas ...
# Think hard, very important:
>_< # Genuine ASCII art "Oh tish!" face
POR FAVOR, NÃO deixe este tipo de coisa por aí . O mais provável é que você queira compilar no nome do script como um caminho absoluto , ou seja, substituir todo o código em main()
por:
const char *args[] = { "perl", "/opt/suid_scripts/whatever.pl" }
return execv("/usr/bin/perl", (char * const*)args);
Certifique-se de que /opt/suid_scripts
e tudo nele são 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 root. Assim, a menos que você saiba que tanto o interpretador quanto o próprio script são robustos contra todas as possíveis variáveis de ambiente, NÃO FAÇA ISSO .
Então, o que devo fazer em vez disso?
Uma maneira mais segura de permitir que um usuário não raiz execute um script como root é adicionar uma regra sudo e ter o usuário executando sudo /path/to/script
. O Sudo remove 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. Veja Como executar um específico programa como root sem um prompt de senha? para um exemplo.