Como posso garantir com segurança que uma variável contém apenas um nome de arquivo válido?

7

Dado o script abaixo, como posso garantir que o argumento contenha apenas um nome de arquivo válido dentro de /home/charlesingalls/ e não um caminho ( ../home/carolineingalls/ ) ou caractere curinga, etc?

Eu quero apenas que o script possa excluir um único arquivo do diretório codificado fornecido. Este script será executado como um usuário privilegiado.

#!/bin/bash

rm -f /home/charlesingalls/"$1"
    
por Aaron Cicali 19.07.2016 / 04:17

3 respostas

7

Se você quiser excluir apenas um arquivo em /home/charlesingalls (e não um arquivo em um subdiretório), é fácil: basta verificar se o argumento não contém / .

case "$1" in
  */*) echo 1>&2 "Refusing to remove a file in another directory"; exit 2;;
  *) rm -f /home/charlesingalls/"$1";;
esac

Isso executa rm mesmo se o argumento for . ou .. ou vazio, mas, nesse caso, rm será inutilmente incapaz de excluir um diretório.

Caracteres curinga não são relevantes aqui, já que nenhuma expansão de caractere curinga é executada.

Isso é seguro mesmo na presença de links simbólicos: se o arquivo é um link simbólico, o link simbólico (que está em /home/charlesingalls ) é removido e o destino desse link não é afetado.

Observe que isso pressupõe que /home/charlesingalls não pode ser movido ou alterado. Isso deve ser ok se o diretório for codificado no script, mas se for determinado a partir de variáveis, a determinação poderá não ser mais válida no momento em que o comando rm for executado.

Baseado no informações adicionais de que o argumento é um nome de host virtual, você deve fazer lista de permissões em vez de lista negra: verifique se o nome é um nome de host virtual sensato, em vez de apenas banir barras. Verifico se o nome começa com uma letra minúscula ou dígito e se não contém caracteres além de letras minúsculas, dígitos, pontos e traços.

LC_CTYPE=C LC_COLLATE=C
case "$1" in
  *[!-.0-9a-z]*|[!0-9a-z]*) echo >&2 "Invalid host name"; exit 2;;
  *) rm -f /home/charlesingalls/"$1";;
esac
    
por 19.07.2016 / 18:22
10

Esta resposta assume que $1 tem permissão para incluir subdiretórios. Se você estiver interessado no caso mais simples em que $1 deve ser um nome de diretório simples, consulte uma das outras respostas.

Os caracteres curinga não são expandidos quando estão entre aspas duplas. Como $1 está entre aspas, os curingas não são um problema.

O ../ e os links simbólicos podem obscurecer o local real de um arquivo. Abaixo são mostrados os testes para determinar se o arquivo é realmente, não apenas aparentemente, sob o caminho que queremos.

Sistemas mais recentes: usando realpath

Quanto a descobrir se o arquivo é realmente se o arquivo está realmente com /home/charlesingalls/ ou não, você pode usar realpath :

realpath --relative-base=/home/charlesingalls/ "/home/charlesingalls/$1"  | grep -q '^/' && exit 1

O acima é executado exit 1 se o arquivo especificado por $1 estiver em qualquer lugar diferente do diretório /home/charlesingalls/ . realpath canonicaliza todo o caminho, eliminando os links simbólicos e ../ .

realpath faz parte do GNU coreutils e deve estar disponível em qualquer sistema Linux.

realpath requer GNU coreutils 8.15 (jan 2012) ou melhor .

Exemplos

Para demonstrar como o caminho real segue ../ para determinar a localização real de um arquivo (por exemplo, a opção -q para grep é omitida para que a saída real do grep seja visível):

$ touch /tmp/test
$ realpath --relative-base=$HOME "$HOME/../../tmp/test" | grep '^/' && echo FAIL
/tmp/test
FAIL

Para demonstrar como segue os links simbólicos:

$ ln -s /tmp/test ~/test
$ realpath --relative-base=$HOME "$HOME/test" | grep '^/' && echo FAIL
/tmp/test
FAIL

Sistemas mais antigos: usando readlink -e

readlink também é capaz de cononicalizar um caminho, seguindo ambos os links simbólicos e ../ :

readlink -e "$HOME/test" | grep -q "^$HOME" || exit 1

Usando os mesmos arquivos de exemplo:

$ readlink -e "$HOME/../../tmp/test" | grep "$HOME" || echo FAIL
FAIL
$ readlink -e "$HOME/test" | grep "^$HOME" || echo FAIL
FAIL

Além de estar disponível em sistemas GNU mais antigos, versões do readlink estão disponíveis no BSD.

    
por 19.07.2016 / 04:38
2

Se você quiser proibir caminhos completamente, a maneira mais simples é testar se a variável contém uma barra ( / ). No bash:

if [[ "$1" = */* ]] ; then...

Isso bloqueará todos os caminhos, incluindo foo/bar . Você poderia testar .. , mas isso deixaria a possibilidade de links simbólicos apontando para diretórios fora do caminho de destino.

Se você deseja permitir apenas a exclusão de um único arquivo, não acredito que deva usar rm -r .

Além disso, dependendo do que você está fazendo, você pode usar as permissões de arquivo do sistema para permitir apenas a exclusão de arquivos que o usuário possa excluir. Algo parecido com isto:

su charlesingalls -c "rm /home/charlesingalls/'$1'"

Embora como @Gilles comentou, isso tem um problema de citação: ele falhará se $1 contiver uma única citação, portanto a variável deve ser testada para essa primeira (por exemplo, if [[ "$1" = *\'* ]] ; then fail... ou pela lista de permissões de um conjunto sensato de caracteres), ou o nome do arquivo passou por uma variável de ambiente com, por exemplo,

file="$1" su charlesingalls -c 'rm "/home/charlesingalls/$file"'
    
por 19.07.2016 / 11:37