如何安全地確保變數僅包含有效的文件名?
鑑於下面的腳本,如何確保參數僅包含有效的文件名
/home/charlesingalls/
而不包含路徑 (../home/carolineingalls/
) 或萬用字元等?我只希望腳本能夠從給定的硬編碼目錄中刪除單個文件。此腳本將以特權使用者身份執行。
#!/bin/bash rm -f /home/charlesingalls/"$1"
如果您只想刪除其中的文件
/home/charlesingalls
(而不是子目錄中的文件),那麼很容易:只需檢查參數是否不包含/
.case "$1" in */*) echo 1>&2 "Refusing to remove a file in another directory"; exit 2;; *) rm -f /home/charlesingalls/"$1";; esac
rm
即使參數為.
or..
或空,它也會執行,但在這種情況下rm
,刪除目錄將無害地失敗。萬用字元在這裡不相關,因為沒有執行萬用字元擴展。
即使存在符號連結,這也是安全的:如果文件是符號連結,則符號連結(位於 中
/home/charlesingalls
)將被刪除,並且該連結的目標不受影響。請注意,這假設
/home/charlesingalls
不能移動或更改。rm
如果目錄是在腳本中硬編碼的,那應該沒問題,但如果它是根據變數確定的,那麼在命令執行時該確定可能不再有效。根據參數是虛擬主機名的附加資訊,您應該將其列入白名單而不是列入黑名單:檢查名稱是否是合理的虛擬主機名,而不僅僅是禁止使用斜杠。我檢查名稱是否以小寫字母或數字開頭,並且它不包含除小寫字母、數字、點和破折號以外的字元。
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
該答案假定
$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 如何
../
確定文件的實際位置(例如,-q
省略了 grep 的選項,以便 grep 的實際輸出可見):$ 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 上也可以使用 的版本。