Permitir setuid em scripts de shell


185

O setuidbit de permissão diz ao Linux para executar um programa com o ID do 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

No entanto, isso se aplica apenas a executáveis; scripts de 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

A Wikipedia diz :

Devido à maior probabilidade de falhas de segurança, muitos sistemas operacionais ignoram o atributo setuid quando aplicados a scripts 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 comum para esse problema? Minha solução atual é adicionar uma sudoersentrada para permitir ALLa execução de um determinado script como o usuário com o qual quero que ele seja executado, NOPASSWDpara evitar o prompt de senha. A principal desvantagem disso é a necessidade de uma sudoersentrada toda vez que eu quero fazer isso, e a necessidade do chamador em sudo some-scriptvez de apenassome-script

Respostas:


203

O Linux ignora o bit setuid¹ em todos os executáveis ​​interpretados (ou seja, executáveis ​​iniciando com uma #!linha). As perguntas frequentes do comp.unix.questions explicam 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 importa com a segurança e deseja permitir scripts setuid, no Linux, precisará corrigir o kernel. No kernel 3.x, acho que você precisa adicionar uma chamada install_exec_credsna load_scriptfunção antes da chamada open_exec, mas ainda não testei.


Setuid shebang

Há uma condição de corrida inerente à maneira #!como normalmente é implementado o shebang ( ):

  1. O kernel abre o executável e descobre que ele começa com #!.
  2. O kernel fecha o executável e abre o intérprete.
  3. O kernel insere o caminho para o script na lista de argumentos (como argv[1] ) e executa o intérprete.

Se scripts setuid forem permitidos com esta implementação, um invasor poderá chamar um script arbitrário criando um link simbólico para um script setuid existente, executando-o e organizando a alteração do link após o kernel ter executado a etapa 1 e antes que o intérprete chegue ao abrindo seu primeiro argumento. Por esta razão, maioria das unidades ignora o bit setuid quando detecta um shebang.

Uma maneira de proteger essa implementação seria o kernel bloquear o arquivo de script até que o intérprete o abrisse (observe que isso deve impedir não apenas desvincular ou sobrescrever o arquivo, mas também renomear qualquer diretório no caminho). Mas os sistemas unix tendem a evitar os bloqueios obrigatórios, e os links simbólicos tornariam um recurso de bloqueio correto especialmente difícil e invasivo. Eu não acho que alguém faça dessa maneira.

Alguns sistemas unix (principalmente o OpenBSD, o NetBSD e o Mac OS X, que exigem a habilitação de uma configuração do kernel) implementam o shebang do setuid seguro usando um recurso adicional: o caminho refere-se ao arquivo já aberto no descritor de arquivo N (portanto, a abertura é aproximadamente equivalente a ). Muitos sistemas unix (incluindo Linux) possuem/dev/fd/N/dev/fd/Ndup(N)/dev/fd mas não scripts setuid.

  1. O kernel abre o executável e descobre que ele começa com #! . Digamos que o descritor de arquivo do executável seja 3.
  2. O kernel abre o intérprete.
  3. O kernel insere /dev/fd/3a lista de argumentos (as argv[1]) e executa o intérprete.

A página shebang de Sven Mascheck tem muitas informações sobre shebang nos departamentos, incluindo suporte a setuid .


Intérpretes Setuid

Vamos supor que você tenha conseguido executar seu programa como root, seja porque o seu sistema operacional suporta setuid shebang ou porque você usou um invólucro binário nativo (como sudo). Você abriu uma brecha na segurança? Talvez . O problema aqui não é sobre programas interpretados versus compilados. O problema é se o sistema de tempo de execução se comporta com segurança se executado com privilégios.

  • Qualquer executável binário nativo vinculado dinamicamente é interpretado pelo carregador dinâmico (por exemplo /lib/ld.so), que carrega as bibliotecas dinâmicas exigidas pelo programa. Em muitos departamentos, 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é 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 um especialmente criado libc.soem $LD_LIBRARY_PATH(entre outras táticas). Todos os sistemas sãos ignoram as LD_*variáveis ​​nos executáveis ​​setuid.

  • Em shells como sh, csh e derivadas, as variáveis ​​de ambiente se tornam automaticamente parâmetros de shell. Por meio de parâmetros como PATH, IFSe muitos mais, o invocador do script tem muitas oportunidades para executar código arbitrário no contexto dos scripts de shell. Alguns shells definem essas variáveis ​​como padrões saudáveis, se detectarem que o script foi invocado com privilégios, mas não sei se há alguma implementação específica em que eu confiaria.

  • A maioria dos ambientes de tempo de execução (nativos, bytecode ou interpretados) possui recursos semelhantes. Poucos tomam precauções especiais em executáveis ​​setuid, embora os 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. Ele suporta explicitamente scripts setuid de maneira segura. De fato, seu script pode executar o setuid, mesmo que seu SO tenha ignorado o bit setuid nos scripts. Isso ocorre porque o perl é fornecido com um assistente raiz setuid que executa as verificações necessárias e reinicia o intérprete nos scripts desejados com os privilégios desejados. Isso é explicado no manual do perlsec . Antes, os scripts setuid perl necessários, em #!/usr/bin/suidperl -wTvez de #!/usr/bin/perl -wT, mas na maioria dos sistemas modernos, #!/usr/bin/perl -wTsão suficientes.

Observe que o uso de um wrapper binário nativo não faz nada por si só para evitar esses problemas . Na verdade, ele pode tornar a situação pior , porque pode evitar que o seu ambiente de tempo de execução de detectar que é invocada com privilégios e ignorando sua configurabilidade execução.

Um wrapper binário nativo pode tornar um script de shell seguro se o wrapper higienizar 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. As variáveis ​​da lista negra estão sujeitas a erros, portanto, sempre use a lista de permissões. Com sudo, certifique-se de que a env_resetopção está ligada, que setenvestá desligado, e que env_filee env_keepcontêm apenas variáveis inócuos.


TL, DR:

  • Shebang Setuid é inseguro, mas geralmente ignorado.
  • Se você executar um programa com privilégios (por meio do sudo ou setuid), escreva código nativo ou perl ou inicie o programa com um wrapper que higienize o ambiente (como o sudo com a env_resetopção).

Discussion Esta discussão se aplica igualmente se você substituir “setgid” por “setuid”; ambos são ignorados pelo kernel do Linux em scripts


2
@ Josh: Scripts de shell setuid seguros são possíveis, mas somente se o implementador de shell e o gravador de scripts forem muito cuidadosos. Em vez de código nativo, recomendo o Perl, onde os implementadores cuidaram para que os scripts setuid fossem protegidos com pouco esforço da parte do gravador de scripts.
Gilles

3
aparentemente, o suidperlmaterial foi preterido e marcado para remoção por anos (mas persiste não-a-menos)
jmtd

7
Na verdade suidperl, foi removido a partir do perl 5.11 (5.12 estável): perl5110delta:> "suidperl" foi removido. Ele costumava fornecer um mecanismo para emular bits de permissão setuid em sistemas que não o suportam adequadamente. perl5120delta:> "suidperl" não faz mais parte do Perl. Ele costumava fornecer um mecanismo para emular bits de permissão setuid em sistemas que não o suportam adequadamente.
Randy Stauner

5
Observe também esta linha dos documentos do perl 5.6.1 (quase uma década atrás) ... perl561delta:> Observe que o suidperl não é construído nem instalado por padrão em qualquer versão recente do perl. O uso de suidperl é altamente desencorajado . Se você acha que precisa, tente alternativas como o sudo primeiro. Veja courtesan.com/sudo .
Randy Stauner

3
Eu não entendo: isso parece ser uma explicação das razões dos problemas, mas há uma resposta real para a pergunta do OP aqui? Existe uma maneira de dizer ao meu sistema operacional para executar scripts shell setuid?
Tom

55

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ê deve 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 linha de comando arg para acima do exe.


25
Eu desaconselho a sugestão de permitir a passagem do script como um argumento de linha de comando, pois isso essencialmente dá a qualquer pessoa que possa executar o programa a capacidade de executar qualquer script como esse usuário definido.
dsp

40
Observe que ISSO NÃO É SEGURO, mesmo que o caminho completo para o script seja codificado. O shell herdará variáveis ​​do ambiente, e muitas delas permitem ao invocador injetar código arbitrário. PATHe LD_LIBRARY_PATHsão vetores óbvios. Alguns escudos executar $ENVou $BASHENVou ~/.zshenvaté mesmo antes de começar a executar o script adequado, então você não pode proteger contra estes em tudo de dentro do script. A única maneira segura de chamar um script de shell com privilégios é limpar o ambiente . Sudo sabe como fazer isso com segurança. Portanto , não escreva seu próprio invólucro, use sudo .
Gilles

28
Sinto-me mal por ele ter sido subitamente votado por isso - eu disse especificamente que também queria ouvir versões inseguras, e estava imaginando um executável que utilizava um argumento de script de shell quando disse isso. Obviamente, é extremamente inseguro, mas eu queria saber quais são as possibilidades
Michael Mrozek

8
@Gilles: FYI, o Linux desativa, LD_LIBRARY_PATHentre outras coisas, quando encontra o bit setuid.
grawity

3
Em vez de usar system, você pode achar mais simples (e mais eficiente) usar um membro da execfamília - provavelmente execve. Dessa forma, você não cria um novo processo ou inicia um shell e pode encaminhar argumentos (assumindo que seu script privilegiado possa manipular argumentos com segurança).
Toby Speight

23

Prefixo alguns scripts que estão neste barco assim:

#!/bin/sh
[ "root" != "$USER" ] && exec sudo $0 "$@"

Observe que isso não usa, setuidmas simplesmente executa o arquivo atual com sudo.


5
Isso não usa setuid, mas apenas fornece um sudoprompt. (Para mim, todo o ponto de setuid é permitir que as coisas executado como root sem necessidade de sudo.)
Luc

12

Se você quiser evitar ligar, sudo some_scriptbasta:

  #!/ust/bin/env sh

  sudo /usr/local/scripts/your_script

Os programas SETUID precisam ser projetados com extremo cuidado, pois são executados com privilégios de root e os usuários têm grande controle sobre eles. Eles precisam verificar a sanidade de tudo. Você não pode fazer isso com scripts porque:

  • Os shells são grandes pedaços de software que interagem fortemente com o usuário. É quase impossível verificar tudo com integridade - especialmente porque a maior parte do código não se destina a ser executada nesse modo.
  • Os scripts são uma solução bastante rápida e geralmente não são preparados com tanto cuidado que permitiriam setuid. Eles têm muitos recursos potencialmente perigosos.
  • Eles dependem fortemente de outros programas. Não é suficiente que o shell tenha sido verificado. sed, awketc. também precisariam ser verificados

Observe que isso sudofornece algumas verificações de integridade, mas não é suficiente - verifique todas as linhas em seu próprio código.

Como uma última observação: considere o uso de recursos. Eles permitem que você conceda a um processo em execução como usuário privilégios especiais que normalmente exigiriam privilégios de root. No entanto, por exemplo, embora seja pingnecessário manipular a rede, ele não precisa ter acesso aos arquivos. Não tenho certeza, no entanto, se eles são herdados.


2
Eu presumo que /ust/bin/envdeveria ser /usr/bin/env.
Bob

4

Você pode criar um alias para sudo + o nome do script. Claro, isso é ainda mais trabalhoso de configurar, pois você também precisa configurar um alias, mas evita que você precise digitar sudo.

Mas se você não se importa com riscos horríveis de segurança, use um shell setuid como intérprete para o script do shell. Não sei se isso funcionará para você, mas acho que pode.

Deixe-me declarar que eu aconselho a não fazer isso, no entanto. Estou apenas mencionando isso para fins educacionais ;-)


