一次又一次,我在 Stack Overflow 上看到 Bash 答案,使用 eval
并且答案被抨击,双关语,因为使用了这种“邪恶”的结构。为什么 eval
如此邪恶?
如果 eval
不能安全使用,应该改用什么?
原文由 Zenexer 发布,翻译遵循 CC BY-SA 4.0 许可协议
eval
安全eval
可以 安全使用 - 但需要首先引用它的所有参数。就是这样:
此功能将为您完成:
function token_quote {
local quoted=()
for token; do
quoted+=( "$(printf '%q' "$token")" )
done
printf '%s\n' "${quoted[*]}"
}
示例用法:
给定一些不受信任的用户输入:
% input="Trying to hack you; date"
构造一个 eval 的命令:
% cmd=(echo "User gave:" "$input")
用 看似 正确的引用来评估它:
% eval "$(echo "${cmd[@]}")"
User gave: Trying to hack you
Thu Sep 27 20:41:31 +07 2018
注意你被黑了。 date
被执行而不是按字面打印。
而不是 token_quote()
:
% eval "$(token_quote "${cmd[@]}")"
User gave: Trying to hack you; date
%
eval
不是邪恶的——只是被误解了:)
原文由 Tom Hale 发布,翻译遵循 CC BY-SA 4.0 许可协议
7 回答5.3k 阅读
4 回答4k 阅读
2 回答5.9k 阅读✓ 已解决
2 回答2.5k 阅读✓ 已解决
1 回答2.3k 阅读✓ 已解决
2 回答799 阅读✓ 已解决
2 回答3.2k 阅读
这个问题比表面上看到的要多。我们将从显而易见的开始:
eval
有可能执行“脏”数据。脏数据是任何未被重写为可安全使用的数据-XYZ;在我们的例子中,它是任何没有被格式化以便安全评估的字符串。乍一看,清理数据似乎很容易。假设我们正在抛出一个选项列表,bash 已经提供了一种清理单个元素的好方法,以及另一种将整个数组作为单个字符串进行清理的方法:
现在假设我们要添加一个选项来重定向输出作为 println 的参数。当然,我们可以在每次调用时重定向 println 的输出,但为了举例,我们不打算这样做。我们需要使用
eval
,因为变量不能用于重定向输出。看起来不错,对吧?问题是,eval 解析了两次命令行(在任何 shell 中)。在解析的第一遍中,一层引用被删除。删除引号后,将执行一些可变内容。
我们可以通过让变量扩展发生在
eval
中来解决这个问题。我们所要做的就是将所有内容单引号,将双引号留在原处。一个例外:我们必须在eval
之前扩展重定向,因此必须保留在引号之外:这应该有效。只要
$1
inprintln
从不脏,它也是安全的。现在稍等一下:我一直使用与
sudo
相同的不带 引号 的语法!为什么它在那里工作,而不是在这里?为什么我们必须单引号引用所有内容?sudo
更现代一点:它知道将收到的每个参数括在引号中,尽管这过于简化了。eval
简单地连接所有内容。不幸的是,没有直接替代
eval
的方法来处理像sudo
这样的参数,因为eval
是一个内置的 shell;这很重要,因为它在执行时会占用周围代码的环境和范围,而不是像函数那样创建新的堆栈和范围。评估替代品
特定用例通常有
eval
的可行替代方案。这是一个方便的清单。command
代表您通常发送给eval
的内容;随意替换。无操作
一个简单的冒号在 bash 中是无操作的:
创建子外壳
执行命令的输出
永远不要依赖外部命令。您应该始终控制返回值。将这些放在自己的行中:
基于变量的重定向
在调用代码中,将
&3
(或高于&2
的任何内容)映射到您的目标:如果是一次性调用,则不必重定向整个 shell:
在被调用的函数中,重定向到
&3
:变量间接
设想:
坏的:
为什么?如果 REF 包含双引号,这将破坏并打开代码以进行攻击。可以对 REF 进行消毒,但是如果你有这个,那就是浪费时间:
没错,bash 从第 2 版开始就内置了可变间接。如果您想做更复杂的事情,它比
eval
有点棘手:无论如何,新方法更直观,尽管对于习惯于
eval
的经验丰富的编程人员来说似乎不是这样。关联数组
关联数组本质上是在 bash 4 中实现的。需要注意的是:它们必须使用
declare
创建。在旧版本的 bash 中,您可以使用变量间接: