env x ='(){:;};是什么?命令” bash做,为什么它不安全?


237

bash中显然存在一个漏洞(CVE-2014-6271):Bash特制的环境变量代码注入攻击

我试图弄清楚正在发生什么,但是我不确定我是否完全理解。如何echo使用单引号将其执行?

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
vulnerable
this is a test

编辑1:修补的系统如下所示:

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `x'
this is a test

编辑2:有一个相关的漏洞/补丁:CVE-2014-7169,它使用略有不同的测试:

$ env 'x=() { :;}; echo vulnerable' 'BASH_FUNC_x()=() { :;}; echo vulnerable' bash -c "echo test"

未修补的输出

vulnerable
bash: BASH_FUNC_x(): line 0: syntax error near unexpected token `)'
bash: BASH_FUNC_x(): line 0: `BASH_FUNC_x() () { :;}; echo vulnerable'
bash: error importing function definition for `BASH_FUNC_x'
test

部分(早期版本)修补输出

bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `x'
bash: error importing function definition for `BASH_FUNC_x()'
test

修补了CVE-2014-7169及以下的输出

bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `BASH_FUNC_x'
test

编辑3:故事继续:


执行的不是回声。其x的函数定义。如果x中定义的函数完成了一些偷偷摸摸的工作,如果函数x是实数,bash就无法检查返回值。注意,测试代码中的功能为空。未经检查的返回值可能导致脚本注入。脚本注入导致特权升级,特权升级导致root用户访问。该补丁禁用了x作为函数的创建
eyoung100

26
eyoung100,没有回声得到执行。您可以看到它正在执行,因为单词vulnerable出现在输出中。主要问题在于bash 也在函数定义之后解析并执行代码。有关其他示例,请参见seclists.org/oss-sec/2014/q3/650/bin/id部分。
Mikel 2014年

4
只是一个简短的评论。红帽建议,已发布的补丁程序只是部分补丁程序,使系统仍然处于危险之中。
彼得

2
@ eyoung100的不同之处在于,函数中的代码仅在显式调用环境变量时执行。每当新的Bash进程启动时,函数定义之后的代码就会执行。
David Farrell 2014年

1
stackoverflow.com/questions/26022248/...了解更多详细信息
Barmar

Answers:


204

bash将导出的函数定义存储为环境变量。导出的函数如下所示:

$ foo() { bar; }
$ export -f foo
$ env | grep -A1 foo
foo=() {  bar
}

也就是说,环境变量foo具有文字内容:

() {  bar
}

当启动新的bash实例时,它将查找这些特制的环境变量,并将其解释为函数定义。您甚至可以自己编写一个,然后仍然可以使用:

$ export foo='() { echo "Inside function"; }'
$ bash -c 'foo'
Inside function

不幸的是,从字符串(环境变量)解析函数定义可能会产生比预期更广泛的影响。在未修补的版本中,它也解释在函数定义终止后发生的任意命令。这是由于在环境中确定可接受的类似函数的字符串时约束不足。例如:

$ export foo='() { echo "Inside function" ; }; echo "Executed echo"'
$ bash -c 'foo'
Executed echo
Inside function

请注意,函数定义外部的回显在bash启动期间已意外执行。函数定义只是进行评估和利用的一步,函数定义本身和使用的环境变量是任意的。外壳程序查看环境变量,看到foo,它看起来像满足了它所知道的关于函数定义的约束,并评估了该行,无意中还执行了echo(可能是任何命令,无论是否恶意)。

之所以认为这是不安全的,是因为变量本身通常不被允许或期望直接导致它们中包含的任意代码的调用。也许您的程序从不受信任的用户输入中设置了环境变量。出乎意料的是,可以以这样的方式操纵这些环境变量,即用户可以出于代码中声明的原因使用该环境变量而无需您的明确意图就可以运行任意命令。

这是一个可行的攻击示例。您运行的Web服务器在其生命周期中的某个地方运行有漏洞的Shell。该Web服务器将环境变量传递给bash脚本,例如,如果您使用的是CGI,则通常会将HTTP请求的信息作为环境变量包含在Web服务器中。例如,HTTP_USER_AGENT可能设置为您的用户代理的内容。这意味着如果您将用户代理欺骗为类似'(){:; }; 当该shell脚本运行时,echo foo将执行echo foo' 。再次,echo foo可以是任何东西,无论是否恶意。


3
这样会影响其他像Zash一样的Bash外壳吗?
阿梅利奥·瓦兹奎兹·雷纳

3
@ user815423426否,zsh没有此功能。Ksh拥有它,但是实现方式有所不同,我认为功能只能在非常狭窄的情况下(仅在Shell分叉的情况下)才能传递,而不能通过环境传递。
吉尔斯2014年

20
@ user815423426 rc是在环境中传递函数的另一个shell,但是它的变量名称以“ fn_”为前缀,并且仅在调用时解释。
斯特凡Chazelas

18
@StéphaneChazelas-感谢您报告错误。
Deer Hunter

13
@gnclmorais您的意思是您运行export bar='() { echo "bar" ; }'; zsh -c bar并显示bar而不是zsh:1: command not found: bar?您确定不会将正在调用的Shell与用于设置测试的Shell混淆吗?
吉尔斯2014年

85

这可能有助于进一步说明发生了什么:

$ export dummy='() { echo "hi"; }; echo "pwned"'
$ bash
pwned
$

如果您正在运行易受攻击的shell,那么当您启动一个新的子shell(在这里,只需使用bash语句)时,您会看到任意代码(echo "pwned")作为其初始化的一部分立即被执行。显然,外壳程序看到环境变量(虚拟)包含一个函数定义,并评估该定义以便在其环境中定义该函数(请注意,它没有执行该函数:将显示“ hi”。)

不幸的是,它不仅评估函数定义,而且评估环境变量值的整个文本,包括遵循函数定义的可能的恶意语句。请注意,如果没有初始函数定义,则不会评估环境变量,只会将其作为文本字符串添加到环境中。正如Chris Down指出的那样,这是一种用于实现导入的Shell函数导入的特定机制。

我们可以看到已经在新shell中定义的函数(并且已将其标记为已导出到那里),并且可以执行它。此外,哑元尚未作为文本变量导入:

$ declare -f
dummy ()
{
    echo "hi"
}
declare -fx dummy
$ dummy
hi
$echo $dummy
$

该功能的创建或运行将不会做的任何事情都不是漏洞利用程序的一部分-它只是执行漏洞利用程序的工具。关键是,如果攻击者可以在输入到导出的环境变量中的文本字符串中提供恶意代码(其开头是最小且不重要的函数定义),则它将在启动子外壳程序时执行,这是常见事件在许多脚本中。此外,它将以脚本特权执行。


17
如果您仔细阅读,可接受的答案实际上会说出这一点,但我发现这个答案更加清楚,对理解问题的根源在于定义的评估(而不是执行函数本身)更有用。
natevw

1
为什么这个示例export在其他示例中却拥有命令env?我当时在想env用来定义启动另一个bash shell时将调用的环境变量。那么这是如何处理的export
Haris 2014年

直到这一刻,还没有一个公认的答案。我可能要再等几天才能接受。该答案的缺点是,它不会分解原始命令,也不会讨论如何从问题中的原始命令到此答案中的命令,以表明它们是相同的。除此之外,这是一个很好的解释。
jippie 2014年

@ralph- envexport导出环境定义,以便它们在子外壳中可用。实际上,问题在于如何将这些导出的定义导入到子Shell的环境中,尤其是在导入函数定义的机制中。
sdenham 2014年

1
@ralph- env运行带有一些选项和环境变量的命令。请注意,在原始问题示例中,env将其设置x为字符串,然后调用bash -c以运行命令。如果这样做env x='foo' vim,Vim将会启动,然后您可以使用调用其中的Vim外壳/环境!echo $x,并进行打印foo,但是如果您退出和echo $x,则它不会被定义,因为它仅在vim运行时存在通过env命令。该export命令改为在当前环境中设置持久性值,以便稍后运行的子外壳将使用它们。
Gary Fixler 2014年

72

我将其写为教程式的重铸,上面是Chris Down的出色回答。


在bash中,您可以有这样的shell变量

$ t="hi there"
$ echo $t
hi there
$

默认情况下,这些变量不会由子进程继承。

$ bash
$ echo $t

$ exit

但是,如果将它们标记为要导出,bash将设置一个标志,这意味着它们将进入子进程环境(尽管envp很少看到该参数,但是mainC程序中有三个参数:main(int argc, char *argv[], char *envp[])最后一个指针数组是一个数组) shell变量及其定义)。

因此,我们导出t如下:

$ echo $t
hi there
$ export t
$ bash
$ echo $t
hi there
$ exit

上面的内容t在子外壳中是未定义的,现在它在我们导出后显示(export -n t如果要停止导出,请使用)。

但是bash中的功能是另一种动物。您可以这样声明它们:

$ fn() { echo "test"; }

现在,您可以通过调用它来调用该函数,就好像它是另一个shell命令一样:

$ fn
test
$

再一次,如果您生成一个子shell,则不会导出我们的函数:

$ bash
$ fn
fn: command not found
$ exit

我们可以使用导出一个函数export -f

$ export -f fn
$ bash
$ fn
test
$ exit

这是棘手的部分:fn就像我们对shell变量的导出一样,将类似export的函数转换为环境变量t。如果fn是局部变量,则不会发生这种情况,但是在导出后,我们可以将其视为shell变量。但是,您也可以使用具有相同名称的常规(即非函数)shell变量。bash根据变量的内容进行区分:

$ echo $fn

$ # See, nothing was there
$ export fn=regular
$ echo $fn
regular
$ 

现在,我们可以env用来显示所有标记为导出的shell变量,并且常规fn和函数都将fn显示:

$ env
.
.
.
fn=regular
fn=() {  echo "test"
}
$

子外壳将同时包含两个定义:一个作为常规变量,一个作为函数:

$ bash
$ echo $fn
regular
$ fn
test
$ exit

您可以fn像上面一样定义,也可以直接定义为常规变量赋值:

$ fn='() { echo "direct" ; }'

请注意,这是一件非常不寻常的事情!通常,我们会fn像上面使用fn() {...}语法那样定义函数。但是由于bash通过环境将其导出,因此我们可以直接“捷径”到上面的常规定义。需要注意的是(反你的直觉,也许)这并不会导致新的功能fn在当前shell中可用。但是,如果您生成一个** sub **外壳,那么它将。

让我们取消函数的导出,fn并保持新的常规fn(如上所示)完整无缺。

$ export -nf fn

现在,该函数fn不再被导出,而是常规变量fn被包含() { echo "direct" ; }在其中。

现在,当子外壳程序看到()以其开头的常规变量时,会将其余变量解释为函数定义。但这是在新的外壳开始时。正如我们在上面看到的,仅定义一个以shell开头的常规shell变量()并不会使其表现得像一个函数。您必须启动一个子shell。

现在出现“ shellshock”错误:

正如我们所看到的,当一个新的shell吸收一个常规变量的定义时,首先将()其解释为一个函数。但是,如果在定义函数的右花括号后有更多给出的内容,它将执行其中的所有内容。

这些又是要求:

  1. 产生了新的bash
  2. 提取了环境变量
  3. 此环境变量以“()”开头,然后在花括号中包含一个函数主体,然后再包含命令

在这种情况下,易受攻击的bash将执行后面的命令。

例:

$ export ex='() { echo "function ex" ; }; echo "this is bad"; '
$ bash
this is bad
$ ex
function ex
$

常规导出的变量ex被传递到子外壳,该子外壳被解释为函数,ex但是this is bad在子外壳生成时执行了尾随命令()。


解释光滑的单线测试

@jippie的问题中引用了一种流行的用于测试Shellshock漏洞的单线代码:

env x='() { :;}; echo vulnerable' bash -c "echo this is a test"

这里是一个细分:首先,:in bash只是的简写truetrue并且:都以bash评估为(您猜对了)true:

$ if true; then echo yes; fi
yes
$ if :; then echo yes; fi
yes
$

其次,env命令(也内置在bash中)打印环境变量(如我们在上面看到的),但也可以用于运行单个命令,并为该命令提供导出的变量(或多个变量),并bash -c从其运行单个命令命令行:

$ bash -c 'echo hi'
hi
$ bash -c 'echo $t'

$ env t=exported bash -c 'echo $t'
exported
$

因此,将所有这些东西缝合在一起,我们可以将bash作为命令运行,给它做一些虚拟的事情(例如bash -c echo this is a test),并导出以开头的变量,()以便子shell将其解释为函数。如果存在shellshock,它还将立即执行子shell中的所有尾随命令。由于我们传递的函数与我们无关(但必须解析!),因此我们使用可以想象的最短有效函数:

$ f() { :;}
$ f
$ 

f这里的函数只是执行:命令,该命令返回true并退出。现在,在该“邪恶”命令之后附加一个常规变量并将其导出到子外壳中,您将获胜。这是单线纸:

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"

因此,x将其导出为带有简单有效函数并附echo vulnerable加到最后的常规变量。这被传递给bash,bash解释x为一个函数(我们不在乎),然后echo vulnerable如果存在shellshock则执行该函数。

我们可以通过删除以下this is a test消息来缩短单线:

$ env x='() { :;}; echo vulnerable' bash -c :

这不会打扰,this is a test但会:再次运行无声命令。(如果不选择,-c :那么您将坐在子shell中,必须手动退出。)也许最用户友好的版本是以下版本:

$ env x='() { :;}; echo vulnerable' bash -c "echo If you see the word vulnerable above, you are vulnerable to shellshock"

12
很好的解释。这个问题正在接受很多观点(可能不是每个人都像其他人一样精通bash),我相信没有人对{ :;};实际所说的话花了几句话。我认为,这将是对您的答案的一个很好的补充。可以解释您如何从示例中获取问题的原始命令?
jippie 2014年

20

如果您可以将任意环境变量提供给程序,则可以通过使其加载您选择的库来使它执行几乎所有操作。在大多数情况下,这在接收那些环境变量的程序中不被视为漏洞,而是在外人可以输入任意环境变量的机制中被视为漏洞。

但是CVE-2014-6271是不同的。

在环境变量中拥有不受信任的数据没有什么错。只需确保它不会放入任何可以修改程序行为的环境变量中即可。稍微抽象一点,对于特定的调用,您可以创建环境变量名称的白名单,外部人员可以直接指定这些白名单。

在CVE-2014-6271上下文中提出的示例是用于解析日志文件的脚本。那些可能非常需要在环境变量中传递不受信任的数据。当然,选择这种环境变量的名称时应避免任何不利影响。

但是,这个特殊的bash漏洞有什么坏处。可以通过任何变量名来利用它。如果您创建一个名为的环境变量GET_REQUEST_TO_BE_PROCESSED_BY_MY_SCRIPT,那么除了您自己的脚本之外,您不会期望其他任何程序来解释该环境变量的内容。但是,通过利用此bash错误,每个环境变量都将成为攻击媒介。

注意,这并不意味着环境变量的名称应该是秘密的。知道所涉及的环境变量的名称并不会使攻击变得更加容易。

如果program1调用program2又调用program3,则program1可以program3通过环境变量传递数据。每个程序都有其设置的环境变量的特定列表以及对其执行操作的特定列表。如果选择的名称不被识别program2,则可以从传递数据program1program3而不必担心会对产生不利影响program2

如果名称集之间没有重叠,则知道由导出的变量的确切名称和由program1解释的变量的名称的攻击者program2无法利用此知识来修改“ program2”的行为。

但这如果program2bash脚本则无法解决,因为由于该错误,bash会将每个环境变量都解释为代码。


1
“每个环境变量都成为攻击的载体”-这就是我所缺少的部分。谢谢。
wrschneider 2014年

9

在您链接的文章中对此进行了解释...

您可以在调用bash shell之前使用特制的值创建环境变量。这些变量可以包含代码,该代码在调用外壳程序后立即执行。

这意味着被调用的bash在被调用时-c "echo this is a test"执行单引号中的代码。

Bash具有功能,尽管在某种程度上受实现的限制,并且可以将这些Bash函数放入环境变量中。当在这些函数定义的末尾(在环境变量内部)添加额外的代码时,就会触发此缺陷。

意味着您发布的代码示例利用了这样的事实,即被执行的bash在执行分配后不会停止评估此字符串。在这种情况下的功能分配。

据我了解,关于您发布的代码段的实际特殊之处在于,通过在我们要执行的代码之前使用函数定义,可以规避某些安全机制。

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.