web-dev-qa-db-ja.com

変数に有効なファイル名のみが含まれていることを確認するにはどうすればよいですか?

以下のスクリプトを前提として、引数に/home/charlesingalls/内の有効なファイル名のみが含まれ、パス(../home/carolineingalls/)やワイルドカードなどが含まれていないことをどのように確認できますか?

スクリプトが特定のハードコードされたディレクトリから単一のファイルを削除できるようにしたいだけです。このスクリプトは、特権ユーザーとして実行されます。

#!/bin/bash

rm -f /home/charlesingalls/"$1"
8
Aaron Cicali

この回答は、$1にサブディレクトリを含めることが許可されていることを前提としています。 $1が単純なディレクトリ名である必要があるより単純なケースに興味がある場合は、他の回答の1つを参照してください。


二重引用符で囲んだ場合、ワイルドカードは展開されません。 $1は二重引用符で囲まれているため、ワイルドカードは問題になりません。

../とシンボリックリンクの両方が、ファイルの実際の場所を覆い隠す可能性があります。以下に示すのは、ファイルが実際に、見た目だけでなく、必要なパスの下にあるかどうかを判断するためのテストです。

新しいシステム:realpathの使用

ファイルが本当に/home/charlesingalls/の下にあるかどうかを確認するには、realpathを使用できます。

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

上記は、exit 1で指定されたファイルがディレクトリ$1の下以外の場所にある場合、/home/charlesingalls/を実行します。 realpathはパス全体を正規化し、シンボリックリンクと../の両方を削除します。

realpathはGNU coreutilsの一部であり、すべてのLinuxシステムで使用できるはずです。

realpathには GNU coreutils 8.15(2012年1月)以上 が必要です。

Realpathが../に従ってファイルの実際の場所を決定する方法を示すために(たとえば、grepの実際の出力が表示されるようにgrepの-qオプションは省略されています):

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

シンボリックリンクをたどる方法を示すには:

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

古いシステム:readlink -eを使用

readlinkは、シンボリックリンクと../の両方に従って、パスを非正規化することもできます。

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

同じサンプルファイルの使用:

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

古いGNUシステムで利用可能であることに加えて、readlinkのバージョンがBSDで利用可能です。

10
John1024

パスを完全に禁止する場合、最も簡単な方法は、変数にスラッシュ(/)が含まれているかどうかをテストすることです。 bashで:

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

ただし、これにより、foo/barを含むすべてのパスがブロックされます。代わりに..をテストすることもできますが、その場合、シンボリックリンクがターゲットパス外のディレクトリを指す可能性があります。

1つのファイルの削除のみを許可したい場合は、rm -rを使用するべきではないと思います。


また、実行している内容によっては、システムのファイル権限を使用して、ユーザーが自分で削除できるファイルの削除のみを許可することもできます。このようなもの:

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

@Gillesがコメントしたように、これには引用の問題があります:$1に単一引用符が含まれていると失敗します。そのため、最初に変数をテストする必要があります(例:if [[ "$1" = *\'* ]] ; then fail...、または賢明なセットをホワイトリストに登録することによって)文字の)、または環境変数を介して渡されるファイル名。

file="$1" su charlesingalls -c 'rm "/home/charlesingalls/$file"'
2
ilkkachu