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 sudo
ou su
costuma fazer).
Por exemplo:
$ sudo cp /usr/bin/env .
$ sudo chmod 4755 ./env
Agora eu tenho um env
comando 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
perl
possui 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 id
comandos sejam chamados com um ID do usuário real e o conjunto salvo seja 0 (embora se eu tivesse usado o meu em ./env
vez sudo
disso, 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.
bash
não tem como alterar os IDs do usuário. Portanto, mesmo se você tivesse um executável setuid para chamar seu bash
script, 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 0
usado 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 bash
expandir aliases muito cedo em seu processo de análise. Ter skip
um 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
) bash
com a _x
variável em seu ambiente (usamos env
aqui porque sudo
geralmente higieniza seu ambiente e não permite a passagem de envios arbitrários). Isso bash
avalia o conteúdo dessa $_x
variá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 +o
de 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 $_x
variá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 xtrace
configuraçõ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 bash
que 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 skip
alias (substitui-o :
pelo comando no-op), a menos que a $_u
variável esteja definida.
skip
Esse é o nosso skip
apelido. 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 root
usuá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 sudo
normalmente 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 sudo
como alice
e alice
tem o direito de sudo
como bob
e bob
comoroot
.
Portanto, na prática, isso não é realmente útil. Usar em su
vez de sudo
(ou uma sudo
configuração em que sudo
autentica 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.