5
Vai funcionar. Horrivelmente como você afirmou. O bit SETUID permite a execução com o proprietário certo. O shell Setuid (a menos que tenha sido projetado para funcionar com o setuid) será executado como root para qualquer usuário. rm -rf /Ou seja, qualquer um pode executar (e outros comandos da série NÃO O FAZEM EM CASA ).
Maciej Piechotka 17/08/10

9
@MaciejPiechotka por NÃO FAÇA EM CASA você quer dizer Sinta-se livre para fazer isso no trabalho? :)
peterph

4

comando super [-r reqpath] [args]

Super permite que usuários especificados executem scripts (ou outros comandos) como se fossem root; ou pode definir os grupos uid, gid e / ou suplementares por comando antes de executar o comando. Ele pretende ser uma alternativa segura para criar scripts como setuid. Super também permite que usuários comuns forneçam comandos para execução por outros; estes são executados com o uid, gid e grupos do usuário que oferece o comando.

Super consulta um arquivo `` super.tab '' para ver se o usuário tem permissão para executar o comando solicitado. Se a permissão for concedida, super executará o pgm [args], em que pgm é o programa associado a este comando. (A raiz é permitida a execução por padrão, mas ainda pode ser negada se uma regra excluir a raiz. Usuários comuns não podem executar a execução por padrão.)

