“comando sudo -s” executa o comando em um shell, mas curingas ou metacaracteres não funcionam

3

Ao usar sudo -s (abreviação da opção "--shell" ), é possível passar "sudo" um comando, caso em que ele irá executar o comando em um shell iniciado por "sudo" como o usuário alvo.

(Similarmente, sudo -i , também disponível como a opção "--login" , também inicia um shell e similarmente aceita um comando, que se comporta da mesma maneira.

Executar comandos no sudo por meio de um shell pode ser importante em muitos casos:

  • Ao usar curingas em um diretório ao qual o usuário atual não tem acesso, mas o root tem, o comando precisa ser executado em um shell raiz para que os curingas sejam expandidos adequadamente.
  • Executando um pipeline inteiro, muitos comandos encadeados em pipes ( | ).
  • Ao executar um shell interno, como for , if , etc. A execução de um pequeno "script" interno em linha sob um único sudo pode ser útil.

A documentação da opção "-s" diz (ênfase minha ):

Run the shell specified by the SHELL environment variable if it is set or the shell specified by the invoking user's password database entry. If a command is specified, it is passed to the shell for execution via the shell's -c option. If no command is specified, an interactive shell is executed. Note that most shells behave differently when a command is specified as compared to an interactive session; consult the shell's manual for details.

Em outras palavras, ao passar o comando sudo -s a, ele é passado para o shell usando a opção -c , que recebe uma string com um "script" e então continua a executá-lo como um script shell.

A documentação não vai muito mais longe em como usar esta opção, ou para apresentar exemplos, exceto para dizer "consulte o manual do shell para detalhes". Isto implica que o comando recebido é passado diretamente para a opção -c do shell. No entanto, como se vê, esse não é o caso.

Passando um script de shell com várias palavras falha:

$ sudo -s 'ls -ld /var/empty'
/bin/bash: ls -ld /var/empty: No such file or directory

A mensagem de erro implica que ele está tentando executar a string inteira como um comando simples ... Hmmm, ok, então talvez adicionar espaços funcionaria? Sim, esse é o caso:

$ sudo -s ls -ld /var/empty
drwxr-xr-x. 3 root root 18 Jul 12 21:48 /var/empty

Não é realmente assim que o -c do shell funciona ... Ah, bem, vamos tentar usar alguns metacaracteres, como ~ , que é um atalho para o diretório home, para ver como isso se comporta. Observe que ~ precisa ser citado, para evitar que o shell não-sudo o expanda (nesse caso, ele se expandiria para a home do usuário não raiz, em vez de /root , que é esperado):

$ sudo -s ls '~'
ls: cannot access '~': No such file or directory

Ok, então isso não funciona, e a saída do erro parece implicar que a expansão não está acontecendo, já que está preservando um literal ~ lá.

E os curingas? Não está funcionando:

$ sudo -s ls '/root/*.cfg'
ls: cannot access '/root/*.cfg': No such file or directory

Em ambos os casos, a execução do comando com $SHELL -c funciona bem. Nesse caso, $SHELL é bash, então:

$ sudo bash -c 'ls ~'
anaconda-ks.cfg
$ sudo bash -c 'ls /root/*.cfg'
/root/anaconda-ks.cfg

Uma exceção é que as variáveis parecem funcionar em sudo -s , como:

$ sudo -s echo '$HOME'
/root

Então:

  • O que está acontecendo aqui?
  • Por que caracteres curinga e metacaracteres, como ~ , não funcionam no comando passado para sudo -s ou sudo -i ?
  • Dado $SHELL -c usa uma única string com um script, mas sudo -s recebe vários argumentos, como o script é montado a partir dos argumentos?
  • O que é uma maneira confiável de executar comandos em um shell via sudo ?
por Filipe Brandenburger 27.11.2018 / 05:48

1 resposta

4

TL; DR: Ao tomar um comando nas opções "--shell" ou "--login", sudo irá escapar da maioria dos caracteres (usando escapes de barra invertida), incluindo todos os metacaracteres ( exceto por $ ), incluindo também espaços.

Isso quebra todos os casos de uso nos quais você deseja usar um shell, o que torna sudo -s inadequada para executar comandos do shell que precisam para serem executados como comandos do shell.

Em vez de sudo -s , use sudo sh -c '...' ou sudo bash -c '...' (ou seja qual for o% esperado$SHELL.) Em vez de sudo -i , use sudo bash -l -c '...' (assumindo que o shell do root seja bash novamente.)

Analisando a parte relevante do código fonte do sudo , há este snippet:

/* quote potential meta characters */
if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
    *dst++ = '\';
*dst++ = *src;

O fragmento é executado sobre cada caractere de cada argumento. Se o caractere não for alfanumérico, sublinhado, traço ou cifrão, ele será salvo com uma barra invertida.

Isso significa que os caracteres curinga * , ? , [...] , etc. serão ignorados para \* , \? , \[...\] .

Também metacaracteres, como ~ , ; , | , etc., serão escapados para \~ , \; , \| .

Uma string com espaços ls /root será ignorada para ls\ /root .

Por algum motivo, $ é uma exceção no escape, é por isso que sudo -s echo '$HOME' funciona, pois o $ será mantido sem escape. (Observe que isso só funciona para variáveis, e nem mesmo para a forma ${HOME} , caso em que as chaves serão escapadas, o que quebrará a expressão.)

As conseqüências dessa citação é que a maioria dos casos que exigem executar um shell como root não pode se beneficiar do formato sudo -s <command> :

  • Os wilcards não serão expandidos, pois terão escape e não funcionarão como curingas.
  • Os pipelines não funcionarão, pois o canal | será ignorado e também não funcionará.
  • Comandos como for e if normalmente precisam usar ; , que será escapado e não funcionará também. Uma nova linha seria uma alternativa, mas também não parece haver uma maneira de introduzir uma nova linha sem escape.

Em suma, o formulário sudo -s <command> parece funcionar apenas para casos que não envolvem curingas, pipelines, scriptlets, etc. Mas, em seguida, você normalmente não precisa de um shell para executar esses comandos! Apenas executar sudo <command> sem o -s é normalmente suficiente para esses casos.

Não está claro qual a motivação para a implementação do sudo. Talvez para manter alguma consistência entre o tratamento de comandos ao adicionar ou remover o -s . Talvez também porque -i preceda -s e, nesse caso, há uma pequena diferença em como as variáveis de ambiente são definidas ...

SOLUÇÃO ALTERNATIVA: A solução é executar o -c explicitamente.

Isso envolve saber que $SHELL é para o usuário de destino (que pode não corresponder ao usuário atual, portanto, usar $SHELL diretamente nem sempre é correto.)

Por exemplo, em vez de sudo -s ls -l '/root/*.cfg' , use:

$ sudo bash -c 'ls -l /root/*.cfg'

E, em vez de sudo -i ls -l '~' , use:

$ sudo bash -l -c 'ls -l ~'

(O argumento -l do bash cria um shell de "login", que é equivalente ao criado por sudo -i .)

    
por 27.11.2018 / 05:48

Tags