Como reescrever esta função para evitar a injeção de argumentos

5

Eu tenho uma função no meu arquivo .bashrc que me permite executar um script em um servidor remoto com argumentos via ssh.

Atualmente, a função contém:

function runMyScript {
    if [ $1 = "s3" ]
    then
        ssh -i "~/path/to/.pem" server.amazonaws.com "/home/ubuntu/scripts/script.sh ${1} ${2} ${3}"
    elif [ $1 = "ec2" ]
    then 
        ssh -i "~/path/to/.pem" server.amazonaws.com "/home/ubuntu/scripts/script.sh ${1} ${2}"
    else
        echo "** Run with s3 or ec2 options"
    fi
}

Então, eu seria capaz de chamar a função com runMyScript arg_1 arg_2 arg_3 ou runMyScript arg_1 arg_2 .

Como re-escrever esta função para torná-la mais segura e evitar possíveis injecções de argumentos?

    
por cpd 02.04.2017 / 17:41

2 respostas

3

How to avoid possible argument injection?

Descubra de que tipo de entradas você deseja passar e certifique-se de que os argumentos apenas os contenham.

No mínimo, certifique-se de que eles não contenham caracteres especiais para o shell remoto.

$1 não é um grande problema, uma vez que você compara com valores conhecidos. Embora você precise citar duas vezes, caso contrário, ele pode se expandir para várias palavras na comparação, e isso pode permitir passar valores engraçados por meio dele (algo como 1 -o x passaria a comparação).

Use if [ "$1" = "s3" ] ; then ... em vez disso.

Quanto a $2 e $3 , passá-las por ssh é basicamente o mesmo que passar para eval ou sh -c . Quaisquer substituições dentro deles serão expandidas no controle remoto.

Digamos que executaremos ssh somehost "echo $var" . Agora, se var contiver $(ls -ld /tmp) , a linha de comando remota será echo $(ls -ld /tmp) e o ls será executado no remoto. Duplicar a variável não ajudará na expansão do comando, mas aspas simples o farão. Com o comando escrito como ssh somehost "echo '$var'" , a expansão do comando não acontece.

As aspas simples dentro da variável ainda são um problema, pois elas terminarão as aspas simples, então, no mínimo, precisaremos verificar isso:

case "$var" in *"'"*) echo var has a single-quote; exit 1 ;; esac

Apesar de podermos procurar por quaisquer caracteres especiais que não queremos passar. Os sinais de dólar iniciam a maioria das expansões, os backticks iniciam a expansão de comandos e as citações também não confiam:

case "$var" in *[\'\"\$\']*) echo var has something funny; exit 1 ;; esac

Mas não tenho certeza se isso é tudo, então é melhor apenas colocar na lista de permissões os personagens que queremos passar. Se letras, dígitos, traços e sublinhados são suficientes:

case "$var" in *[!-_a-zA-Z0-9]*) echo var has something funny; exit 1 ;; esac

Então, isso pode ser um começo:

function runMyScript {
    case "$2" in *[!-_a-zA-Z0-9]*) exit 1 ;; esac
    case "$3" in *[!-_a-zA-Z0-9]*) exit 1 ;; esac

    if [ "$1" = "s3" ] ; then
        ssh somehost "script.sh '$1' '$2' '$3'"
    elif [ "$1" = "ec2" ] ; then 
        ssh somehost "script.sh '$1' '$2'"
    else
        echo "** Run with s3 or ec2 options"
    fi
}

Se você usou function runMyScript em vez de runMyScript() , não está executando um shell POSIX simples. Se for Bash, você pode reescrever as correspondências de padrões com [[...]] test, que suporta correspondências de expressões regulares .

Novas versões do Bash também têm a expansão ${var@Q} , que deve produzir o conteúdo de var cotado em um formato que pode ser reutilizado como entrada. Também pode ser útil, mas eu não tenho um novo bash o suficiente para testá-lo.

Além disso, não confie cegamente em mim para lembrar de todas as peculiaridades possíveis da linguagem shell.

    
por 02.04.2017 / 20:00
3

Como complemento à resposta do @ ikkachu:

Em comparação com a passagem de strings arbitrárias para eval ou sh -c , o problema é agravado aqui por:

  • o fato de você não saber qual shell será usado no host remoto para analisar essa linha de comando.
  • Qual será o código do idioma e, em particular, qual conjunto de caracteres será usado no host remoto. Seja o mesmo que no sistema local ou não.

Sua pergunta é principalmente um subconjunto de Como executar um comando simples arbitrário sobre o ssh sem conhecer o shell de login do usuário remoto?

Portanto, as respostas serão aplicadas aqui.

Em particular, se sshd no host remoto permitir a passagem de variáveis de ambiente que começam com LC_* (como muitas implantações de openssh), você pode fazer:

LC_ARG1=$1 LC_ARG2=$2 LC_ARG3=$3 ssh -o SendEnv='LC_*' host exec sh -c \
   \''exec my-script "$LC_ARG1" "$LC_ARG2" "$LC_ARG3"'\'

A sanitização da entrada é uma boa ideia, mas observe que você não deseja usar intervalos como [A-Z] , a menos que esteja na localidade C. Por exemplo, na maioria das localidades do sistema GNU, Ǒ está nesse intervalo e, por exemplo, no zh_HK.big5hkscs locale, esse caractere é codificado como 0x88 0x60 e você pode reconhecer 0x60 como a codificação ASCII (e BIG5HKSCS) do personagem backtick!

Melhor é listar os caracteres permitidos individualmente:

is_safe() case $1 in
  *[!-_.ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]*) false;;
esac

Tenha também cuidado com a string vazia, que precisará ser citada ( '' é suportado por todos os shells comuns). E cuidado com os argumentos que começam com - que algumas ferramentas podem ter como opções.

    
por 03.04.2017 / 21:27