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 ...-privileged
shell" e "script responsável". Deseja fazer outra resposta sobre conchas? Não posso prometer a você, é claro;) Não há muitas boas razões para fazer isso -sudo
é uma opção muito melhor, se possível - eu a escrevi como uma resposta genérica decorrente de uma pergunta inicial sobre autenticação de senha do sistema em php .sudo
- eu considerosudo
como psicótico em que ele finge não serroot
. Você pode até colocar globs de concha no seusudoers
.su
é melhor na minha opinião para fins de script, emborasudo
seja bom para aplicativos ao vivo. Mas também não é tão explícita como um script com um#!/bin/ksh -p
bangline ou qualquer que seja osuid ksh
em$PATH
é.Sim, você pode, mas provavelmente é uma péssima ideia . Normalmente, você não configuraria o bit SUID diretamente no seu executável, mas usaria um sudo (8) ou su (1) para executá-lo (e limitar quem pode executá-lo)
Note, no entanto, existem muitos problemas de segurança ao permitir que usuários comuns executem programas (e especialmente scripts!) Como root. A maioria deles precisa fazer isso, a menos que o programa seja específico e muito cuidadosamente escrito para lidar com isso, usuários mal-intencionados poderiam usá-lo para obter facilmente o shell raiz.
Portanto, mesmo se você fizer isso, seria uma boa idéia primeiro limpar todas as entradas do programa que você deseja executar como raiz, incluindo seu ambiente, parâmetros de linha de comando, STDIN e quaisquer arquivos que processe, dados provenientes de conexões de rede abre, etc etc. É extremamente difícil de fazer e ainda mais difícil de fazer o certo.
Por exemplo, você pode permitir que os usuários editem apenas alguns arquivos com o editor. Mas o editor permite a execução de comandos de auxiliares externos ou a suspensão, dando aos usuários o shell raiz para fazer o que bem entenderem. Ou você pode estar executando scripts de shell que parecem muito seguros para você, mas o usuário pode executá-lo com $ IFS modificado ou outras variáveis de ambiente, alterando completamente seu comportamento. Ou pode ser um script PHP que deve executar "ls", mas o usuário o executa com $ PATH modificado e, portanto, executa um shell chamado "ls". Ou dezenas de outros problemas de segurança.
Se o programa realmente precisar fazer parte de seu trabalho como root, provavelmente seria melhor modificá-lo para que ele funcione como usuários normais, mas apenas execute (depois de limpar o máximo possível) a parte que precisa absolutamente de root através do programa suid helper.
Observe que, mesmo que você confie completamente em todos os usuários locais (por exemplo, dê a eles a senha root), é uma má idéia, pois qualquer supervisão não intencional de segurança por QUALQUER deles (como ter um script PHP online ou uma senha fraca ou o que for) repentinamente permite que o atacante remoto comprometa completamente a máquina inteira.
fonte
man sh
- e até mesmo isso falha ao executar umcommand -p env -i ...
. É muito difícil de fazer, afinal. Ainda assim, se for feito corretamente, para fins explícitos, pode ser melhor parasuid
um intérprete capaz do que usá-losu
ousudo
- como o comportamento de qualquer um deles estará sempre fora do controle do script (emborasu
seja menos provável que atire bolas curvas, pois ele tende a ser um pouco menos insano) . Agrupar todo o pacote é a única aposta certa - é melhor ter certeza de que é tudo.