Shell-Script

如何安全地確保變數僅包含有效的文件名?

  • July 21, 2016

鑑於下面的腳本,如何確保參數僅包含有效的文件名/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還能夠對路徑進行錐形化,同時遵循符號連結和../

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 上也可以使用 的版本。

引用自:https://unix.stackexchange.com/questions/296750