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: 故事继续:
答案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
请注意,函数定义之外的 echo 在 bash 启动期间意外执行。函数定义只是进行评估和利用的一个步骤,函数定义本身和使用的环境变量是任意的。 shell 查看环境变量,sees foo
,它看起来满足它所知道的函数定义的约束,并且它评估该行,无意中还执行了 echo(可能是任何命令,无论是否恶意)。
这被认为是不安全的,因为通常不允许或不期望变量本身直接导致调用其中包含的任意代码。也许您的程序根据不受信任的用户输入设置环境变量。令人非常意外的是,这些环境变量可以以这样的方式进行操作:用户可以运行任意命令,而无需您出于代码中声明的原因使用该环境变量明确意图执行此操作。
这是一个可行的攻击示例。作为其生命周期的一部分,您运行的 Web 服务器在某处运行易受攻击的 shell。该 Web 服务器将环境变量传递给 bash 脚本,例如,如果您使用 CGI,则有关 HTTP 请求的信息通常作为来自 Web 服务器的环境变量包含在内。例如,HTTP_USER_AGENT
可能设置为您的用户代理的内容。这意味着如果您将用户代理欺骗为类似 '() { :; }; echo foo',当该 shell 脚本运行时,echo foo
将被执行。同样,echo foo
可以是任何东西,无论是否恶意。
答案2
这可能有助于进一步证明正在发生的事情:
$ export dummy='() { echo "hi"; }; echo "pwned"'
$ bash
pwned
$
如果您正在运行易受攻击的 shell,那么当您启动新的子 shell(此处仅使用 bash 语句)时,您将看到任意代码 ( echo "pwned"
) 作为其启动的一部分立即执行。显然,shell 看到环境变量(虚拟)包含一个函数定义,并评估该定义,以便在其环境中定义该函数(请注意,它没有执行该函数:这将打印“hi”。)
不幸的是,它不仅评估函数定义,还评估环境变量值的整个文本,包括函数定义后面可能存在的恶意语句。请注意,如果没有初始函数定义,则不会评估环境变量,它只会作为文本字符串添加到环境中。正如 Chris Down 所指出的,这是一种实现导出的 shell 函数导入的特定机制。
我们可以看到新 shell 中定义的函数(并且它已被标记为导出),并且我们可以执行它。此外,dummy 尚未作为文本变量导入:
$ declare -f
dummy ()
{
echo "hi"
}
declare -fx dummy
$ dummy
hi
$echo $dummy
$
这个函数的创建,以及它运行时要做的任何事情,都不是漏洞利用的一部分——它只是执行漏洞利用的工具。关键是,如果攻击者可以在放入导出的环境变量的文本字符串中提供恶意代码,前面是最小且不重要的函数定义,那么它将在子 shell 启动时执行,这是一个常见事件在许多脚本中。此外,它将以脚本的权限执行。
答案3
我写这篇文章是对上面 Chris Down 的优秀答案的教程式重铸。
在 bash 中你可以有这样的 shell 变量
$ t="hi there"
$ echo $t
hi there
$
默认情况下,这些变量不会被子进程继承。
$ bash
$ echo $t
$ exit
但是如果你将它们标记为导出,bash 将设置一个标志,这意味着它们将进入子进程的环境(尽管该envp
参数不多见,但main
在你的 C 程序中,它有三个参数:main(int argc, char *argv[], char *envp[])
其中最后一个指针数组是一个数组shell 变量及其定义)。
所以我们导出t
如下:
$ echo $t
hi there
$ export t
$ bash
$ echo $t
hi there
$ exit
虽然上面t
在子 shell 中未定义,但现在在我们导出它之后出现(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 变量一样。当它是局部变量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"
}
$
子 shell 将摄取两种定义:一种作为常规变量,另一种作为函数:
$ bash
$ echo $fn
regular
$ fn
test
$ exit
您可以fn
像上面那样定义,或者直接作为常规变量赋值:
$ fn='() { echo "direct" ; }'
请注意,这是一件非常不寻常的事情!通常我们会fn
像上面那样使用语法来定义函数fn() {...}
。但由于 bash 通过环境导出它,我们可以直接“捷径”到上面的常规定义。请注意(也许与您的直觉相反)这确实不是导致fn
当前 shell 中可用的新函数。但如果你生成一个 **sub**shell,那么它就会。
让我们取消函数的导出fn
并保持新的常规fn
(如上所示)完好无损。
$ export -nf fn
现在函数fn
不再被导出,但常规变量fn
是,并且它包含() { echo "direct" ; }
在其中。
现在,当子 shell 看到以它开头的常规变量时,()
会将其余部分解释为函数定义。但这是仅有的当新的 shell 开始时。正如我们在上面看到的,仅仅定义一个以 开头的常规 shell 变量()
并不会导致它表现得像一个函数。您必须启动一个子外壳。
现在是“shellshock”错误:
正如我们刚刚看到的,当一个新的 shell 获取以它开头的常规变量的定义时,()
会将其解释为函数。但是,如果在定义函数的右大括号之后给出更多内容,则执行任何事情也有吗。
这些是要求,再次:
- 新的bash诞生了
- 环境变量被摄取
- 该环境变量以“()”开头,然后在大括号内包含函数体,然后是命令
在这种情况下,易受攻击的 bash 将执行后面的命令。
例子:
$ export ex='() { echo "function ex" ; }; echo "this is bad"; '
$ bash
this is bad
$ ex
function ex
$
常规导出变量ex
被传递到子 shell,该子 shell 被解释为函数,但尾随命令在子 shell 生成时ex
执行 ( )。this is bad
解释流畅的单行测试
@jippie 的问题中引用了一种流行的用于测试 Shellshock 漏洞的单行代码:
env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
下面是一个细分:首先:
bash 中的 仅仅是 的简写true
。 在 bash 中true
,两者:
都评估为(你猜对了)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 并退出。现在附加一些“邪恶”命令并将常规变量导出到子shell,您就赢了。这又是一句单行话:
$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
Sox
导出为常规变量,并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"
答案4
您链接的文章中对此进行了解释...
您可以在调用 bash shell 之前使用特制值创建环境变量。这些变量可以包含代码,一旦调用 shell,这些代码就会被执行。
这意味着调用的 bash-c "echo this is a test"
在调用时会执行单引号中的代码。
Bash 具有函数,尽管实现有些有限,并且可以将这些 bash 函数放入环境变量中。当额外的代码添加到这些函数定义的末尾(环境变量内)时,会触发此缺陷。
意味着您发布的代码示例利用了这样一个事实:调用的 bash 在执行赋值后不会停止评估该字符串。本例中的函数分配。
据我了解,您发布的代码片段的真正特殊之处在于,通过在我们要执行的代码之前放置函数定义,可以绕过一些安全机制。