
O setuid
bit de permissão diz ao Linux para executar um programa com o ID de usuário efetivo do proprietário em vez do executor:
> 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
Entretanto, isso se aplica apenas a executáveis; scripts shell ignoram o 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
Wikipédiadiz:
Devido à maior probabilidade de falhas de segurança, muitos sistemas operacionais ignoram o atributo setuid quando aplicado a scripts de shell executáveis.
Supondo que estou disposto a aceitar esses riscos, existe alguma maneira de dizer ao Linux para tratar o bit setuid da mesma forma em scripts de shell e em executáveis?
Caso contrário, existe uma solução alternativa comum para esse problema? Minha solução atual é adicionar uma sudoers
entrada para permitir ALL
a execução de um determinado script como o usuário que desejo que ele seja executado, para NOPASSWD
evitar a solicitação de senha. A principal desvantagem disso é a necessidade de uma sudoers
entrada toda vez que eu quiser fazer isso, e a necessidade do chamador, sudo some-script
em vez de apenassome-script
Responder1
O Linux ignora o bit setuid¹ em todos os executáveis interpretados (ou seja, executáveis começando com uma #!
linha). OPerguntas frequentes sobre comp.unix.questionsexplica os problemas de segurança com scripts shell setuid. Esses problemas são de dois tipos: relacionados ao shebang e ao shell; Entro em mais detalhes abaixo.
Se você não se preocupa com segurança e deseja permitir scripts setuid, no Linux você precisará corrigir o kernel. A partir dos kernels 3.x, acho que você precisa adicionar uma chamada parainstall_exec_creds
noload_script
function, antes da chamada para open_exec
, mas não testei.
Setuid shebang
Há uma condição de corrida inerente à forma como shebang( #!
) normalmente é implementado:
- O kernel abre o executável e descobre que ele começa com
#!
. - O kernel fecha o executável e abre o interpretador.
- O kernel insere o caminho do script na lista de argumentos (como
argv[1]
) e executa o interpretador.
Se scripts setuid forem permitidos com esta implementação, um invasor pode invocar um script arbitrário criando um link simbólico para um script setuid existente, executando-o e providenciando a alteração do link após o kernel ter executado a etapa 1 e antes que o interpretador chegue a abrindo seu primeiro argumento. Por esta razão,a maioria das unidades ignora o bit setuidquando eles detectam uma coisa.
Uma maneira de garantir essa implementação seria o kernel bloquear o arquivo de script até que o intérprete o abrisse (observe que isso deve evitar não apenas desvincular ou sobrescrever o arquivo, mas também renomear qualquer diretório no caminho). Mas os sistemas Unix tendem a evitar bloqueios obrigatórios, e links simbólicos tornariam um recurso de bloqueio correto especialmente difícil e invasivo. Acho que ninguém faz assim.
Alguns sistemas unix (principalmente OpenBSD, NetBSD e Mac OS X, todos os quais requerem uma configuração de kernel para serem habilitados) implementamsetuid shebang segurousando um recurso adicional: o caminho refere-se ao arquivo já aberto no descritor de arquivo/dev/fd/N
N(então a abertura é aproximadamente equivalente a ). Muitos sistemas unix (incluindo Linux) possuem scripts setuid, mas não./dev/fd/N
dup(N)
/dev/fd
- O kernel abre o executável e descobre que ele começa com
#!
. Digamos que o descritor de arquivo do executável seja 3. - O kernel abre o interpretador.
- O kernel insere
/dev/fd/3
a lista de argumentos (comoargv[1]
) e executa o interpretador.
Página shebang de Sven Maschecktem muitas informações sobre shebang em unidades, incluindosuporte setuid.
Intérpretes Setuidas
Vamos supor que você conseguiu fazer seu programa rodar como root, seja porque seu sistema operacional suporta setuid shebang ou porque você usou um wrapper binário nativo (como sudo
). Você abriu uma falha de segurança?Talvez. A questão aqui énãosobre programas interpretados versus programas compilados. A questão é se o seusistema de tempo de execuçãose comporta com segurança se executado com privilégios.
Qualquer executável binário nativo vinculado dinamicamente é de certa forma interpretado pelocarregador dinâmico(por exemplo
/lib/ld.so
), que carrega as bibliotecas dinâmicas exigidas pelo programa. Em muitas unidades, você pode configurar o caminho de pesquisa para bibliotecas dinâmicas por meio do ambiente (LD_LIBRARY_PATH
é um nome comum para a variável de ambiente) e até mesmo carregar bibliotecas adicionais em todos os binários executados (LD_PRELOAD
). O invocador do programa pode executar código arbitrário no contexto desse programa, colocando umlibc.so
código especialmente criado$LD_LIBRARY_PATH
(entre outras táticas). Todos os sistemas sensatos ignoram asLD_*
variáveis nos executáveis setuid.Emcartuchoscomo sh, csh e derivados, variáveis de ambiente tornam-se automaticamente parâmetros de shell. Através de parâmetros como
PATH
,IFS
e muitos mais, o invocador do script tem muitas oportunidades de executar código arbitrário no contexto dos scripts de shell. Alguns shells definem essas variáveis para padrões sensatos se detectarem que o script foi invocado com privilégios, mas não sei se existe alguma implementação específica em que eu confiaria.A maioria dos ambientes de tempo de execução(sejam nativos, bytecode ou interpretados) possuem recursos semelhantes. Poucos tomam precauções especiais em executáveis setuid, embora aqueles que executam código nativo geralmente não façam nada mais sofisticado do que a vinculação dinâmica (que toma precauções).
Perlé uma exceção notável. Istosuporta explicitamente scripts setuidde forma segura. Na verdade, seu script pode executar setuid mesmo que seu sistema operacional ignore o bit setuid nos scripts. Isso ocorre porque o perl vem com um auxiliar de root setuid que executa as verificações necessárias e invoca novamente o interpretador nos scripts desejados com os privilégios desejados. Isto é explicado noperlsecmanual. Antigamente, os scripts perl setuid eram necessários
#!/usr/bin/suidperl -wT
em vez de#!/usr/bin/perl -wT
, mas na maioria dos sistemas modernos#!/usr/bin/perl -wT
são suficientes.
Observe que usando um binário nativowrapper não faz nada por si só para evitar esses problemas. Na verdade, pode tornar a situaçãopior, porque pode impedir que seu ambiente de tempo de execução detecte que ele foi chamado com privilégios e ignore sua configurabilidade de tempo de execução.
Um wrapper binário nativo pode tornar um script de shell seguro se o wrapperhigieniza o ambiente. O script deve tomar cuidado para não fazer muitas suposições (por exemplo, sobre o diretório atual), mas isso vale. Você pode usar o sudo para isso, desde que esteja configurado para higienizar o ambiente. Colocar variáveis na lista negra é propenso a erros, portanto, sempre coloque na lista branca. Com o sudo, certifique-se de que a env_reset
opção esteja ativada, desativada setenv
e que contenha env_file
apenas env_keep
variáveis inócuas.
TL,DR:
- Setuid shebang é inseguro, mas geralmente ignorado.
- Se você executar um programa com privilégios (por meio de sudo ou setuid), escreva código nativo ou perl ou inicie o programa com um wrapper que higienize o ambiente (como sudo com a
env_reset
opção).
¹ Esta discussão se aplica igualmente se você substituir “setgid” por “setuid”;ambos são ignorados pelo kernel do Linux em scripts
Responder2
Uma maneira de resolver esse problema é chamar o shell script de um programa que pode usar o bit setuid.
é algo como sudo. Por exemplo, aqui está como você faria isso em um programa 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;
}
Salve-o como setuid-test2.c.
compile
Agora faça o setuid neste programa binário:
su - nobody
[enter password]
chown nobody:nobody a.out
chmod 4755 a.out
Agora, você poderá executá-lo e verá seu script sendo executado sem permissão de ninguém.
Mas aqui também você precisa codificar o caminho do script ou passá-lo como argumento de linha de comando para o exe acima.
Responder3
Eu prefixo alguns scripts que estão neste barco assim:
#!/bin/sh
[ "root" != "$USER" ] && exec sudo $0 "$@"
Observe que isso não usa, setuid
mas simplesmente executa o arquivo atual com a extensão sudo
.
Responder4
Se por algum motivo sudo
não estiver disponível, você pode escrever um script wrapper fino em C:
#include <unistd.h>
int main() {
setuid(0);
execle("/bin/bash","bash","/full/path/to/script",(char*) NULL,(char*) NULL);
}
E depois de compilá-lo, defina-o setuid
como chmod 4511 wrapper_script
.
Isso é semelhante a outra resposta postada, mas executa o script com um ambiente limpo e usa explicitamente /bin/bash
em vez do shell chamado por system()
e, assim, fecha algumas possíveis falhas de segurança.
Observe que isso descarta totalmente o meio ambiente. Se você quiser usar algumas variáveis ambientais sem abrir vulnerabilidades, você realmente só precisa usar sudo
.
Obviamente, você quer ter certeza de que o script em si só pode ser gravado pelo root.