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


18

从findutils的手册中:

例如这两个命令的构造

# risky
find -exec sh -c "something {}" \;
find -execdir sh -c "something {}" \;

非常危险 这样做的原因是将'{}'扩展为一个文件名,该文件名可能包含分号或外壳程序专用的其他字符。例如,如果有人创建了文件, /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`执行的命令提供参数的方法

谢谢。


Answers:


29

这实际上与引用无关,而与参数处理有关。

考虑一下危险的例子:

find -exec sh -c "something {}" \;
  • 这是由外壳,并分割解析成六个字:find-execsh-csomething {}(不包括引号更多);。没有什么可扩展的。Shell find以这六个词作为参数运行。

  • find认定的东西的过程,也就是说foo; rm -rf $HOME,它会替换{}foo; rm -rf $HOME,并运行sh的论据sh-csomething foo; rm -rf $HOME

  • sh现在看到-c,结果将解析something foo; rm -rf $HOME第一个非选项参数)并执行结果。

现在考虑更安全的变体:

find -exec sh -c 'something "$@"' sh {} \;
  • 外壳运行find的论据find-execsh-csomething "$@"sh{};

  • 现在,当find认定foo; rm -rf $HOME,它会替换{}一次,并运行sh的论据sh-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

在更安全的变体中,为什么{}不进行转义或引用?
蒂姆(Tim)

1
通常不需要引用它。它只需要在具有其他含义的shell中引用即可,而且我不认为当今使用的任何主要shell都是这种情况。
史蒂芬·基特

来自gnu.org/software/findutils/manual/html_mono/…:“这两种构造(;{})都必须转义(以'\'表示),或用引号引起来以防止它们被外壳扩展。” 您是说bash不正确吗?
蒂姆(Tim)

3
我的意思是,对于一般的外壳来说,这是不正确的。参见POSIXfindutils手册中的特定声明至少可以追溯到1996年。当然,如果引用{}'{}'"{}",也无害,但这不是必需的。
Stephen Kitt

@Tim您正在遇到一种普遍的情况,即您的直觉还不能解释以下事实:这里进行两种非常不同类型的参数处理:外壳何时/如何从一行文本中解析参数(是引号重要的地方)与原始操作系统参数传递(引号只是常规字符)不同。在shell命令中find some/path -exec sh -c 'something "$@"' {} \;,实际上三层参数处理/传递,其中两层是shell变体,另一层是基本的原始操作系统。
mtraceur

3

第1部分:

find 只是使用文本替换。

是的,如果您未加引号,例如:

find . -type f -exec sh -c "echo {}" \;

并且攻击者能够创建一个名为的文件; echo owned,然后它将执行

sh -c "echo ; echo owned"

这将导致运行shell echo然后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 "{}" \;

无效,因为外壳程序在运行之前会删除双引号find

find -exec sh -c 'something "$@"' sh "'{}'" \;

添加的引号表示shell不会对其进行特殊处理,因此在大多数情况下,这仅意味着该命令不会执行您想要的操作。

有它扩大到/tmp/foo;rm-rf$HOME应该不会有问题,因为那些参数something,并且something可能不会把它的参数作为执行命令。


第3部分:

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


xargs仅在使用-I或时-J才是危险的。在正常操作中,它只是将参数附加到列表的末尾,就像这样-exec ... {} +做一样。
查尔斯·达菲

1
parallel相反,默认情况下,在没有用户明确请求的情况下运行shell,这会增加易受攻击的表面;这是很多讨论的主题;有关说明,请参见unix.stackexchange.com/questions/349483/…,以及指向list.gnu.org/archive/html/bug-parallel/2015-05/msg00005.html的链接)。
Charles Duffy

@CharlesDuffy您的意思是“ 减少脆弱的表面”。由OP所示的攻击确实默认使用GNU并行工作。
Ole Tange

1
是的,我知道您需要它来实现SSH上的奇偶校验- 如果您没有parallel在任何套接字的另一端生成远程“接收器”进程,并且可以执行直接的无外壳程序execv。如果您愿意实施该工具,那正是我会做的。具有非交互式程序的行为基于当前值的不同而不同,这SHELL几乎不会让人感到惊讶。
查尔斯·达菲

1
是的-我认为除非用户明确启动外壳程序,否则管道和重定向应该是不可能的,此时用户只能获得明确启动的外壳程序的明确行为。如果仅能期望execv提供的东西,那将更简单,更令人惊讶,并且一条规则不会在位置/运行时环境中改变。
Charles Duffy

3

1.问题的原因是否find -exec sh -c "something {}" \;在于的替换{}未加引号,因此未视为单个字符串?

从某种意义上说,但引用在这里无济于事。被替换的文件名{}可以包含任何字符,包括引号。无论使用哪种形式的引用,文件名都可以包含相同的文件名,并且可以“分开”引用。

2 ....但是 {}未引用,所以也不会"$@"与原始命令有相同的问题吗?例如,"$@"将扩展为"/tmp/foo;", "rm", "-rf", and "$HOME"

No. "$@"扩展为位置参数(作为单独的单词),并且不会将其进一步拆分。这里,{}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

(直接从外壳程序运行另一个外壳程序没有意义,但是如果这样做,则类似于 find -exec sh ...情况)

(*某种代码包括SQL,因此必须使用XKCD:https://xkcd.com/327/ 加上说明:https: //www.explainxkcd.com/wiki/index.php/Little_Bobby_Tables )


1

您的担心正是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

有关spawning-a-shell问题的更多详细信息,请参见:https : //www.gnu.org/software/parallel/parallel_design.html#Always-running-commands-in-a-shell


1
...也就是说,find . -print0 | xargs -0 printf "Argument: %s\n"同样安全(或更确切地说,因为使用-print0正确处理换行符的文件名);parallel的引用是解决此问题的方法,该问题在没有外壳的情况下根本不存在
查尔斯·达菲

但是,一旦添加| wc到命令中,您就需要跳过箍以使其安全。默认情况下,GNU Parallel是安全的。
Ole Tange

ITYM:它试图通过仔细引用所有内容的复杂过程来涵盖shell语言的每一个怪癖。这远不如将数据正确存储在变量中,不与代码混合在一起那样安全。现在,如果要自动将其转换foo {} | wcsh -c 'foo "$1" | wcsh {}`,那可能会有所不同。
ilkkachu
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.