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


8

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

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

#!/bin/bash

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

2
如果您不想支持“ foo / bar”,只需检查一下它是否不包含/。不能在引号内解释通配符。
Random832 '16

3
如果要删除文件,请不要-r与一起使用rmrm -r用于递归删除目录及其下的所有文件和目录。仅在删除目录时有用。更普遍的是,不要崇拜货物。即,不要仅仅将看起来有用的内容复制到您的命令行或脚本中,而无需了解它们的作用或工作方式。携带魔法货物的飞机神会生气并删除您的所有文件。
cas

关于-r标志的要点-我确实了解它的用法,只是想的不够清楚。
亚伦·西卡利

Answers:


7

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

在这种情况下,这些文件是由上一个过程生成的Web服务器配置文件。它们都具有相同的重要性级别(每个都构成一个虚拟主机)。但是,它们所在的目录与相似目录相邻,并且都位于Web服务器拥有的目录下。该特定脚本旨在仅允许删除特定目录下的虚拟主机配置。
亚伦·西卡利'16

如果提供该用例,这种方法对您有意义吗?我感谢您的经验,并且会承认,肯定有几次我“将火箭筒进行了枪战”以自动化linux中的某些程序。
亚伦·西卡利

@AaronCicali为什么脚本可以删除任何主机配置,而不仅仅是属于发出原始请求的实体的主机?为什么不首先验证虚拟主机名(然后它不包含任何特殊字符)?
吉尔(Gilles)“所以,别再邪恶了”

发出原始请求的实体是GUI,它确实具有删除该文件夹中任何虚拟主机的权限。首先验证虚拟主机名。它来自以前创建的虚拟主机(域名)的列表。我相信,此脚本的工作就是确保它尽可能安全,而不依赖于应用程序另一部分的安全性。即,它只能从该特定目录中删除文件。它还将需要执行其他一些清理工作。
亚伦·西卡利

@AaronCicali好吧,这是一个附加的健全性检查。在这种情况下,您应该将白名单列入白名单:只接受看似合理的主机名的名称。如果您不允许子域,您甚至可以禁止.
Gilles'SO-别成为邪恶

10

该答案假定$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 上还可以使用的版本。


我的Ubuntu 14.04的coreutils没有realpath
heemayl

1
这似乎更多是我在寻找的东西,但是不幸的是我的Centos 6.5服务器没有realpath。我无法安装它。谷歌搜索出现了“ readlink -f”取代了realpath的说法,但我尚未使它起作用。
亚伦·西卡利

1
副标题提到-f(“除了最后一个组件,所有组件都必须存在”),示例使用-e(“所有组件必须存在”),这有点令人困惑。
isanae '16

2
@AaronCicali这绝对不是您需要的答案,因为这是错误的错误。您不得解析符号链接。符号链接的目标可能在您检查它和使用它的时间之间改变。(这是设计错误的众所周知的类别)。此外,解析符号链接没有意义,因为它rm作用于参数本身而不是目标。
吉尔(Gilles)'所以

1
提示:grep -q用于阻止grep输出匹配的行。您仍然获得退出状态,以便&&||仍然工作就像你来。
CVn

2

如果要完全禁止路径,最简单的方法是测试变量是否包含斜杠(/)。在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"'

关于-r标志的要点,那确实不应该在那里。立即移除...
Aaron Cicali

请注意,su由于引用错误,您的命令已损坏。您正在执行的命令中插入的参数作为外壳程序片段插入。例如,如果参数是,$(touch foo)则您的代码将运行touch foo
吉尔斯(Gillles)“所以-别再邪恶了”

@吉尔斯,胡扯,我知道一定有问题。嵌套引用不是我最喜欢的朋友。我认为当前版本的效果更好,双引号将评估外壳中的变量,而单引号将阻止其在内部外壳中被评估。没有保修。
ilkkachu

@ilkkachu如果参数包含单引号,则该版本将失败。(但是只有在这种情况下,这才比使用双引号使验证变得容易得多。)不可能直接插值任意字符串。您需要按摩字符串(例如,全部替换''\''),或者通过诸如环境变量之类的其他通道传递它(这是我在这里要做的:)file_to_remove="$1" su -c 'rm "/home/charlesingalls/$file_to_remove"'。原来,文件名应该是虚拟主机名,因此在这里拒绝所有特殊字符也是可以的。
吉尔斯(Gillles)“所以-别再作恶了”
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.