Por que não podemos executar uma lista de comandos como usuário diferente sem o sudo?

8

Esta pergunta foi feita de maneira diferente em outros fóruns. Mas não houve uma explicação decente por que você não pode fazer o abaixo no bash.

#!/bin/bash
command1
SWITCH_USER_TO rag
command2
command3

Geralmente, a maneira sugerida é

#!/bin/bash
command1
sudo -u rag command2
sudo -u rag command3

mas por que não é possível bashmudar para um usuário diferente em algum momento durante a execução do script bash e executar o restante dos comandos como um usuário diferente?

trapo
fonte
Essa pergunta também foi feita no Superusuário, em superuser.com/questions/93385/… , e esta resposta superuser.com/a/573882/76251 fornece um método semelhante ao HERE DOC para alterar os usuários em um script.
Tim Kennedy

Respostas:

4

As informações já estão na minha outra resposta , mas estão um pouco enterradas lá. Então pensei em adicionar aqui.

bashnão tem provisão para alterar usuários, mas zshsim.

Em zsh, você altera os usuários atribuindo valores a essas variáveis:

  • $EUID: altere o ID do usuário efetivo (e nada mais). Geralmente, você pode alterar o seu euid entre o seu ID do usuário real e o ID do usuário salvo (se chamado de um executável setuid) ou mudar para qualquer coisa se o seu euid for 0.
  • $UID: altere o ID do usuário efetivo, o ID do usuário real e o ID do usuário salvo salvo para o novo valor. A menos que esse novo valor seja 0, não há retorno, já que todos os três foram definidos com o mesmo valor, não há como alterá-lo para outra coisa.
  • $EGIDe $GID: a mesma coisa, exceto para identificações de grupo.
  • $USERNAME. Isso é como usar sudoor su. Ele define seu euid, ruid, ssuid para o uid desse usuário. Ele também define os grupos egid, rgid e ssgid e suplementares com base nas associações ao grupo, conforme definido no banco de dados do usuário. Por exemplo $UID, a menos que você defina $USERNAMEcomo root, não há como voltar, mas por exemplo $UID, você pode alterar o usuário apenas por um subshell.

Se você executar esses scripts como "root":

#! /bin/zsh -
UID=0 # make sure all our uids are 0

id -u # run a command as root

EUID=1000

id -u # run a command as uid 1000 (but the real user id is still 0
      # so that command would be able to change its euid to that.
      # As for the gids, we only have those we had initially, so 
      # if started as "sudo the-script", only the groups root is a
      # member of.

EUID=0 # we're allowed to do that because our ruid is 0. We need to do
       # that because as a non-priviledged user, we can't set our euid
       # to anything else.

EUID=1001 # now we can change our euid since we're superuser again.

id -u # same as above

Agora, para alterar o usuário como em sudoou su, só podemos fazer isso usando subshells, caso contrário, só podemos fazer uma vez:

#! /bin/zsh -

id -u # run as root

(
  USERNAME=rag
  # that's a subshell running as "rag"

  id # see all relevant group memberships are applied
)
# now back to the parent shell process running as root

(
  USERNAME=stephane
  # another subshell this time running as "stephane"

  id
)
Stéphane Chazelas
fonte
8

Ofertas do kernel man 2 setuide amigos.

Agora, ele funciona no processo de chamada. Mais importante, você não pode elevar seus privilégios. É por isso sue sudotêm conjunto de bits SETUID assim que funcionam sempre com privilégios mais elevados ( root) e soltar para o usuário desejado em conformidade.

Essa combinação significa que você não pode alterar o UID do shell executando outro programa para fazer isso (é por isso que você não pode esperar sudoou qualquer outro programa faça isso) e você não pode (como shell) fazê-lo sozinho, a menos que está disposto a executar como root ou o mesmo usuário para o qual deseja mudar, o que não faz sentido. Além disso, uma vez que você remove privilégios, não há caminho de volta.

Miroslav Koškár
fonte
6

Bem, você sempre pode fazer:

