como posso citar uma expansão variável dentro de uma string para evitar a divisão de palavras?

9
$ myvar="/path to/my directory"
$ sudo bash -c "cd $myvar"

Nesse caso, como posso citar $myvarpara evitar a divisão de palavras devido aos espaços em branco no valor de myvar?

Tim
fonte
4
Não importa como você resolva isso, ainda não fará muito. O cdúnico tem efeito dentro do bash -cshell.
Kusalananda
Estou apenas dando um exemplo mínimo para a pergunta, não é uma solução funcional para o meu problema real, que é unix.stackexchange.com/a/269080/674, mais que na cadeia de comando fornecida sudo bash -c, tenho uma expansão variável para um nome de caminho que pode conter espaços em branco.
Tim

Respostas:

13

Não há divisão de palavras (como no recurso que divide variáveis ​​em expansões não citadas) nesse código, pois $myvarnão é entre aspas.

Existe, no entanto, uma vulnerabilidade de injeção de comando, que $myvaré expandida antes de ser passada para bash. Portanto, seu conteúdo é interpretado como código bash!

Os espaços lá farão com que vários argumentos sejam transmitidos cd, não por causa da divisão de palavras , mas porque eles serão analisados ​​como vários tokens na sintaxe do shell. Com um valor de bye;reboot, isso será reiniciado! ¹

Aqui, você deseja:

sudo bash -c 'cd -P -- "$1"' bash "$myvar"

(onde você passa o conteúdo $myvarcomo o primeiro argumento desse script embutido; observe como $myvare $1foram citados para o respectivo shell para impedir a divisão de palavras IFS (e globbing)).

Ou:

sudo MYVAR="$myvar" bash -c 'cd -P -- "$MYVAR"'

(onde você passa o conteúdo $myvarem uma variável de ambiente).

Obviamente, você não conseguirá nada útil executando apenas cd esse script embutido (além de verificar se rootpode cdentrar nele). Presumivelmente, você deseja que esse script chegue cdlá e faça outra coisa como:

sudo bash -c 'cd -P -- "$1" && do-something' bash "$myvar"

Se a intenção era usar sudopara poder cdentrar em um diretório ao qual você não tem acesso, isso não pode realmente funcionar.

sudo sh -c 'cd -P -- "$1" && exec bash' sh "$myvar"

iniciará um interativo bashcom seu diretório atual em $myvar. Mas esse shell estará rodando como root.

Você poderia fazer:

sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"

Para obter uma interatividade sem privilégios bashcom o diretório atual $myvar, mas se você não tiver as permissões para cdentrar nesse diretório em primeiro lugar, não poderá fazer nada nesse diretório, mesmo que seja seu diretório de trabalho atual.

$ myvar=/var/spool/cron/crontabs
$ sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"
bash-4.4$ ls
ls: cannot open directory '.': Permission denied

Uma exceção seria se você tiver permissão de pesquisa para o próprio diretório, mas não para um dos componentes do diretório de seu caminho:

$ myvar=1/2
$ mkdir -p "$myvar"
$ chmod 0 1
$ cd 1/2
cd: permission denied: 1/2
$ sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"
bash-4.4$ pwd
/home/stephane/1/2
bash-4.4$ mkdir 3
bash-4.4$ ls
3
bash-4.4$ cd "$PWD"
bash: cd: /home/stephane/1/2: Permission denied

¹ estritamente falando, para valores $myvariguais $(seq 10)(literalmente), haveria divisão de palavras, é claro, após a expansão dessa substituição de comando pelo bash shell, iniciada comoroot

Stéphane Chazelas
fonte
4

Há uma nova mágica (citando o 4.4 do bash) que permite que você faça mais diretamente o que deseja. Dependendo do contexto maior, o uso de uma das outras técnicas pode ser melhor, mas funciona nesse contexto limitado.

sudo bash -c "cd ${myvar@Q}; pwd"
Seth Robertson
fonte
4

Se você não pode confiar no conteúdo $myvar, a única abordagem razoável é usar o GNU printfpara criar uma versão escapada dele:

#!/bin/bash

myvar=$(printf '%q' "$myvar")
bash -c "echo $myvar"

Como a printfpágina do manual diz:

%q

ARGUMENT é impresso em um formato que pode ser reutilizado como entrada do shell, escapando a caracteres não imprimíveis com a $''sintaxe proposta do POSIX .

