É perigoso executar eco sem aspas?

11

Já vi alguns tópicos semelhantes, mas eles estão se referindo a não citar variáveis, o que eu sei que poderia levar a resultados indesejados.

Eu vi esse código e queria saber se seria possível injetar algo a ser executado quando essa linha de código for executada:

echo run after_bundle

Viktor Fonic
fonte
Eu me deparei com isso quando tinha: target = "*** LIVE SERVER ***"; alvo de eco: $ target; eo *** expandiu-se para uma pasta listagem ... 😬
Matt Parkins

Respostas:

17

Para o caso específico

echo run after_bundle

não é necessário citar. Nenhuma citação é necessária porque o argumento echosão cadeias estáticas que não contêm expansões variáveis ​​ou substituições de comandos etc. Elas são "apenas duas palavras" (e, como Stéphane aponta , elas são construídas adicionalmente a partir do conjunto de caracteres portáteis ).

O "perigo" surge quando você lida com dados variáveis ​​que o shell pode expandir ou interpretar. Nesses casos, deve-se tomar cuidado para que o shell faça a coisa correta e que o resultado seja o que se pretende.

As duas perguntas a seguir contêm informações relevantes sobre isso:


echoàs vezes é usado para "proteger" comandos potencialmente prejudiciais nas respostas deste site. Por exemplo, posso mostrar como remover arquivos ou mover arquivos para um novo destino usando

echo rm "${name##*/}.txt"

ou

echo mv "$name" "/new_dir/$newname"

Isso produziria comandos no terminal em vez de realmente remover ou renomear arquivos. O usuário pode então inspecionar os comandos, decidir que eles parecem bem, remover o echoe executar novamente.

Seu comando echo run after_bundlepode ser uma instrução para o usuário ou um código "comentado" que é muito perigoso para ser executado sem conhecer as consequências.

Usando echoassim, é necessário saber o que o comando modificado faz e é preciso garantir que o comando modificado seja realmente seguro (potencialmente não seria se contivesse redirecionamentos e usá-lo em um pipeline não funcione etc.)

Kusalananda
fonte
Adicionar aspas não é suficiente para saber o que um shell faria, no entanto - assim como você não pode dizer que isso echo rm "first file.txt" "second file.txt"é de alguma forma diferente echo rm "first" "file.txt" "second" "file.txt", a saída de ambos é a mesma. Se você deseja gerar um comando shell como saída, deve-se usar printf '%q ' rm "first file.txt" "second file.txt"; echoou algo equivalente que gere novamente a citação sintática que seja avaliada como argvpassada.
Charles Duffy
@CharlesDuffy Eu realmente espero que ninguém copie e cole a saída de depuração e a execute no shell!
Kusalananda
1
Gerar comandos shell e depois canalizá-los para shnão é exatamente um padrão incomum, e ver as pessoas perguntarem "por que foofunciona quando eu o executo em uma linha de comando, mas esse script que emite a string exata echona frente da linha não? " acontece o tempo todo aqui. Mais precisamente, a saída de depuração não é útil se ocultar seus erros - e se seus erros estiverem relacionados à citação, echoeles não serão revelados.
Charles Duffy
27

Apenas uma nota extra em cima da boa resposta de @ Kusalananda .

echo run after_bundle

é bom porque nenhum dos caracteres nesses 3 argumentos¹ passou para echoconter caracteres especiais para o shell.

E (o ponto extra que quero destacar aqui) não há localidade do sistema em que esses bytes possam ser traduzidos para caracteres especiais para o shell.

Todos esses caracteres estão no que o POSIX chama de conjunto de caracteres portátil . Esses caracteres devem estar presentes e codificados da mesma forma em todos os conjuntos de caracteres em um sistema POSIX².

Portanto, essa linha de comando será interpretada da mesma forma, independentemente da localidade.

