该站点上的许多答案和评论都提到通常应避免eval
在 shell 脚本中使用。这些评论通常是针对初学者的。
我已经看到提到安全问题和健壮性,但很少有例子。
我已经明白命令是什么,以及如何使用它eval
。但是,那里给出的答案仅简要提及使用eval
. 值得注意的是,每个答案都指出这eval
可能是一个主要的安全漏洞,但大多数只是将这一事实作为结束语而没有详细说明。这似乎值得进一步考虑,实际上是它自己的问题。
为什么是用eval
一个坏主意?
当将它适当地使用?
是否有任何指导方针可以完全安全地使用它,或者它总是不可避免地存在安全漏洞?
编辑:这篇文章是我在发布这个问题时实际寻找的那种规范参考(答案)。任何滥用的人eval
都可以指向此参考。
Tho*_*key 11
这个问题会吸引意见......
eval
是标准外壳的一部分:
该
eval
实用程序应通过将参数连接在一起来构造命令,并用<space>
字符分隔每个参数。构造的命令应由 shell 读取和执行。
不喜欢的可能原因eval
:
eval
允许您根据其他变量的名称设置变量)。另一方面,如果eval
检查进入的数据,例如,测试文件名的结果并确保没有引号使事情复杂化,那么它是一个有用的工具。通常建议的替代方案的可移植性较差,例如,特定于某些特定的 shell 实现。
进一步阅读(但请记住,bash 提供了特定于实现的/非标准的替代方案):
小智 9
什么时候用比较合适?
当 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
Run Code Online (Sandbox Code Playgroud)
该命令是无条件安全的(因为它是),因为唯一可能的结果$#
是一个数字。并且$n
(n 是一个数字)的唯一可能结果是这种位置变量的内容。
如果您想查看命令的作用,请将 eval 替换为 echo
$ set -- "ls -l" "*" "c; date"
$ echo echo \"\$\{$#\}\" ### command under test
echo "${3}"
Run Code Online (Sandbox Code Playgroud)
并且echo ${3}
是非常常见(且安全)的习语。
我们仍然可以更改一些引用并得到相同的结果:
$ echo echo '"${'$#\}\"
echo "${3}"
$ eval echo '"${'$#\}\"
c;date
Run Code Online (Sandbox Code Playgroud)
我希望你很容易看到,这种新的引用方式比上面的更晦涩一些。
$ b=book
$ book="A Tale of Two Cities"
$ eval 'a=$'"$b" ### safe for some values of $b.
$ echo "$a"
A Tale of Two Cities
Run Code Online (Sandbox Code Playgroud)
在这里,我们发现 eval 的第一个(两个中的)和主要问题:
引用不足:
$ b='book;date'
$ eval 'a=$'"$b" ### safe for some values of $b.
Fri Apr 22 22:03:09 UTC 2016
Run Code Online (Sandbox Code Playgroud)
命令日期被执行(我们不打算这样做)。
但这不会执行date
(或者我想感谢@Wildcard)。
此命令的正确解决方案是清理输入,但我将推迟讨论此问题,直到定义了 eval 的第二个问题。
$ eval 'a="$'"$b"\"
$ echo "$a"
A Tale of Two Cities;date
Run Code Online (Sandbox Code Playgroud)
而且诀窍并不难,只需将 eval 替换为 echo 并评估打印的命令行是否安全。比较不安全和安全的命令:
$ echo 'a=$'"$b"
a=$book;date
$ echo 'a="$'"$b"\"
a="$book;date"
Run Code Online (Sandbox Code Playgroud)
这些简单的双引号会将字符串保留为字符串,并且不会转换为要由 shell 执行的命令。
如果我们放置“外部引号”和“内部引号”(添加空格以提高可读性),可能更容易理解引用的逻辑:
$ echo a\=\"\$ "$b" \"
Run Code Online (Sandbox Code Playgroud)
内部引号是那些$b
始终是“好主意”的引号。
外部的都是反斜杠中的那些(在这个例子中)。
没有空格的命令(应该使用):
$ echo a\=\"\$"$b"\"
a="$book;date"
$ eval a\=\"\$"$b"\"
$ echo "$a"
A Tale of Two Cities;date
Run Code Online (Sandbox Code Playgroud)
但即使是这个例子也很快被用户打破(感谢@Wildcard):
$ b='";date;:"'
$ eval a\=\"\$"$b"\"
Fri Apr 22 23:25:43 UTC 2016
Run Code Online (Sandbox Code Playgroud)
eval 执行的命令被阻止成为恶意的,让我们使用 echo:
$ echo a\=\"\$"$b"\"
a="$";date;:""
Run Code Online (Sandbox Code Playgroud)
思考两次引用的结果从来都不是一件简单的事情。
这比第一个更令人费解。在某些情况下,我们不希望 eval 参数的第一次扩展的结果保持引用状态。很多情况下是因为我们想在一个变量中执行一个命令。
或者,攻击者(如上所示)制作一个与 qutoes 匹配的字符串,然后将命令放在引号之外。该命令将被执行,并且(如果我们幸运的话)将报告一个错误。
这打破了第一层保护:引用
并且使变量的值完全可以执行。
在这种情况下,我们必须确定变量将具有的值。所有可能的值。
如果变量值由某个用户控制,我们告诉该用户:
给我任何命令,我会以我的权限执行它。
这总是一个危险的赌注,一个肯定的错误,作为根:一个疯狂的行动。
综上所述,这是完全安全的:
#!/bin/bash
a=${1//[^0-9]} ### Only leave a number (one or many digits).
eval echo $(( a + 1 ))
Run Code Online (Sandbox Code Playgroud)
无论外部用户在位置参数中放置什么,它都会变成一个数字,它可能是一个大数字,$(( ... )) 将失败,但该输入不会触发任何命令的执行。
现在我们可以b
从上面的命令讨论清理变量了。命令是:
$ b='";date;:"'
$ eval a\=\"\$"$b"\"
Sat Apr 23 01:56:30 UTC 2016
Run Code Online (Sandbox Code Playgroud)
因为 b 包含几个用于更改行的字符。
$ echo a\=\"\$"$b"\"
a="$";date;:""
Run Code Online (Sandbox Code Playgroud)
更改为单引号将不起作用, b 的其他一些值也可以匹配它们。我们需要b
通过删除(至少)引号来“清理” 。
我们可以替换$b
为${b//\"}
:
$ eval a\=\"\$"${b//\"}"\"
$ echo "$a"
$;date;:
Run Code Online (Sandbox Code Playgroud)
但是我们可以通过承认 shell 中的变量名只能有0-9a-zA_Z
和下划线来使它更好。我们可以删除所有其他人:
$ c="$( LC_COLLATE=C; echo "${b//[^0-9A-Za-z_]}" )"
Run Code Online (Sandbox Code Playgroud)
这会将变量清理b
为有效的变量名称。
然后,我们可以添加更严格的检查,通过实际检查内部的变量名称$b
(使用cleaned c
)是否存在:
$ if declare -p "$c" &>/dev/null; then eval a\=\"\$"$c"\" ; fi
Run Code Online (Sandbox Code Playgroud)