来自 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 {} \;
,首先
{}
被替换,但由于{}
未加引号,是否"$@"
也有与原始命令相同的问题?例如,"$@"
将扩展为"/tmp/foo;"
、"rm"
、"-rf"
和"$HOME"
?为什么
{}
不转义或引用?
- 您能否提供适用于相同类型的问题和解决方案的其他示例(仍然带有
sh -c
或不带有它,如果适用;带有或不带有find
它可能不是必需的),并且这些示例是最小的示例,以便我们可以专注于问题和解决方案尽可能少的干扰?看为“bash -c”执行的命令提供参数的方法
谢谢。
答案1
这实际上与引用无关,而是与参数处理有关。
考虑一个有风险的例子:
find -exec sh -c "something {}" \;
这由 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
(第一个非选项参数) 并执行结果。
现在考虑更安全的变体:
find -exec sh -c 'something "$@"' sh {} \;
shell
find
使用参数find
,-exec
,sh
,-c
,something "$@"
,sh
,{}
,运行;
。现在,当
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
。新建一个目录,进入,运行
touch "hello; echo pwned"
运行第一个变体如下
find -exec sh -c "printf \"Argument: %s\n\" {}" \;
产生
Argument: .
Argument: ./hello
pwned
而第二个变体,运行为
find -exec sh -c 'printf "Argument: %s\n" "$@"' sh {} \;
产生
Argument: .
Argument: ./hello; echo pwned
答案2
第1部分:
find
只是使用文本替换。
是的,如果你不加引号,就像这样:
find . -type f -exec sh -c "echo {}" \;
攻击者能够创建一个名为 的文件; echo owned
,然后它会执行
sh -c "echo ; echo owned"
echo
这将导致 shell 运行echo owned
。
但是,如果您添加了引号,攻击者可以结束您的引号,然后通过创建一个名为的文件将恶意命令放在其后面'; echo owned
:
find . -type f -exec sh -c "echo '{}'" \;
这将导致 shell 运行echo ''
,echo owned
.
(如果您将双引号替换为单引号,攻击者也可以使用其他类型的引号。)
第2部分:
在 中find -exec sh -c 'something "$@"' sh {} \;
,{}
最初并不由 shell 解释,而是直接使用 执行execve
,因此添加 shell 引号没有帮助。
find -exec sh -c 'something "$@"' sh "{}" \;
没有效果,因为 shell 在运行之前去掉了双引号find
。
find -exec sh -c 'something "$@"' sh "'{}'" \;
添加 shell 不会特殊处理的引号,因此在大多数情况下,这仅意味着该命令不会执行您想要的操作。
将其扩展为/tmp/foo;
, rm
, -rf
,$HOME
应该不是问题,因为这些是 , 的参数something
,并且something
可能不会将其参数视为要执行的命令。
第 3 部分:
我假设类似的考虑适用于任何接受不受信任的输入并将其作为命令(一部分)运行的东西,例如xargs
和parallel
。
答案3
1. 问题的原因是否是
find -exec sh -c "something {}" \;
替换为{}
未加引号,因此不被视为单个字符串?
从某种意义上来说,但是引用在这里没有帮助。被替换的文件名{}
可以包含任何人物,包括引号。无论使用哪种形式的引用,文件名都可以包含相同的引用,并“打破”引用。
2. ...但是由于
{}
未加引号,是否"$@"
也存在与原始命令相同的问题?例如,"$@"
将扩展到"/tmp/foo;", "rm", "-rf", and "$HOME"
?
No."$@"
扩展为位置参数,作为单独的单词,并且不他们进一步分裂。这里,{}
是 in 本身的一个参数find
,并将find
当前文件名也作为不同的参数传递给sh
.它可以直接作为 shell 脚本中的变量使用,而不是作为 shell 命令本身进行处理。
...为什么
{}
不转义或引用?
在大多数 shell 中,不需要如此。如果您运行fish
,它需要:fish -c 'echo {}'
打印一个空行。但如果你引用它也没关系,shell 只会删除引号。
3.你能举出其他例子吗...
任何时候您在被视为某种代码(*)的字符串中按原样扩展文件名(或另一个不受控制的字符串)时,都可能执行任意命令。
例如,这会$f
直接扩展 Perl 代码,如果文件名包含双引号,则会导致问题。文件名中的引号将结束 Perl 代码中的引号,文件名的其余部分可以包含任何 Perl 代码:
touch '"; print "HELLO";"'
for f in ./*; do
perl -le "print \"size: \" . -s \"$f\""
done
(文件名必须有点奇怪,因为 Perl 在运行任何代码之前都会预先解析整个代码。所以我们必须避免解析错误。)
虽然这可以安全地通过参数:
for f in ./*; do
perl -le 'print "size: " . -s $ARGV[0]' "$f"
done
(直接从一个shell运行另一个shell是没有意义的,但如果这样做的话,情况也类似find -exec sh ...
)
(* 某些代码包含 SQL,因此必须 XKCD:https://xkcd.com/327/加解释: https://www.explainxkcd.com/wiki/index.php/Little_Bobby_Tables)
答案4
您的担忧正是 GNU Parallel 引用输入的原因:
touch "hello; echo pwned"
find . -print0 | parallel -0 printf \"Argument: %s\\n\" {}
这不会运行echo pwned
。
它将运行一个 shell,因此如果您扩展命令,您不会突然感到惊讶:
# This _could_ be run without spawining a shell
parallel "grep -E 'a|bc' {}" ::: foo
# This cannot
parallel "grep -E 'a|bc' {} | wc" ::: foo
有关生成 shell 问题的更多详细信息,请参阅:https://www.gnu.org/software/parallel/parallel_design.html#Always-running-commands-in-a-shell