如何安全地确保变量仅包含有效的文件名?

如何安全地确保变量仅包含有效的文件名?

给定下面的脚本,如何确保参数仅包含有效的文件名/home/charlesingalls/,而不包含路径 ( ../home/carolineingalls/) 或通配符等?

我只希望脚本能够从给定的硬编码目录中删除单个文件。该脚本将以特权用户身份运行。

#!/bin/bash

rm -f /home/charlesingalls/"$1"

答案1

这个答案假设$1允许包含子目录。如果您对更简单的情况感兴趣,其中$1应该是一个简单的目录名称,那么请参阅其他答案之一。


用双引号引起来的通配符不会被扩展。由于$1is 用双引号引起来,因此通配符不是问题。

和 符号链接都../可以掩盖真实的文件的位置。下面显示的是确定文件是否确实位于我们想要的路径下(而不仅仅是表面上)的测试。

较新的系统:使用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 上使用 的版本。

答案2

如果您只想删除其中的文件/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

答案3

如果要完全禁止路径,最简单的方法是测试变量是否包含斜杠 ( /)。在bash中:

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

但这将阻止所有路径,包括foo/bar.您可以改为进行测试..,但这会留下符号链接指向目标路径之外的目录的可能性。

如果您只想允许删除单个文件,我认为您不应该使用rm -r.


此外,根据您正在执行的操作,您可以使用系统的文件权限来仅允许删除用户可以自行删除的文件。像这样的东西:

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

尽管正如@Gilles评论的那样,这有一个引用问题:如果包含单引号,它将失败,$1因此应该首先测试该变量(例如,if [[ "$1" = *\'* ]] ; then fail...或者通过将一组合理的字符列入白名单),或者通过的文件名环境变量,例如

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

相关内容