Analisa corretamente os argumentos no script que se comportam como um shell chamado por meio do SSH

3

Eu tenho um servidor com um uso muito limitado que eu quero ser capaz de executar duas instruções muito específicas (e personalizadas) através do SSH. Para fazer isso, configurei o shell para esse usuário limitado como um script BASH personalizado que aceitará apenas os dois "comandos" .

Este é o /etc/passwd para esse usuário:

limited:x:1000:1000:,,,:/home/limited:/usr/sbin/limited_shell.bash

Este é o script limited_shell.bash (bem, o que eu tenho até agora, que não está funcionando corretamente):

#!/bin/bash

readonly RECORDER_SCRIPT="/usr/sbin/record.py"
echo "Verifying params: \: $1, \: $2"
shift

case $1 in
  [0-9]*)
    nc -z localhost $1 < /dev/null >/dev/null 2>&1
    result=$?
    if [[ $result -eq 0 ]]
    then
      echo "O"
    else
      echo "C"
    fi
    exit $result
    ;;
  record)
    $RECORDER_SCRIPT ${*:3}
    exit $?
    ;;
  *)
    exit 0
    ;;
  esac
exit 0

Como você pode deduzir do script, os dois comandos que eu quero limited_shell.bash aceitar são: um número e uma string "record". Quando limited_shell.bash é chamado com um número, ele retornará se corresponde a uma porta local aberta ou fechada. O outro comando allowed dispara uma chamada para um script python ( /usr/sbin/record.py ), que grava alguns vídeos de uma entrada. Esse segundo comando precisa ser chamado com argumentos extras e é aí que os problemas começam.

Quando, em outra máquina, eu tento executar remotamente o comando record ...

ssh [email protected] record -option1 foo -option2 bar

... o que realmente chega a limited_shell.bash é: -c 'record -option1 foo -option2 bar' (tecnicamente dois argumentos, um deles sendo -c e o segundo sendo toda a cadeia comando + args que eu deseja executar)

Pensei em mudar o primeiro argumento (o -c ) e dividir o segundo argumento (o comando record real com argumentos) pelo espaço, mas isso está sujo e me causará muitos problemas se um deles os parâmetros que desejo passar para o script record.py são uma string contendo um espaço.

Qual é o jeito certo de analisar os comandos? Tenho certeza de que tem que haver uma maneira melhor do que mudar e dividir.

    
por BorrajaX 18.11.2013 / 17:30

3 respostas

2

Seu script destina-se a implementar um shell . Isto é, um interpretador de linha de comando.

Quando você executa:

ssh host echo '$foo;' rm -rf '/*'

ssh (o cliente) concatena os argumentos (com caracteres ASCII SPC) e os envia para sshd . sshd chama o shell de login do usuário como:

exec("the-shell", "-c", "the command-line")

Está aqui:

exec("the-shell", "-c", "echo $foo; rm -rf /*")

E isso seria exatamente o mesmo se você tivesse executado:

ssh host echo '$foo;' 'rm -rf' '/*'
ssh host 'echo $foo;' "rm -rf /*"
ssh host 'echo $foo; rm -rf /*'

(o mais recente é o preferido, pois deixa mais claro o que está sendo feito).

Depende de the-shell decidir o que fazer com essa linha de comando. Por exemplo, um shell parecido com o Bourne expandiria $foo para o conteúdo da variável foo , consideraria ; como um separador de comando e expandiria /* para uma lista classificada de arquivos não ocultos em / .

Agora, no seu caso desde então, você pode fazer o que quiser. Mas como isso é um usuário restrito , você pode querer fazer o mínimo possível, por exemplo, não expandir variável, substituição de comando, globs, não permitir vários comandos, não redirecionar ...

Outra coisa a ter em conta é que bash~/.bashrc quando chamado sobre ssh mesmo quando não é interativo (como na interpretação de scripts). Então, você provavelmente deve evitar bash (ou pelo menos chamá-lo como sh ) ou certificar-se de que ~/.bashrc não seja escrito pelo usuário ou use a opção --norc .

Agora, desde que você define como a linha de comando é interpretada, basta dividir um espaço, nova linha ou guia:

#!/bin/sh -
[ "$1" = "-c" ] || exit
set -f
set -- $2
case $1 in
    (record) 
        shift
        exec /usr/sbin/record.py "$@"
        ;;
    ("" | *[!0-9]*)
        echo >&2 "command not supported"
        exit 1
        ;;
    (*)
        nc ...
        ;;
esac

Mas isso significa que record não poderá receber argumentos que contenham espaços, tabulações ou novas linhas ou que estejam vazios.

Se você quiser que eles consigam fazer isso, você precisa fornecer algum tipo de sintaxe de cotação.

zsh tem uma ferramenta de análise de cotações que pode ajudá-lo:

#! /bin/zsh -f
[ "$1" = "-c" ] || exit
set -- "${(Q@)${(Z:cn:)2}}"
case $1 in
    (record) 
        shift
        exec /usr/sbin/record.py "$@"
        ;;
    ("" | *[!0-9]*)
        echo >&2 "command not supported"
        exit 1
        ;;
    (*)
        nc ...
        ;;
esac

Isso suporta aspas duplas e as barras invertidas. Mas também consideraria coisas como $(foo bar) como um único argumento (mesmo que não o expandisse).

    
por 18.11.2013 / 22:03
2
#!/bin/bash
shopt -s extglob

# domain-specific language: provide a "record" command
record () {
    /usr/sbin/record.py "$@"
}

command=$2
set -- $command   # re-use the positional parameters
case $1 in
    record) 
        # let the shell parse the given command
        eval "$command"
        ;;
    +([0-9]))    
        nc ... # as above
        ;;
    *) exit 0 ;;
esac

Note que eu uso o padrão de globos estendidos +([0-9]) para corresponder a uma palavra com um ou mais dígitos. O padrão [0-9]* corresponde a uma palavra começando com um dígito, então 1foo corresponderia - suponho que não é isso que você deseja.

Cuidado o uso de eval é altamente perigoso. Considere adicionar mais verificação de comando ao comando que o usuário está lhe dando. O que acontece quando:

ssh [email protected] record -option1 foo -option2 \'rm -rf /\'
    
por 18.11.2013 / 18:13
0

Sugiro uma abordagem mais simples que requer um pouco mais de configuração, mas salva a escrita de um script complexo e propenso a erros.

Você pode declarar que uma chave pública de SSH tem permissão apenas para executar um determinado comando . Gere o par de chaves, copie a chave pública para o servidor e adicione uma diretiva command=… a ela. Faça isso para cada um dos pequenos números de comandos que você deseja permitir. Isso significa linhas como essa no arquivo ~/.ssh/authorized_keys no servidor:

command="/path/to/command1" ssh-rsa AAAA…== key1
command="/path/to/command2" no-pty ssh-rsa AAAA…== key2

Quando você executa o comando remoto, escolha a chave privada apropriada:

ssh -i ~/.ssh/key1.id_rsa server.example.com 'this is ignored'

Você pode escrever qualquer código shell na diretiva command . O comando fornecido pelo cliente está disponível na variável SSH_ORIGINAL_COMMAND .

    
por 19.11.2013 / 00:58