Se o comando for um link simbólico (ou link físico também) para o super programa, digitar% command args é equivalente a digitar% super command args (o comando não deve ser super ou super não reconhecerá que está sendo chamado por meio de um ligação.)

http://www.ucolick.org/~will/RUE/super/README

http://manpages.ubuntu.com/manpages/utopic/en/man1/super.1.html


Olá e bem-vindo ao site! Esperamos que as respostas sejam mais detalhadas aqui. Você poderia editar sua resposta e explicar o que é esse programa e como ele poderia ajudar a resolver o problema do OP?
terdon

Obrigado Nizam, por horas tentando encontrar algo como super para que as contas possam ser executadas por outro usuário como o usuário do arquivo.
Chad

2

Se, por algum motivo, sudonão estiver disponível, você poderá escrever um script de 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 como setuidcom chmod 4511 wrapper_script.

Isso é semelhante a outra resposta publicada, mas executa o script com um ambiente limpo e usa explicitamente, em /bin/bashvez do shell chamado por system(), e, portanto, fecha algumas falhas de segurança em potencial.

Observe que isso descarta o ambiente completamente. Se você deseja usar algumas variáveis ​​ambientais sem abrir vulnerabilidades, você realmente só precisa usá-lo sudo.

Obviamente, você deseja garantir que o próprio script seja gravável apenas pela raiz.


