问题

问题

该站点上的许多答案和评论都提到,通常应该避免eval在 shell 脚本中使用。这些评论通常是针对初学者的。

我见过提到安全问题和稳健性,但例子很少。

我已经明白了eval命令是什么以及如何使用它。然而,那里给出的答案只是简单地提到了使用eval.值得注意的是,每一个答案指出这eval可能是一个主要的安全漏洞,但大多数人只是将这一事实作为结论性句子提及,而没有详细说明。这似乎值得进一步考虑,而且确实是它自己的问题。

为什么使用eval一个坏主意?

什么时候用起来合适吗?

是否有任何指导原则可以完全安全地使用它,或者它总是不可避免地存在安全漏洞?


编辑:本文是我发布此问题时实际上正在寻找的规范参考(答案)。任何滥用者eval都可以指出此参考。

答案1

这个问题会引起意见...

eval是标准 shell 的一部分:

实用eval程序应通过将参数连接在一起来构造命令,并用一个分隔符分隔每个参数<space>特点。构造的命令应由 shell 读取并执行。

不喜欢的可能原因eval

  • 脚本编写者可能无法正确引用,从而导致意外行为(考虑来自评估中的变量的不匹配引用的可能性)。
  • 评估的结果可能并不明显(部分是由于引用问题,部分是因为eval允许您根据其他变量的名称设置变量)。

另一方面,如果eval检查输入的数据,例如测试文件名的结果并确保没有引号使事情变得复杂,那么它是一个有用的工具。通常建议的替代方案可移植性较差,例如,特定于某些特定的 shell 实现。

进一步阅读(但请记住 bash 提供了特定于实现/非标准的替代方案):

答案2

问题

什么时候使用比较合适?

当 eval 的参数被正确引用(两级)时,第一次解析中 eval 扩展的变量值将不会成为第二次解析中执行的命令。

为什么使用 eval 是一个坏主意?

如果您真的知道自己在做什么,那么这不是IMO(这个问题将征求意见)。

eval第一:通过替换为和来检查您所写的(评估)是否符合您的预期。echo第二:认真考虑哪些变量是本地设置的以及它们可能具有什么值。

是否有任何指导原则可以完全安全地使用它,或者它总是不可避免地存在安全漏洞?

是的,在某些情况下它可以完全安全地使用。

但这始终是一种风险(就像编写不正确的脚本存在风险一样)。
碰巧 eval 会将风险提高到某个指数。

这肯定需要一个很长的描述:

细节:

命令 eval (始终是内置命令)允许解析命令行两次。

rm -rf /原则上,它并不比任何其他命令(想想)危险更多或更少。它将使用当前用户权限执行,因此在 root 身份下使用它之前请三思。但上面显示的命令也是如此rm。对于有限用户,rm 在 root 拥有的大多数目录上都会失败。但这rm仍然是一个危险的命令。

一个众所周知的成语。

命令 eval 在已知的习惯用法中非常有用(而且非常安全)。
例如,该表达式打印最后一个位置参数的值:

$ set -- "ls -l" "*" "c;date"

$ eval echo \"\$\{$#\}\"          ### command under test

c;date

该命令是无条件安全的(事实上),因为唯一可能的结果$#是数字。唯一可能的结果$n(n 是数字)是该位置变量的内容。

如果您想查看该命令的作用,请将 eval 替换为 echo

$ set -- "ls -l" "*" "c; date"
$ echo echo \"\$\{$#\}\"          ### command under test
echo "${3}"

并且echo ${3}是非常常见(且安全)的习语。

我们仍然可以更改一些引用并得到相同的结果:

$ echo echo '"${'$#\}\"
echo "${3}"
$ eval echo '"${'$#\}\"
c;date

我希望您很容易看出这种新的引用方式比上面的方式更加晦涩难懂。

略有不同的习语。

$ b=book
$ book="A Tale of Two Cities"
$ eval 'a=$'"$b"               ### safe for some values of $b.
$ echo "$a"
A Tale of Two Cities

在这里我们发现了 eval 的第一个(两个)也是主要问题:
引用不足:

$ b='book;date'

$ eval 'a=$'"$b"               ### safe for some values of $b.
Fri Apr 22 22:03:09 UTC 2016

