在Bash中安全使用eval
1. 简介
在 Bash 编程语言中,eval内置的应用范围很广。也就是说,它可以执行其他命令、设置 shell 环境以及执行变量替换和间接寻址。
但是,应谨慎使用如此强大的工具。我们应该意识到,它能够将任意输入解析为 Bash 命令可能会导致严重的安全问题。
2. eval的基本使用
让我们通过eval 运行 Bash 命令:
$ eval "pwd; echo Hello $USER; date"
/home/joe
Hello joe
Sun Mar 27 01:01:06 PM EDT 2022
让我们注意环境变量“USER”的扩展。 有关更多信息,我们应该在终端中输入help eval 。
3. 在当前Shell中设置变量
eval内置不会产生子进程。因此,我们将使用它为当前 shell 设置变量。
假设文件“vars”包含变量定义:
echo "Setting variables"
foo=FOO
bar=BAR
现在,让我们将这些变量放入当前 shell:
$ eval "$(cat vars)"
Setting variables
$ echo $foo $bar
FOO BAR
cat 输出周围的引号保存了起到命令分隔符作用的换行符。
我们可以将source 用于相同的目的。
4. 字符串中的变量扩展
现在,让我们替换嵌入在问候模板hello.txt中的 Bash 变量:
Hello $USER! Welcome to $HOSTNAME.
所以让我们检查一下:
$ eval echo \"$(cat hello.txt)\"
echo 的输出用引号括起来。因此,Bash 不会将其解释为命令。
但是,变量替换仍然发生:
Hello joe! Welcome to fedora.
请注意,我们也可以使用envsubst 。
5. 使用eval 的变量间接
我们将在不知道它的名字的情况下动态定义一个 Bash 变量。
因此,让我们打印传递给脚本last_arg的最后一个参数的值:
#!/bin/bash
echo echo \"\${$#\}\" # just for test
eval echo \"\${$#\}\"
现在,让我们测试一下:
$ ./last_arg foo bar foobar
echo "${3}"
foobar
让我们首先了解一下我们替换变量“#”。它的值是参数的数量,在我们的例子中是 3。
然后,我们创建一个新变量${3}引用第三个脚本的参数。
6. 安全问题
在使用 eval 时,我们应该注意它带来的安全问题。
让我们考虑一个不安全的变量间接:
$ foo=bar bar="Hello all *"
$ eval echo \$$foo
Hello all hello_from_evil.txt hello.txt vars
*我们不只是打印bar的值,而是意外地评估了*echo *并列出了工作目录中的文件。
再举一个例子,让我们稍微改变我们的问候语为hello_from_evil.txt:
Hello $USER! Welcome to $HOSTNAME. The system is up to ";date"
现在*eval echo \”$(cat hello_from_evil.txt)\”*的输出不同了:
Hello joe! Welcome to fedora. The system is up to
Sun Apr 3 08:00:32 AM EDT 2022
这里的date 是一个“邪恶”命令的模型。 毫无疑问,在这种情况下,我们不打算运行此命令或任何其他命令。
7. 使用双引号的基本字符串清理
在变量间接的情况下,我们应该使用双引号作为必须的保护:
$ foo=bar; bar="Hello all *"
$ eval echo \"\$$foo\"
Hello all *
8. 通过删除内容的引用进行更多的清理
现在让我们安全地处理来自hello_from_evil文件的字符串。
首先,让我们使用echo来检查eval的参数:
$ content="$(echo \"$(cat hello_from_evil.txt)\")"
$ echo "$content"
"Hello $USER! Welcome to $HOSTNAME. The system is up to ";date""
所以罪魁祸首是一个释放的命令分隔符。
*因此,我们将使用替换模式${content//\”}***删除所有引号:
$ eval echo \""${content//\"}"\"
Hello joe! Welcome to fedora. The system is up to ;date
9. 安全底线
出于安全原因,**我们不应该将原始用户的输入传递给eval。**它会为粗心或恶意行为打开我们的脚本。
接下来,我们不应该高估字符串清理,因为它可能会导致错误的安全感。
最后,我们应该确保eval的输入仅限于保留在具有明确定义的极端情况的控制数据之下。