-3

Encontrei essa pergunta pela primeira vez, não convencida de todas as respostas, e aqui está uma muito melhor, que também permite ofuscar completamente o script do bash, se você quiser!

Deve ser auto-explicativo.

#include <string>
#include <unistd.h>

template <typename T, typename U>
T &replace (
          T &str, 
    const U &from, 
    const U &to)
{
    size_t pos;
    size_t offset = 0;
    const size_t increment = to.size();

    while ((pos = str.find(from, offset)) != T::npos)
    {
        str.replace(pos, from.size(), to);
        offset = pos + increment;
    }

    return str;
}

int main(int argc, char* argv[])
{
    // Set UUID to root
    setuid(0);

    std::string script = 
R"(#!/bin/bash
whoami
echo $1
)";

    // Escape single quotes.
    replace(script, std::string("'"), std::string("'\"'\"'"));

    std::string command;
    command = command + "bash -c '" + script + "'"; 

    // Append the command line arguments.
    for (int a = 0; a < argc; ++a)
    {
        command = command + " " + argv[a];
    }

    return system(command.c_str());
}

Então você corre

g++ embedded.cpp -o embedded
sudo chown root embedded
sudo chmod u+s embedded

Por que os votos negativos ???
Theodore R. Smith

2
Provavelmente porque é uma solução excessivamente complicada que manipula seqüências de caracteres e possui código de shell incorporado. Ou você cria um iniciador simples ou usa o sudo. Esta é a pior de todas as opções.
siride
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.