“运行任何将不可信数据传递给将参数解释为命令的命令的命令”

“运行任何将不可信数据传递给将参数解释为命令的命令的命令”

来自 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 命令文本要安全得多。

  1. 问题的原因是find -exec sh -c "something {}" \;替换为{}未加引号,因此不被视为单个字符串吗?
  2. 在解决方案中find -exec sh -c 'something "$@"' sh {} \;

    • 首先{}被替换,但由于{}未加引号,是否"$@"也有与原始命令相同的问题?例如, "$@"将扩展为"/tmp/foo;""rm""-rf""$HOME"?

    • 为什么{}不转义或引用?

  3. 您能否提供适用于相同类型的问题和解决方案的其他示例(仍然带有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 {} \;
  • shellfind使用参数find, -exec, sh, -c, something "$@", sh, {},运行;

  • 现在,当find找到时foo; rm -rf $HOME,它再次替换,并使用参数, , , ,{}运行。shsh-csomething "$@"shfoo; rm -rf $HOME

  • sh-c、 和something "$@"视为要运行的命令,并将shfoo; 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 部分:

我假设类似的考虑适用于任何接受不受信任的输入并将其作为命令(一部分)运行的东西,例如xargsparallel

答案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

相关内容