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

我试图弄清楚发生了什么,但是我不确定我是否完全理解它。

 echo 



EDIT 1:修补后的系统如下所示:

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


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

 $ 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
 


未打补丁的输出:

 $ 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
 


修补了直到CVE-2014-7169的输出:

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


编辑3:故事继续:


CVE-2014- 7186
CVE-2014-7187
CVE-2014-6277


评论

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

eyoung100,没有执行回显。您可以看到它正在执行,因为在输出中出现了脆弱字样。主要问题是bash也在函数定义之后解析并执行代码。有关其他示例,请参见seclists.org/oss-sec/2014/q3/650的/ bin / id部分。

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

@ eyoung100的区别在于,函数中的代码仅在显式调用环境变量时执行。每当新的Bash进程启动时,函数定义之后的代码就会执行。

有关其他详细信息,请参见stackoverflow.com/questions/26022248/…

#1 楼

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启动。函数定义只是进行评估和利用的一步,函数定义本身和使用的环境变量是任意的。 Shell查看环境变量,查看foo,它看起来像满足了它对函数定义的了解,并且评估了该行,无意中还执行了echo(可能是任何命令,无论是否恶意) 。

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

这里是一个可行的攻击的例子。您运行的Web服务器在其生命周期中的某个地方运行有漏洞的Shell。该Web服务器将环境变量传递到bash脚本,例如,如果您使用的是CGI,则有关HTTP请求的信息通常作为Web服务器中的环境变量包括在内。例如,可能将HTTP_USER_AGENT设置为用户代理的内容。这意味着如果您将用户代理欺骗为类似'(){:; }; echo foo',当该shell脚本运行时,将执行echo foo。同样,echo foo可能是恶意软件,也可能不是恶意软件。

评论


那会不会影响到像Zsh这样的其他Bash类外壳?

–阿梅里奥·巴斯克斯·雷纳(Amelio Vazquez-Reina)
2014-09-24 22:52

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

–吉尔斯'所以-不再是邪恶的'
2014-09-24 23:19

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

–StéphaneChazelas
2014-09-24 23:48

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

–鹿猎人
2014-09-25 4:55

@gnclmorais您的意思是您运行export bar ='(){echo“ bar”; }'; zsh -c bar,它显示bar而不是zsh:1:找不到命令:bar?您确定不会将正在调用的Shell与用于设置测试的Shell混淆吗?

–吉尔斯'所以-不再是邪恶的'
2014-09-25 13:06

#2 楼

这可能有助于进一步说明正在发生的情况: bash语句),您会看到任意代码(echo "pwned")作为其初始化的一部分立即被执行。显然,外壳程序看到环境变量(dummy)包含一个函数定义,并评估该定义以便在其环境中定义该函数(请注意,它没有执行该函数:将显示“ hi”。)不幸的是,它不仅评估函数定义,还评估环境变量值的整个文本,包括遵循函数定义的可能的恶意语句。请注意,如果没有初始函数定义,则不会评估环境变量,而只会将其作为文本字符串添加到环境中。正如Chris Down指出的那样,这是一种用于实现导出的Shell函数导入的特定机制。

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

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


创建此函数或运行该函数将不执行任何操作漏洞利用程序-只有执行漏洞利用程序的工具。关键是,如果攻击者可以在输入到导出的环境变量的文本字符串中提供恶意代码,然后加上最小且不重要的函数定义,则将在启动子shell时执行该代码,这是常见事件在许多脚本中。此外,它将以脚本特权执行。

评论


如果您仔细阅读,可接受的答案实际上是在说这句话,但我发现这个答案更加清楚,并且在理解问题的根源在于定义的评估(而不是执行函数本身)方面,对您有所帮助。

–natevw
2014-09-25 18:21

为什么这个示例在其他有env的情况下却具有export命令?我当时以为env被用来定义当另一个bash shell启动时将被调用的环境变量。那出口如何运作

–哈里斯
2014-09-25 19:12



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

–吉比
2014-09-25 19:31



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

–斯登纳姆
2014-09-26 14:46

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

–加里·FIX勒
2014-09-27 8:15



#3 楼

我将其写为教程式的重铸,上面是Chris Down的出色回答。 />
默认情况下,这些变量不会被子进程继承。

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


但是如果将其标记为导出,bash将设置一个标志,表示它们将进入子进程的环境中(尽管看不见envp参数,但是C程序中的main具有三个参数:main(int argc, char *argv[], char *envp[]),其中最后一个指针数组是具有其定义的shell变量数组)。

所以我们将t导出如下:

$ bash
$ echo $t

$ exit


尽管上面的t在子外壳中未定义,但现在在导出它后出现(如果需要,请使用export -n t停止输出)。

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

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


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

$ fn() { echo "test"; }


再次,如果您生成一个子shell,则不会导出我们的函数: br />
$ fn
test
$


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

$ bash
$ fn
fn: command not found
$ exit


子外壳程序将同时吸收两个定义:一个作为常规变量,一个作为函数:

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


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

$ echo $fn

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


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

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

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


现在不再输出函数fn,而是常规变量fn,并且其中包含fn

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

现在出现“ shellshock”错误:

正如我们刚刚看到的,当一个新的shell接收以()开头的常规变量的定义时它解释为一个功能。但是,如果在定义函数的右花括号之后有更多给出的内容,则它也将执行那里的所有内容。

这些又是要求:


产生了新的bash
摄取了环境变量
此环境变量以“()”开头,然后在花括号中包含一个函数主体,然后在其后具有命令

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

示例:

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


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

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

$ fn='() { echo "direct" ; }'
down:首先,bash中的this is bad只是:的简写。 truetrue都以bash计算为true(您猜对了):

$ export -nf fn


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

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


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

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


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

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


因此,将:导出为具有简单有效函数且带有x末尾的常规变量的常规变量。这被传递给bash,bash将echo vulnerable解释为一个函数(我们不在乎),然后如果存在shellshock,则可能执行x

我们可以通过删除echo vulnerable消息来稍微缩短单线:

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

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


这不会影响this is a test,但会再次运行无提示this is a test命令。 (如果不使用:,则您必须坐在子shell中,并且必须手动退出。)也许最用户友好的版本是以下版本:

$ f() { :;}
$ f
$ 


评论


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

–吉比
2014-09-26 5:27



#4 楼

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

CVE-2014-6271不同。

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

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

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

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

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

攻击者知道program1导出的变量的确切名称和名称如果名称集之间没有重叠,则由program2解释的变量中的一组不能利用此知识来修改'program2'的行为。这个错误program2会将每个环境变量都解释为代码。

评论


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

–wrschneider
2014年9月27日,1:17

#5 楼

您链接的文章中对此进行了说明。


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


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


Bash具有函数,尽管在实现上受到一定限制,并且
可以将这些bash函数放入环境变量中。
在这些功能定义的末尾(在环境变量内部)添加额外的代码时,会触发漏洞。


意味着您发布的代码示例利用了被调用的事实在执行分配后,bash不会停止评估该字符串。在这种情况下,是一个函数分配。

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