Usando o bit setuid corretamente

45

Eu tenho um processo que precisa de privilégios de root quando executado por um usuário normal. Aparentemente, eu posso usar o "bit setuid" para fazer isso. Qual é a maneira correta de fazer isso em um sistema POSIX?

Além disso, como posso fazer isso com um script que usa um intérprete (bash, perl, python, php, etc)?

Cachinhos Dourados
fonte

Respostas:

53

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 ( passwdprecisa acessar o /etc/shadowque 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 ./testuidcomo 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 chowne chmodusado 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_scriptstudo 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.

Cachinhos Dourados
fonte
1
muitos shells se comportam de maneira diferente se chamados como -privilegedshell e, se chamados de um script responsável, também podem ser invocados suid rootdessa 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 ...
mikeserv
@mikeserv Não estou familiarizado com os conceitos de " -privilegedshell" 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 .
Goldilocks
1
Eu realmente não quero fazer isso - scripts responsáveis são realmente difíceis - além de mim, provavelmente. Mas devo dizer que não concordo com a sua análise de sudo- eu considero sudocomo psicótico em que ele finge não ser root. Você pode até colocar globs de concha no seu sudoers. sué melhor na minha opinião para fins de script, embora sudoseja bom para aplicativos ao vivo. Mas também não é tão explícita como um script com um #!/bin/ksh -pbangline ou qualquer que seja o suid kshem $PATHé.
mikeserv
Embora essa resposta atenda à pergunta, os scripts setuid são uma receita para se abrir para uma vulnerabilidade de escalação de privilégios local. Há uma razão pela qual os scripts não podem ser criados facilmente. A resposta correta aqui é sudo.
Mdadm
Tomei a liberdade de editar sua resposta para, pelo menos, mencionar a enorme vulnerabilidade com variáveis ​​de ambiente. Eu não gosto disso como uma resposta canônica porque ela entra em muitas digressões em um método inseguro (o invólucro setuid para scripts). Seria melhor recomendar o sudo e deixar a discussão estendida para outro tópico (eu a abordei , a propósito).
Gilles 'SO- stop be evil'
11

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.

Matija Nalis
fonte
1
destacado. veja o exemplo de chamada segura na série do programador POSIX para man sh- e até mesmo isso falha ao executar um command -p env -i .... É muito difícil de fazer, afinal. Ainda assim, se for feito corretamente, para fins explícitos, pode ser melhor para suidum intérprete capaz do que usá-lo suou sudo- como o comportamento de qualquer um deles estará sempre fora do controle do script (embora suseja 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.
mikeserv