"Executa qualquer comando que transmita dados não confiáveis ​​para comandos que interpretam argumentos como comandos"

18

Do manual do findutils:

Por exemplo, construções como esses dois comandos

# risky
find -exec sh -c "something {}" \;
find -execdir sh -c "something {}" \;

são muito perigosos. A razão para isso é que o '{}' é expandido para um nome de arquivo que pode conter ponto-e-vírgula ou outros caracteres especiais para o shell. Se, por exemplo, alguém criar o arquivo /tmp/foo; rm -rf $HOME, os dois comandos acima poderão excluir o diretório inicial de alguém.

Portanto, por esse motivo, não execute nenhum comando que transmita dados não confiáveis ​​(como nomes de arquivos) para comandos que interpretam argumentos como comandos a serem interpretados posteriormente (por exemplo, 'sh').

No caso do shell, há uma solução inteligente para esse problema:

# safer
find -exec sh -c 'something "$@"' sh {} \;
find -execdir sh -c 'something "$@"' sh {} \;

Não é garantido que essa abordagem evite todos os problemas, mas é muito mais segura do que substituir dados da escolha de um invasor pelo texto de um comando do shell.

  1. A causa do problema é find -exec sh -c "something {}" \;que a substituição de {}é sem aspas e, portanto, não é tratada como uma única sequência?
  2. Na solução find -exec sh -c 'something "$@"' sh {} \;,

    • primeiro {}é substituído, mas como {}não está entre aspas, "$@"também não tem o mesmo problema que o comando original? Por exemplo, "$@"será expandido para "/tmp/foo;", "rm", "-rf"e "$HOME"?

    • por que {}não é escapado ou citado?

  3. Você poderia dar outros exemplos (ainda com sh -c, ou sem, se aplicável; com ou sem o findque pode não ser necessário) onde o mesmo tipo de problema e solução se aplica e quais são exemplos mínimos para que possamos focar no problema e na solução com pouca distração possível? Veja Maneiras de fornecer argumentos para um comando executado por `bash -c`

Obrigado.

Tim
fonte
Relacionado: unix.stackexchange.com/questions/156008
Kusalananda

Respostas:

29

Isso não está realmente relacionado à citação, mas ao processamento de argumentos.

Considere o exemplo arriscado:

find -exec sh -c "something {}" \;
  • Esta é analisado pelo shell, e dividida em seis palavras: find, -exec, sh, -c, something {}(sem aspas mais), ;. Não há nada para expandir. O shell roda findcom essas seis palavras como argumentos.

  • Quando findencontra algo de processo, por exemplo foo; rm -rf $HOME, substitui {}com foo; rm -rf $HOME, e corre shcom os argumentos sh, -c, e something foo; rm -rf $HOME.

  • shagora vê -ce, como resultado, analisa something foo; rm -rf $HOME( o primeiro argumento sem opção ) e executa o resultado.

Agora considere a variante mais segura:

find -exec sh -c 'something "$@"' sh {} \;
  • O shell é executado findcom os argumentos find, -exec, sh, -c, something "$@", sh, {}, ;.

  • Agora, quando finddescobertas foo; rm -rf $HOME, ele substitui {}outra vez, e corre shcom os argumentos sh, -c, something "$@", sh, foo; rm -rf $HOME.

  • sh-ce analisa something "$@"como o comando a ser executado she foo; rm -rf $HOMEcomo parâmetros posicionais (a partir de$0 ), se expande "$@"para foo; rm -rf $HOME um valor único e é executado somethingcom o argumento único foo; rm -rf $HOME.

Você pode ver isso usando printf. Crie um novo diretório, insira-o e execute

touch "hello; echo pwned"

Executando a primeira variante da seguinte maneira

find -exec sh -c "printf \"Argument: %s\n\" {}" \;

produz

Argument: .
Argument: ./hello
pwned

enquanto a segunda variante, é executada como

find -exec sh -c 'printf "Argument: %s\n" "$@"' sh {} \;

produz