#! /bin/bash -
{ shopt -s expand_aliases;SWITCH_TO_USER(){ { _u=$*;_x="$(declare;alias
shopt -p;set +o);"'set -- "${_a[@]}";unset _x _a';set +x;} 2>/dev/null
exec sudo -u "$1" env "_x=$_x" bash -c 'eval "$_x" 2> /dev/null;. "$0"
' "$0";};alias skip=":||:<<'SWITCH_TO_USER $_u'"
alias SWITCH_TO_USER="{ eval '"'_a=("$@")'"';} 2>/dev/null;SWITCH_TO_USER"
${_u+:} alias skip=:;} 2>/dev/null
skip

echo test
a=foo
set a b

SWITCH_TO_USER root

echo "$a and $1 as $(id -un)"
set -x
foo() { echo "bar as $(id -un)"; }

SWITCH_TO_USER rag

foo
set +x

SWITCH_TO_USER root again

echo "hi again from $(id -un)"

(ʘ‿ʘ)

Isso começou como uma piada, pois implementa o que é solicitado, embora provavelmente não exatamente como o esperado, e não é praticamente útil. Mas como ele evoluiu para algo que funciona até certo ponto e envolve alguns hacks legais, aqui está uma pequena explicação:

Como disse Miroslav , se deixarmos de lado os recursos no estilo Linux (que de qualquer forma não ajudariam aqui), a única maneira de um processo sem privilégios mudar o uid é executando um executável setuid.

Depois de obter o privilégio de superusuário (executando um executável setuid cujo proprietário é root, por exemplo), você pode alternar o ID do usuário efetivo entre o ID do usuário original, 0 e qualquer outro ID, a menos que você renuncie ao ID do usuário definido salvo ( como as coisas gostam sudoou sucostuma fazer).

Por exemplo:

$ sudo cp /usr/bin/env .
$ sudo chmod 4755 ./env

Agora eu tenho um envcomando que me permite executar qualquer comando com um ID de usuário efetivo e salvar o ID de usuário definido 0 (meu ID de usuário real ainda é 1000):

$ ./env id -u
0
$ ./env id -ru
1000
$ ./env -u PATH =perl -e '$>=1; system("id -u"); $>=0;$>=2; system("id -u");
   $>=0; $>=$<=3; system("id -ru; id -u"); $>=0;$<=$>=4; system("id -ru; id -u")'
1
2
3
3
4
4

perlpossui wrappers para setuid/ seteuid(aqueles $>e $<variáveis).

O mesmo acontece com o zsh:

$ sudo zsh -c 'EUID=1; id -u; EUID=0; EUID=2; id -u'
1
2

Embora acima, esses idcomandos sejam chamados com um ID do usuário real e o conjunto salvo seja 0 (embora se eu tivesse usado o meu em ./envvez sudodisso, apenas o conjunto definido seria salvo, enquanto o ID do usuário real permaneceria 1000), o que significa que se fossem comandos não confiáveis, eles ainda poderiam causar algum dano; portanto, você gostaria de escrevê-lo como:

$ sudo zsh -c 'UID=1 id -u; UID=2 id -u'

(isto é, define todos os uids (conjunto efetivo, real e salvo) apenas para a execução desses comandos.

bashnão tem como alterar os IDs do usuário. Portanto, mesmo se você tivesse um executável setuid para chamar seu bashscript, isso não ajudaria.

Com bash, você fica executando um executável setuid toda vez que deseja alterar o uid.

A idéia no script acima é uma chamada para SWITCH_TO_USER, para executar uma nova instância do bash para executar o restante do script.

SWITCH_TO_USER someuseré mais ou menos uma função que executa o script novamente como um usuário diferente (usando sudo), mas pula o início do script até SWITCH_TO_USER someuser.

Onde fica complicado é que queremos manter o estado do bash atual depois de iniciar o novo bash como um usuário diferente.

Vamos dividir:

{ shopt -s expand_aliases;

Vamos precisar de pseudônimos. Um dos truques deste script é pular a parte do script até o SWITCH_TO_USER someuser, com algo como:

:||: << 'SWITCH_TO_USER someuser'
part to skip
SWITCH_TO_USER

Esse formulário é semelhante ao #if 0usado em C, que é uma maneira de comentar completamente algum código.

:é um no-op que retorna true. Então : || :, o segundo: nunca é executado. No entanto, é analisado. E << 'xxx'é uma forma de documento aqui, onde (porque xxxé citado), nenhuma expansão ou interpretação é feita.

Poderíamos ter feito:

: << 'SWITCH_TO_USER someuser'
part to skip
SWITCH_TO_USER

Mas isso significaria que o documento aqui teria que ser escrito e passado como padrão para : . :||:evita isso.

Agora, onde fica hacky é que usamos o fato de bashexpandir aliases muito cedo em seu processo de análise. Ter skipum alias para a :||: << 'SWITCH_TO_USER someuther'parte do construto comentando .

Vamos continuar:

SWITCH_TO_USER(){ { _u=$*;_x="$(declare;alias
shopt -p;set +o);"'set -- "${_a[@]}";unset _x _a';set +x;} 2>/dev/null
exec sudo -u "$1" env "_x=$_x" bash -c 'eval "$_x" 2> /dev/null;. "$0"
' "$0";}

Aqui está a definição do SWITCH_TO_USER função . Veremos abaixo que SWITCH_TO_USER acabará por ser um alias envolvido nessa função.

Essa função faz a maior parte da reexecução do script. No final, vemos que ele é reexecutado (no mesmo processo por causa de exec) bashcom a _xvariável em seu ambiente (usamos envaqui porque sudogeralmente higieniza seu ambiente e não permite a passagem de envios arbitrários). Isso bashavalia o conteúdo dessa $_xvariável como código bash e origina o próprio script.

_x é definido anteriormente como:

_x="$(declare;alias;shopt -p;set +o);"'set -- "${_a[@]}";unset _x _a'

Todo o declare, alias, shopt -p set +ode saída tornar-se um dump do estado interno do shell. Ou seja, eles despejam a definição de todas as variáveis, funções, aliases e opções como código de shell pronto para ser avaliado. Além disso, adicionamos a configuração dos parâmetros posicionais ( $1, $2...) com base no valor do$_a matriz (veja abaixo), e alguns limpam para que a grande $_xvariável não permaneça no ambiente pelo restante do script.

Você notará que a primeira parte até set +xé agrupada em um grupo de comandos cujo stderr é redirecionado para /dev/null( {...} 2> /dev/null). Isso porque, se em algum momento do script set -x(ou set -o xtrace) for executado, não queremos que esse preâmbulo gere rastreamentos, pois queremos torná-lo o menos invasivo possível. Portanto, executamos um set +x(depois de ter certeza de despejar as xtraceconfigurações da opção (incluindo ) antecipadamente) em que os rastreamentos são enviados para / dev / null.

O eval "$_X"stderr também é redirecionado para / dev / null por razões semelhantes, mas também para evitar erros na gravação de tentativa de variáveis ​​especiais somente leitura.

Vamos continuar com o script:

alias skip=":||:<<'SWITCH_TO_USER $_u'"

Esse é o nosso truque descrito acima. Na invocação inicial do script, ele será cancelado (veja abaixo).

alias SWITCH_TO_USER="{ eval '"'_a=("$@")'"';} 2>/dev/null;SWITCH_TO_USER"

Agora, o alias envolve o SWITCH_TO_USER. O principal motivo é poder passar os parâmetros posicionais ( $1, $2...) para o novo bashque interpretará o restante do script. Não foi possível fazê-lo na SWITCH_TO_USER função porque, dentro da função, "$@"estão os argumentos para as funções, não os dos scripts. O redirecionamento stderr para / dev / null é novamente para ocultar xtraces, e evalé para contornar um erro bash. Então chamamos a SWITCH_TO_USER função .

${_u+:} alias skip=:

Essa parte cancela o skipalias (substitui-o :pelo comando no-op), a menos que a $_uvariável esteja definida.

skip

Esse é o nosso skipapelido. Na primeira invocação, será apenas :(o não-op). Em subsequence re-invocações, será algo como: :||: << 'SWITCH_TO_USER root'.

echo test
a=foo
set a b

SWITCH_TO_USER root

Então, aqui, como exemplo, nesse ponto, reinvocamos o script como rootusuário, e o script restaurará o estado salvo e pularemos para esseSWITCH_TO_USER root linha e continuaremos.

O que isso significa é que ele deve ser escrito exatamente como stat, com SWITCH_TO_USER no início da linha e com exatamente um espaço entre argumentos.

A maior parte do estado, stdin, stdout e stderr será preservada, mas não os outros descritores de arquivo, porque sudonormalmente os fecha, a menos que explicitamente configurado para não. Então, por exemplo:

exec 3> some-file
SWITCH_TO_USER bob
echo test >&3

normalmente não funcionará.

Observe também que se você fizer:

SWITCH_TO_USER alice
SWITCH_TO_USER bob
SWITCH_TO_USER root

Isso só funciona se você tiver o direito de sudocomo alicee alicetem o direito de sudocomo bobe bobcomoroot .

Portanto, na prática, isso não é realmente útil. Usar em suvez de sudo(ou uma sudoconfiguração em que sudoautentica o usuário de destino em vez do chamador) pode fazer um pouco mais de sentido, mas isso ainda significa que você precisa saber as senhas de todos esses caras.

Stéphane Chazelas
fonte
você pode explicar o que o seu script está fazendo?
rag
@rag, você pediu ...
Stéphane Chazelas
1
Essa é a sua entrada para o ioshcc? Você deveria ter digitado apenas uma linha.
#
-2

O comando "sudo -u ram sh" pode ser usado para mudar para o usuário "ram", executando o comando "sh" ou shell. O comando "exit" retornará ao usuário original.

user45185
fonte