来自 findutils 的手册:
例如这两个命令之类的构造
# risky find -exec sh -c "something {}" \; find -execdir sh -c "something {}" \;
非常危险。这样做的原因是“{}”被扩展为一个文件名,该文件名可能包含分号或其他 shell 特殊字符。例如,如果有人创建了文件
/tmp/foo; rm -rf $HOME
,那么上面的两个命令可以删除某人的主目录。因此,由于这个原因,不要运行任何将不可信数据(例如文件名)传递给将参数解释为要进一步解释的命令的命令(例如“sh”)的命令。
在 shell 的情况下,这个问题有一个聪明的解决方法:
# safer find -exec sh -c 'something "$@"' sh {} \; find -execdir sh -c 'something "$@"' sh {} \;
这种方法不能保证避免所有问题,但它比将攻击者选择的数据替换到 shell 命令的文本中要安全得多。
- 问题的原因是否
find -exec sh -c "something {}" \;
在于替换为{}
未引用,因此不被视为单个字符串? 在解决方案中
find -exec sh -c 'something "$@"' sh {} \;
,first
{}
被替换了,但是由于{}
没有被引用,是不是"$@"
也和原来的命令有同样的问题?例如,"$@"
将扩展为"/tmp/foo;"
、"rm"
、"-rf"
和"$HOME"
?为什么
{}
没有转义或引用?
- 您能否给出其他示例(
sh -c
如果适用,仍然有,或没有它;有或没有find
哪个可能不是必需的)适用于相同类型的问题和解决方案,并且这些示例是最小的示例,以便我们可以专注于问题和解决方案尽可能少分心?请参阅为 `bash -c` 执行的命令提供参数的方法
谢谢。
这实际上与引用无关,而是与参数处理有关。
考虑一个危险的例子:
这由 shell 解析,并分成六个单词:
find
,-exec
,sh
,-c
,something {}
(不再加引号),;
. 没有什么可扩展的。shellfind
以这六个单词作为参数运行。例如,当
find
找到要处理的内容时foo; rm -rf $HOME
,它会替换{}
为,并使用参数、和foo; rm -rf $HOME
运行。sh
sh
-c
something foo; rm -rf $HOME
sh
现在看到-c
, 并作为结果解析something foo; rm -rf $HOME
(第一个非选项参数)并执行结果。现在考虑更安全的变体:
shell
find
使用参数find
,-exec
,sh
,-c
,something "$@"
,sh
,{}
,运行;
。现在当
find
find时,它再次foo; rm -rf $HOME
替换,并使用参数, , , ,运行。{}
sh
sh
-c
something "$@"
sh
foo; rm -rf $HOME
sh
看到-c
并解析something "$@"
为要运行的命令,sh
以及foo; rm -rf $HOME
作为位置参数(从 开始$0
),扩展"$@"
为foo; rm -rf $HOME
单个值,并something
使用单个参数运行foo; rm -rf $HOME
。您可以使用
printf
. 创建一个新目录,输入它,然后运行运行第一个变体如下
生产
而第二个变体,运行为
生产
第1部分:
find
只使用文本替换。是的,如果你没有引用它,就像这样:
并且攻击者能够创建一个名为 的文件
; echo owned
,然后它将执行这将导致 shell 运行
echo
thenecho owned
。但是如果你添加了引号,攻击者可以结束你的引号,然后通过创建一个名为的文件将恶意命令放在它后面
'; echo owned
:这将导致 shell 运行
echo ''
,echo owned
.(如果将双引号换成单引号,攻击者也可以使用其他类型的引号。)
第2部分:
在
find -exec sh -c 'something "$@"' sh {} \;
中,{}
最初不是由 shell 解释的,而是直接用 执行的execve
,因此添加 shell 引号无济于事。没有效果,因为 shell 在运行
find
.添加 shell 没有特别处理的引号,因此在大多数情况下,它只是意味着该命令不会执行您想要的操作。
将其扩展为
/tmp/foo;
,rm
,-rf
,$HOME
应该不是问题,因为这些是 的参数something
,并且something
可能不会将其参数视为要执行的命令。第 3 部分:
我假设类似的考虑适用于任何接受不受信任的输入并将其作为命令(的一部分)运行的东西,例如
xargs
andparallel
.从某种意义上说,但在这里引用无济于事。被替换的文件名
{}
可以包含任何字符,包括引号。无论使用何种形式的引用,文件名都可以包含相同的内容,并且可以“中断”引用。No.
"$@"
扩展为位置参数,作为单独的单词,并且不会进一步拆分它们。在这里,它本身{}
是一个参数,并将当前文件名也作为一个不同的参数传递给. 它可以直接作为 shell 脚本中的变量使用,它本身并不作为 shell 命令处理。find
find
sh
在大多数 shell 中,它不需要。如果你运行
fish
,它需要:fish -c 'echo {}'
打印一个空行。但是如果你引用它并不重要,shell只会删除引号。每当您在被视为某种代码(*)的字符串中按原样扩展文件名(或另一个不受控制的字符串)时,就有可能执行任意命令。
例如,这会
$f
直接扩展 Perl 代码,如果文件名包含双引号,则会导致问题。文件名中的引号将结束 Perl 代码中的引号,文件名的其余部分可以包含任何 Perl 代码:(文件名必须有点奇怪,因为 Perl 在运行任何代码之前都会预先解析整个代码。所以我们必须避免解析错误。)
虽然这可以通过一个参数安全地传递它:
(直接从一个shell运行另一个shell是没有意义的,但是如果你这样做了,
find -exec sh ...
情况就差不多了)(* 某种代码包括 SQL,所以必须使用 XKCD:https ://xkcd.com/327/加上解释: https ://www.explainxkcd.com/wiki/index.php/Little_Bobby_Tables )
您的关注正是 GNU Parallel 引用输入的原因:
这不会运行
echo pwned
。它会运行一个 shell,所以如果你扩展你的命令,你不会突然感到惊讶:
有关 spawning-a-shell 问题的更多详细信息,请参见:https ://www.gnu.org/software/parallel/parallel_design.html#Always-running-commands-in-a-shell