Estou escrevendo um script de shell que muda seu comportamento com base no número de parâmetros posicionais passados
script.sh
if [ $# -eq 1 ]; then
if [ -f "$1" ]; then
validate='validate <"$1"'
else
validate='validate <<<"$1"'
fi
else
IFS=$'\n'
validate="validate <<<'${*}'"
fi
eval "$validate" | pull_data
Ambas validate
e pull_data
são funções Bash que leem do stdin e escrevem no stdout
Se for o primeiro caso [ $# -eq 1 ]
, acredito que o código esteja seguro contra injeção. Como a string é entre aspas simples, a expansão de parâmetros só acontece depois que eval é chamado. Não consigo pensar em uma string que possa resultar em injeção de código aqui.
Emitir
O problema surge com a else
condição. Aqui, cada parâmetro posicional é expandido em uma nova linha. Por exemplo
./script.sh "string_with_'_in_its_name" "code injected'"
A citação dentro de um dos primeiros parâmetros faz com que os novos parâmetros subsequentes sejam interpretados como comandos. Similarmente, isso também resulta em uma vulnerabilidade de injeção:
./script.sh valid_input \''$(code injected)'\'
Contexto
Estou usando eval para evitar repetir a validate ... | pull_data
construção em cada condição if. O script real tem muito mais condições, e usar eval o torna mais legível.
Qualquer conselho sobre como refatorar este script para evitar o problema de injeção e, ao mesmo tempo, manter a legibilidade seria muito apreciado!
No primeiro caso, o script avaliaria a string
validate <"$1"
, com o contexto eval expandindo"$1"
. É o mesmo que tervalidate <"$1"
sem um eval, e tudo bem. O segundo caso é similar.No terceiro caso, o script se expande
$*
antes de passá-lo para eval (junto com as aspas simples), então quaisquer aspas simples nos próprios argumentos quebrarão suas aspas, permitindo, por exemplo, injeções de comando por meio de substituições de comando. (Por exemplo, se o script for executado comoscript.sh "'\$(date >&2)'" x
, a expansão será"validate <<<''$(date >&2) x''"
, e eval executará odate
comando com facilidade.)Mas por que fazer dessa forma no terceiro caso, oposto aos outros dois? O contexto eval vê as mesmas variáveis que o contexto principal, então
IFS
também, não apenas os parâmetros posicionais. Apenas deixe o contexto eval fazer a expansão aqui também:Embora você tenha mencionado querer evitar repetir
validate
epull_data
, mas você está repetindovalidate
em cada branch. Já que tudo o que você quer fazer é imprimir algo paravalidate | pull_data
, você não precisa de eval para isso. Apenas faça separadamente, por exemplo, com outra função:(É claro que você também pode simplesmente dispensar
if ...; fi | validate | pull_data
uma função, mas pode ficar melhor com o encapsulamento.)