Toby Speight
fonte
Veja minha resposta para uma versão portátil disso.
R .. GitHub Pare de ajudar o gelo
O GNU printfserá invocado apenas nos sistemas GNU e se $(printf '%q' "$myvar")for executado em um shell onde printfnão está embutido. A maioria das conchas tem printfconstruído hoje em dia. Nem todos suportam um %qpensamento e, quando o fazem, citam algo em um formato suportado pelo shell correspondente, que pode não ser necessariamente o mesmo que o entendido por bash. Na verdade, não bashé printfpossível citar as coisas corretamente, mesmo para alguns valores de $myvarem alguns locais.
Stéphane Chazelas
Com bash-4.3pelo menos, ainda é uma vulnerabilidade de injeção de comando myvar=$'\xa3``reboot`\xa3`' em um local zh_HK.big5hkscs, por exemplo.
Stéphane Chazelas
3

Primeiro de tudo, como outros observaram, seu cdcomando é inútil, pois acontece no contexto de um shell que sai imediatamente. Mas, em geral, a questão faz sentido. O que você precisa é uma maneira de citar uma string arbitrária para que ela possa ser usada em um contexto em que será interpretada como uma string entre aspas. Eu tenho um exemplo disso na minha página de truques sh :

quote () { printf %s\\n "$1" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/" ; }

Com esta função, você pode:

myvar="/path to/my directory"
sudo bash -c "cd $(quote "$myvar")"
R .. GitHub PARE DE AJUDAR O GELO
fonte
3

O que Stéphane Chazelas sugere é definitivamente a melhor maneira de fazer isso. Vou apenas fornecer uma resposta sobre como citar com segurança a sintaxe de expansão de parâmetro, em vez de usar um sedsubprocesso, como na resposta da R ..

myvar='foo \'\''"bar'

quote() {
  printf "'%s'" "${1//\'/\'\\\'\'}"
}

printf "value: %s\n" "$myvar"
printf "quoted: %s\n" "$(quote "$myvar")"
bash -c "printf 'interpolated in subshell script: %s\n' $(quote "$myvar")"

A saída desse script é:

value: foo \'"bar
quoted: 'foo \'\''"bar'
interpolated in subshell script: foo \'"bar
JoL
fonte
1

Sim, há divisão de palavras.
Bem, tecnicamente, a linha de comando é dividida no bash analisando a linha.

Vamos primeiro citar corretamente:

A mais simples (e insegura) das soluções para que o comando funcione como eu acredito que você desejará é:

bash -c "cd \"$myvar\""

Vamos confirmar o que acontece (supondo myvar="/path to/my directory"):

$ bash -c "printf '<%s>\n' $myvar"
<./path>
<to/my>
<directory>

Como você pode ver, a cadeia de caminho foi dividida em espaços. E:

$ bash -c "printf '<%s>\n' \"$myvar\""
<./path to/my directory>

Não está dividido.

No entanto, o comando (conforme escrito) é inseguro. Melhor uso:

$ bash -c "cd -P -- \"$myvar\"; pwd"
/home/user/temp/path to/my directory

Isso protegerá o CD contra arquivos que começam com um hífen (-) ou seguem os links que podem levar a problemas.

Isso funcionará se você puder confiar que a string $myvaré um caminho.
No entanto, a "injeção de código" ainda é possível, pois você está "executando uma sequência":

$ myvar='./path to/my directory";date;"true'
$ bash -c "printf '<%s> ' \"$myvar\"; echo"
<./path to/my directory> Tue May  8 12:25:51 UTC 2018

Você precisa de uma citação mais forte da string, como:

$ bash -c 'printf "<%s> " "$1"; echo' _ "$myvar"
<./path to/my directory";date -u;"true>

Como você pode ver acima, o valor não foi interpretado, mas apenas usado como "$1'.

Agora podemos (com segurança) usar o sudo:

$ sudo bash -c 'cd -P -- "$1"; pwd' _ "$myvar"
_: line 0: cd: ./path to/my directory";date -u;"true: No such file or directory

Se houver vários argumentos, você pode usar:

$ sudo bash -c 'printf "<%s>" "$@"' _ "$val1" "$val2" "$val3"
Isaac
fonte
-2

As aspas simples não funcionarão aqui, pois sua variável não será expandida. Se você tem certeza de que a variável do seu caminho é higienizada, a solução mais simples é simplesmente adicionar aspas à expansão resultante, uma vez que ela esteja no novo shell bash:

sudo bash -c "cd \"$myvar\""
cunninghamp3
fonte
2
Ainda uma vulnerabilidade de injeção de comando, como por valores de $myvarcomo $(reboot)ou"; reboot; #
Stéphane Chazelas
1
o uso sudo bash -c "cd -P -- '$myvar'"limitaria o problema a apenas valores $myvar que contenham caracteres de aspas simples, que seriam mais fáceis de limpar.
Stéphane Chazelas