Agora, se começarmos a usar caracteres fora desse conjunto de caracteres portátil, é uma boa ideia citá-los, mesmo que não sejam especiais para o shell, porque em outro local, os bytes que os constituem podem ser interpretados como caracteres diferentes que podem se tornar especial para a concha. Observe que, independentemente de você estar usando echoou qualquer outro comando, o problema não é com echomas como o shell analisa seu código.

Por exemplo, em um UTF-8:

echo voilà | iconv -f UTF-8 -t //TRANSLIT

Isso àé codificado como 0xc3 0xa0. Agora, se você tiver essa linha de código em um script de shell e o script de shell for invocado por um usuário que use um código de idioma cujo conjunto de caracteres não seja UTF-8, esses dois bytes poderão gerar caracteres muito diferentes.

Por exemplo, em um fr_FR.ISO8859-15código de idioma, um código de idioma típico do francês que usa o conjunto de caracteres de byte padrão que cobre o idioma francês (o mesmo usado para a maioria dos idiomas da Europa Ocidental, incluindo o inglês), que 0xc3 byte é interpretado como o Ãcaractere e 0xa0 como não quebrando o caráter do espaço.

E em alguns sistemas como o NetBSD³, esse espaço sem quebra é considerado como um caractere em branco ( isblank()ele retorna verdadeiro, é correspondido por [[:blank:]]) e shells como, bashportanto, o tratam como um delimitador de token em sua sintaxe.

Isso significa que em vez de correr echocom $'voil\xc3\xa0'como argumento, eles executá-lo com $'voil\xc3'como argumento, o que significa que não será impresso voilàcorretamente.