命令日期已执行(我们不打算这样做)。

但这不会执行date(或者我想谢谢@Wildcard)。
该命令的正确解决方案是清理输入,但我将推迟讨论这个问题,直到定义了 eval 的第二个问题。

$ eval 'a="$'"$b"\"
$ echo "$a"
A Tale of Two Cities;date

而且技巧并不难,只需用 echo 替换 eval 并评估打印的命令行是否安全即可。比较不安全和安全命令:

$ echo 'a=$'"$b"
a=$book;date
$ echo 'a="$'"$b"\"
a="$book;date"

这些简单的双引号会将字符串保留为字符串,并且不会转换为由 shell 执行的命令。

如果我们放置“外部引号”和“内部引号”(为了易读而添加空格),可能更容易理解引用的逻辑:

$ echo a\=\"\$    "$b"   \"

内部引用总是一个$b“好主意”。
外部的都是反斜杠中的那些(在本例中)。
不带空格的命令(应该使用):

$ echo a\=\"\$"$b"\"
a="$book;date"

$ eval a\=\"\$"$b"\"
$ echo "$a"
A Tale of Two Cities;date

但即使这个例子也很快被用户打破(感谢@Wildcard):

$ b='";date;:"'
$ eval a\=\"\$"$b"\"
Fri Apr 22 23:25:43 UTC 2016

eval 执行的命令被阻止成为恶意命令,让我们使用 echo:

$ echo a\=\"\$"$b"\"
a="$";date;:""

思考两次引用的结果是什么绝非易事。

第二个问题。

这比第一个更复杂。在某些情况下,我们不希望 eval 参数的第一次扩展的结果保留引用。在很多情况下,因为我们想要在变量内执行命令。

或者,攻击者(如上所示)制作一个与引号匹配的字符串,然后将命令放在引号之外。该命令将被执行,并且(如果幸运的话)将报告错误。

这打破了第一层保护:引用
并使变量的值完全开放于执行。

在这种情况下,我们必须确定变量将具有的值。所有可能的值。

如果变量值由某个用户控制,我们会告诉该用户:

给我任何命令,我都会以我的权限执行它。

这始终是一个危险的赌注,一个肯定的错误,而且从根本上来说:这是一个疯狂的行为。

清理外部数据。

综上所述,这是完全安全的:

#!/bin/bash

a=${1//[^0-9]}       ### Only leave a number (one or many digits).

eval echo $(( a + 1 ))

无论外部用户在位置参数中放置什么,它都会变成一个数字,它可能是一个很大的数字, $(( ... )) 将失败,但该输入不会触发任何命令的执行。

现在我们可以讨论b上面命令中的清理变量了。命令是:

$ b='";date;:"'
$ eval a\=\"\$"$b"\"
Sat Apr 23 01:56:30 UTC 2016

因为b包含了几个用来换行的字符。

$ echo a\=\"\$"$b"\"
a="$";date;:""

更改为单引号不起作用,b 的其他一些值也可以匹配它们。我们需要b通过删除(至少)引号来“清理”。
我们可以替换$b${b//\"}

$ eval a\=\"\$"${b//\"}"\"
$ echo "$a"
$;date;:

更强大的变量名称清理。

但是我们可以通过承认 shell 中的变量名只能包含0-9a-zA_Z和下划线来使其变得更好。我们可以用这个删除所有其他的:

$ c="$( LC_COLLATE=C; echo "${b//[^0-9A-Za-z_]}" )"

这会将变量清理b为有效的变量名称。
然后,我们可以通过实际检查内部变量名称$b(使用 clean c)是否存在来添加更严格的检查:

$ if declare -p "$c" &>/dev/null; then   eval a\=\"\$"$c"\" ; fi

答案3

Eval 是针对不良代码的简单解决方案。这是我想到的一个例子:

假设您想通过引用获取数组元素。您可以使用 eval:

$ nov=(osc pap que)
$ rom=nov
$ eval echo \${$rom[2]}
que

更好的方法是:

$ nov=(osc pap que)
$ rom=nov[2]
$ echo ${!rom}
que

更好的方法仍然是:

$ tail -1 <<+
> osc
> pap
> que
> +
que

相关内容