Argument: .
Argument: ./hello; echo pwned
Stephen Kitt
fonte
Na variante mais segura, por que {}não é escapado ou citado?
Tim
11
Geralmente não precisa ser citado; só precisaria ser citado em um shell onde tenha algum outro significado, e não acho que seja esse o caso de qualquer um dos principais shells em uso atualmente.
Stephen Kitt
De gnu.org/software/findutils/manual/html_mono/… : "Ambas as construções ( ;e {}) precisam ser escapadas (com um '\') ou citadas para protegê-las da expansão pelo shell." Você quer dizer que está incorreto para o bash?
Tim
3
Quero dizer que é incorreto para conchas em geral. Veja POSIX . Essa declaração específica no findutilsmanual remonta a pelo menos 1996 ... É claro que não faz mal citar {}, como '{}'ou "{}", mas não é necessário.
Stephen Kitt
@ Tim Você está enfrentando uma situação comum em que sua intuição ainda não é responsável pelo fato de que existem dois tipos muito diferentes de processamento de argumentos em andamento aqui: quando / como o shell analisa argumentos em uma linha de texto (que é onde a citação é importante) é diferente da passagem bruta do argumento do sistema operacional (onde as aspas são apenas caracteres regulares). No comando shell, find some/path -exec sh -c 'something "$@"' {} \;existem três camadas de processamento / aprovação de argumentos, duas da variedade shell e uma da variedade básica do sistema operacional bruto.
Mtraceur
3

Parte 1:

find apenas usa substituição de texto.

Sim, se você fez sem aspas, assim:

find . -type f -exec sh -c "echo {}" \;

e um invasor conseguiu criar um arquivo chamado ; echo owned, então ele executaria

sh -c "echo ; echo owned"

o que resultaria no shell sendo executado echoentão echo owned.

Mas se você adicionar aspas, o invasor poderá encerrá-las e, depois, colocar o comando malicioso criando um arquivo chamado '; echo owned:

find . -type f -exec sh -c "echo '{}'" \;

o que resultaria na concha de corrida echo '', echo owned.

(se você trocou as aspas duplas por aspas simples, o invasor também pode usar o outro tipo de aspas.)


Parte 2:

Em find -exec sh -c 'something "$@"' sh {} \;, {}não é inicialmente interpretado pelo shell, é executado diretamente com execve, portanto, adicionar aspas do shell não ajudaria.

find -exec sh -c 'something "$@"' sh "{}" \;

não tem efeito, pois o shell retira as aspas duplas antes de executar find.

find -exec sh -c 'something "$@"' sh "'{}'" \;

adiciona aspas que o shell não trata especialmente, portanto, na maioria dos casos, significa que o comando não fará o que você deseja.

Tê-lo expandir-se para /tmp/foo;, rm, -rf, $HOMEnão deve ser um problema, porque esses são argumentos para somethinge somethingprovavelmente não trata os seus argumentos como comandos para executar.


Parte 3:

Suponho que considerações semelhantes se apliquem a qualquer coisa que aceite entrada não confiável e a execute como (parte de) um comando, por exemplo, xargse parallel.

Mikel
fonte
xargssó é perigoso se -Iou -Jfor usado. Em operação normal, apenas anexamos argumentos ao final da lista, assim como -exec ... {} +faz.
Charles Duffy
11
( parallelpor outro lado, executa um shell por padrão sem solicitação explícita do usuário, aumentando a superfície vulnerável; esse é um assunto que tem sido objeto de muita discussão; consulte unix.stackexchange.com/questions/349483/… para obter uma explicação, e o link incluído para lists.gnu.org/archive/html/bug-parallel/2015-05/msg00005.html ).
Charles Duffy
@CharlesDuffy Você quer dizer " diminuir a superfície vulnerável". O ataque ilustrado pelo OP realmente não funciona por padrão com o GNU Parallel.
precisa
11
Sim, eu sei que você precisa dele para paridade no SSH - se você não gerou um parallelprocesso de "receptor" remoto na outra extremidade de qualquer soquete, capaz de executar um shell-less direto execv. O que é exatamente o que eu teria feito, se você estivesse implementando a ferramenta. Ter um programa não interativo se comporta de maneira diferente com base no valor atual de SHELLdificilmente é menos surpreendente.
Charles Duffy
11
Sim - sustento que pipes e redirecionamentos devem ser impossíveis, a menos que o usuário inicie explicitamente um shell; nesse momento, eles obtêm apenas o comportamento explícito do shell que iniciaram explicitamente. Se alguém pode esperar apenas o que um execvfornece, isso é mais simples e menos surpreendente, e uma regra que não muda nos locais / ambientes de tempo de execução.
Charles Duffy
3

1. A causa do problema é find -exec sh -c "something {}" \;que a substituição por {}não é citada e, portanto, não é tratada como uma única sequência?

Em certo sentido, mas citar não pode ajudar aqui . O nome do arquivo que é substituído no lugar de {}pode conter qualquer caractere, incluindo aspas . Qualquer que seja a forma de citação usada, o nome do arquivo pode conter o mesmo e "interromper" a citação.

2. ... mas como {}não está entre aspas, "$@"também não tem o mesmo problema que o comando original? Por exemplo, "$@"será expandido para "/tmp/foo;", "rm", "-rf", and "$HOME"?

Não. "$@"Expande para os parâmetros posicionais, como palavras separadas, e não os divide mais. Aqui, {}existe um argumento para findele mesmo e findpassa o nome do arquivo atual também como um argumento distinto para sh. Está diretamente disponível como uma variável no script do shell, não é processado como um próprio comando do shell.

... por que {}não é escapado ou citado?

Não precisa ser, na maioria das conchas. Se você executar fish, ele precisa ser: fish -c 'echo {}'imprime uma linha vazia. Mas não importa se você citar, o shell apenas removerá as aspas.

3. Você poderia dar outros exemplos ...

Sempre que você expande um nome de arquivo (ou outra string não controlada) como está dentro de uma string que é usada como algum tipo de código (*) , existe a possibilidade de execução arbitrária de comandos.

Por exemplo, isso expande $fdiretamente o código Perl e causará problemas se um nome de arquivo contiver aspas duplas. A citação no nome do arquivo terminará a citação no código Perl, e o restante do nome do arquivo poderá conter qualquer código Perl:

touch '"; print "HELLO";"'
for f in ./*; do
    perl -le "print \"size: \" . -s \"$f\""
done

(O nome do arquivo deve ser um pouco estranho, pois o Perl analisa todo o código antecipadamente, antes de executá-lo. Portanto, teremos que evitar um erro de análise.)

Enquanto isso passa com segurança por um argumento:

for f in ./*; do
    perl -le 'print "size: " . -s $ARGV[0]' "$f"
done

(Não faz sentido executar outro shell diretamente a partir de um shell, mas se você o fizer, é semelhante ao find -exec sh ...caso)

(* algum tipo de código inclui SQL, XKCD obrigatório: https://xkcd.com/327/ mais explicação: https://www.explainxkcd.com/wiki/index.php/Little_Bobby_Tables )

ilkkachu
fonte
1

Sua preocupação é precisamente a razão pela qual o GNU Parallel cita a entrada:

touch "hello; echo pwned"
find . -print0 | parallel -0 printf \"Argument: %s\\n\" {}

Isso não será executado echo pwned.

Ele executará um shell, de modo que, se você estender seu comando, de repente não terá uma surpresa:

# This _could_ be run without spawining a shell
parallel "grep -E 'a|bc' {}" ::: foo
# This cannot
parallel "grep -E 'a|bc' {} | wc" ::: foo

Para obter mais detalhes sobre a questão de gerar um shell, consulte: https://www.gnu.org/software/parallel/parallel_design.html#Always-running-commands-in-a-shell

Ole Tange
fonte
11
... dito isso, find . -print0 | xargs -0 printf "Argument: %s\n"é tão seguro (ou melhor, mais, porque com -print0um lida com nomes de arquivos com novas linhas corretamente); As citações de paralelo são uma solução alternativa para um problema que não existe quando nenhum shell está presente.
Charles Duffy
Mas assim que você adicionar | wcao comando, precisará pular os aros para torná-lo seguro. O GNU Parallel é seguro por padrão.
precisa
ITYM: ele tenta cobrir todas as peculiaridades da linguagem shell pelo processo complicado de citar tudo com cuidado. Isso não é tão seguro quanto armazenar dados corretamente em variáveis, não misturados ao código. Agora, se fosse convertê-lo automaticamente foo {} | wcem sh -c 'foo "$1" | wcsh {} `, isso poderia ser diferente.
Ilkkachu