Ele fica muito pior com conjuntos de caracteres chineses como BIG5, BIG5-HKSCS, GB18030, GBK que têm muitos personagens cujas codificação contém a mesma codificação como |, `, \(para citar o pior) (também que SJIS ridícula, aka Microsoft Kanji, excepto em ¥vez de \, mas ainda tratado como \pela maioria das ferramentas, pois está codificado como 0x5c).

Por exemplo, se em um zh_CN.gb18030local chinês, você escreve um script como:

echo  reboot

Esse script será produzido 詜 rebootem um 唰 rebootcódigo de idioma usando GB18030 ou GBK, em um código de idioma usando BIG5 ou BIG5-HKSCS, mas em um código de idioma C usando ASCII ou em um código de idioma usando ISO8859-15 ou UTF-8, será rebootexecutado porque a codificação GB18030 de é 0xd4 0x7c e 0x7c é a codificação |em ASCII, portanto, acabamos executando:

 echo �| reboot

(que representa no entanto o byte 0xd4 é renderizado no código do idioma). Exemplo usando o menos prejudicial em unamevez de reboot:

$ echo $'echo \u8a5c uname' | iconv -t gb18030 > myscript
$ LC_ALL=zh_CN.gb18030 bash ./myscript | sed -n l
\324| uname$
$ LC_ALL=C bash ./myscript | sed -n l
Linux$

( unamefoi executado).

Portanto, meu conselho seria citar todas as strings que contêm caracteres fora do conjunto de caracteres portáteis.

No entanto, observe que, como a codificação de \e `é encontrada na codificação de alguns desses caracteres, é melhor não usar \or "..."ou $'...'(dentro do qual `e / ou \ainda são especiais), mas '...'sim citar caracteres fora do conjunto de caracteres portátil.

Não conheço nenhum sistema que possua uma localidade em que o conjunto de caracteres possua qualquer caractere (que não seja 'ele próprio, é claro) cuja codificação contenha a codificação de ', portanto esses '...'devem ser definitivamente os mais seguros.

Observe que vários shells também suportam uma $'\uXXXX'notação para expressar caracteres com base em seu ponto de código Unicode. Em shells como zshe bash, o caractere é inserido codificado no conjunto de caracteres da localidade (embora possa causar comportamentos inesperados se esse conjunto de caracteres não tiver esse caractere). Isso permite que você evite inserir caracteres não ASCII no seu código de shell.

Então acima:

echo 'voilà' | iconv -f UTF-8 -t //TRANSLIT
echo '詜 reboot'

Ou:

echo $'voil\u00e0'
echo $'\u8a5c reboot'

(com a ressalva, ele pode interromper o script quando executado em locais que não possuem esses caracteres).

Ou melhor, já que \também é especial para echo(ou pelo menos algumas echo implementações, pelo menos as compatíveis com Unix):

printf '%s\n' 'voilà' | iconv -f UTF-8 -t //TRANSLIT
printf '%s\n' '詜 reboot'

(observe que \também é especial no primeiro argumento para printf, portanto, caracteres não-ASCII também são melhor evitados no caso de conterem a codificação de \).

Observe que você também pode fazer:

'echo' 'voilà' | 'iconv' '-f' 'UTF-8' '-t' '//TRANSLIT'

(isso seria um exagero, mas poderia lhe dar alguma tranqüilidade se você não tiver certeza de quais caracteres estão no conjunto de caracteres portáteis)

Além disso, certifique-se de nunca usar a `...`forma antiga de substituição de comando (que introduz outro nível de processamento de barra invertida), mas use em $(...)vez disso.


¹ tecnicamente, echotambém é passado como argumento para o echoutilitário (para dizer como foi chamado), é o argv[0]e argcé 3, embora na maioria dos shells hoje em dia echoesteja embutido, de modo que exec()um /bin/echoarquivo com uma lista de 3 argumentos seja simulado pelo Concha. Também é comum considerar a lista de argumentos como iniciando com o segundo ( argv[1]para argv[argc - 1]), pois é sobre isso que os comandos atuam principalmente.

² uma exceção notável por ser o ja_JP.SJISlocal ridículo dos sistemas FreeBSD cujo charset não tem \nem ~caráter!

³ observe que, embora muitos sistemas (FreeBSD, Solaris, e não os GNU) considerem U + 00A0 como um local [[:blank:]]UTF-8, poucos o fazem em outros locais como os que usam ISO8859-15, possivelmente para evitar esse tipo de problema.

Stéphane Chazelas
fonte
No seu primeiro parágrafo, você nos diz "... dos caracteres nesses 3 argumentos passados ​​para echo...", apenas conto 2 argumentos sendo passados ​​para o comando echo, os argumentos que posso contar são rune after_bundle, gostaria de explicar como você contou e chegou a 3 argumentos?
Ferrybig 13/05/19
1
@ViktorFonic, veja editar sobre o número de argumentos (e com o qual o principal problema não está echo). Veja (exec -a foo /bin/echo --help)em um sistema GNU e com o shell GNU como passar um primeiro argumento arbitrário para o /bin/echoutilitário.
Stéphane Chazelas
@Ferrybig Veja a edição de Stephane, nota de rodapé 1. Os argumentos a serem comandados no estilo C usual são uma matriz de argumentos, com argv [0] sendo o próprio nome do executável. $0Parâmetros meio e posicionais em conchas.
Sergiy Kolodyazhnyy 13/05/19
Existem 373 codificações iconvnas quais ESCé convertido em '. Tente (como exemplo):printf '\x1b'|iconv -f utf8 -t IBM-937|xxd
NotAnUnixNazi
Existem 173 codificações nas quais algum ponto de código (que não seja ESC) é convertido em a '. Tente printf '\u2804' | iconv -f utf8 -t BRF | xxd. Existem codificações nas quais existem muitos pontos de código que se tornam '. Cerca de 8695 pontos de código no UCS-4 se tornam '. Tente printf '\U627' | iconv -cf utf-8 -t UCS-4. Várias codificações (37) convertem o caractere 0x127 em a '. Tenteprintf '\U127' | iconv -cf utf8 -t UCS2 |xxd
NotAnUnixNazi