Bash软件中声名狼藉的bug,CVE-ID为CVE-2014-6271(译者注:CVE即Common Vulnerabilities and Exposures,这个系统为公开的信息安全漏洞提供参考信息),现在有了新的名字“ShellShock”(译者注:在英语中ShellShock是指弹震症,一种精神疾病,参考这里)。精心伪造的数据通过网络传到一台服务器上,直接或间接触发一个bash脚本,就可以远程执行恶意代码。最初的bug已经修复了,但引发了人们对Bash的解析程序可能产生0day漏洞的关切,随后又挖掘出了第二个漏洞CVE-2014-7169,这个漏洞也在前天得到了修复。但这种漏洞的根源到底是什么?同一类型的漏洞已经全部被消灭了吗?FreeBSD 和NetBSD已经默认关闭了自动导入函数的功能,以应对未来可能出现的漏洞。
这个问题的发生是因为Bash的一个功能(不是bug吗?),它允许在Bash的shell中使用环境变量来定义函数。函数的作用是把经常调用的代码封装起来,然后在其他地方复用,所有的shell脚本语言都有这个功能。Bash中函数的定义是这样的(大多数其他shell也是):
function hello { echo "Hello" } hello # 调用这个函数
但是,Bash还有一种使用环境变量来定义函数的方法,这是它独有的。如果环境变量的值以字符“(){”开头,这个变量就会被当作是一个导入函数的定义,这种定义只有在shell启动的时候才生效。
$ export HELLO="() { echo 'Hello'; }" $ HELLO -bash: HELLO: command not found $ bash $ HELLO Hello
因为它只会在shell启动的时候生效,所以大多数用来演示这个漏洞的示例代码都只有一行:
env HELLO="() { echo 'Hello'; }" bash -c HELLO
这行代码的作用跟上面的例子是一样的。(env命令代表“先设置下面的环境变量,再运行下面的程序”,并且执行完成后当前的环境变量不受影响。实际上,直接写成HELLO="() { echo 'Hello'; }" bash -c HELLO也可以。)这一行bash命令指定了-c选项是为了启动bash时就执行HELLO函数,这里必须新起一个bash,因为只有bash启动的时候才会去解析函数的定义。
解析代码中最早发现的缺陷发生在bash解析完函数定义,执行函数的时候,所以:
env CVE_2014_6271="() { echo 'Hello';}; echo 'Goodbye'" bash -c CVE_2014_6271
在修复后的系统中,上面命令的结果应该只打印“Hello”,而有漏洞的系统还会把“Goodbye”打出来。问题出在自动导入函数的解析器越过了函数定义的结尾,接着执行后面的代码——并且由于每一个新的bash启动时都会触发这个漏洞,相当于任意代码都能被执行了。
(译者注:读者需要明白,执行CVE_2014_6271这个函数并不会导致打印Goodbye,导入CVE_2014_6271这个函数才会导致打印Goodbye。把两者放在一起有一定的误导性。)
这个问题因为两个原因被放大了:首先,Bash是一个被广泛集成的软件,所有的系统都在运行,从Rapsberry Pis到手机,再到数据中心的服务器以及大型机。由于自动导入函数的功能至少从Bash 3.0开始就存在了,所以这个bug有可能在大多数系统中存在近20年了。其次,当我们在Apache服务器中使用mod_cgi(不包括mod_php或mod_python)运行脚本的时候,数据是通过环境变量来传递的,这可以算是互联网领域最古老的一些技术了。其他一些客户端也会受到影响——比如Linux的DHCP客户端——它大量运用Bash脚本来使修改生效,这也使黑客能通过在DHCP数据包中加入恶意数据来达到攻击的目的。
鉴于Bash是大多数Linux系统(以及OSX)上默认的shell,这个漏洞就意味着,把有害数据编入环境变量,传到服务器端,触发服务器运行脚本,就完成了攻击。举个例子,HTTP协议的头User-Agent通常是通过环境变量HTTP_USER_AGENT来传递的,这意味使用以下命令就可以利用这个漏洞了:
curl -A "() {:;}; echo 'Game Over'}" http://example.com/some-cgi/script.cgi
(译者注:这条命令的意思是以"() {:;}; echo 'Game Over'}"为user agent去下载那个脚本。)
对于不传递user agent的服务器来说,常常还有其他受攻击的可能——比如Cookie,或者请求本身。
注意,这个bug不仅仅影响CGI脚本和Apache——如果其他程序也收到并传递了有害的环境变量(比如ssh服务会接收TERM或DISPLAY环境变量),然后这些进程再运行一个Bash脚本(或通过system()调用来执行),同样的漏洞也会被利用。和HTTP不一样,ssh一般不允许匿名请求——触发这个漏洞之前必须要登录——但是代码托管服务商们却允许匿名登录(哪怕只登录到一个权限受限的shell),所以为了防止入侵,GitHub更新了他们的企业级产品,Bitbucket也更新了他们的服务器。
CVE-2014-6271中的bug修复后,问题马上就解决了,大多数厂商都及时提供了修复后的Bash版本。面向互联网的服务器没有理由不马上修复它,因为这个漏洞会使主机完全落入别人的控制(以Apache所使用的用户身份)中。
但是,大家的目光已经聚焦在这个领域,新的bug被发现了。同时使用Bash的shell重定向功能和函数自动导入功能,CVE-2014-7169出现了。这回导致的结果是可以随意读写远程机器上的文件,使用的手段和上次一样,只不过这次是利用了shell的重定向符号<或>。
env CVE_2014_7169='() { (a)=>\' bash -c "echo date"; cat echo
这次解析器先停在=号上(由于(a)=不是一个有效的Bash表达式),但至关重要的是把<号留在了解析管道中。接下来的转义符\会使解析器在重定向命令之间插入一些空格(但无关紧要),最终导致了后面的命令变成了一条重定向命令:
>echo data
这是一条有效的Bash命令,语义上它等价于下面这种更常见的形式:
date >echo
注意,另外一个重定向符号<在这里也是有效的,它可以把输入重定向到文件。
所以这个bug也被修复了。有人担心,修复Bash的自动导入函数功能中的边界情况所引起的bug,会演变成一场打地鼠游戏:一个bug刚刚被消灭,另外一个马上又会冒出来。甚至在这些bug被发现之前,就有人担心使用Bash的自动导入函数功能会导致系统中那些不需要使用绝对路径来访问的标准程序被覆盖:
$ env ls="() { echo 'Game over'; }" bash -c ls Game over $ env ls="() { echo 'Game over'; }" bash -c /bin/ls Applications Desktop Documents Downloads ...
(在最近几个patch发布之前,我们可以通过env /bin/ls="() { echo 'Game over'; }" bash -c /bin/ls来覆盖绝对路径,但在最新的集成所有补丁的版本中,这个问题已经被修复了。)
但是很多可执行程序在运行外部程序的时候并没有指定它的路径,这意味着精心设计好一个函数变量,就可以让程序做它不能做的事情:
$ touch /tmp/a /tmp/b $ env test='() { echo vulnerable >&2; }' /usr/bin/bzdiff /tmp/a /tmp/b vulnerable bzip2: Can't open input file a.bz2: No such file or directory.
(译者注:上面的例子的意思是,bzdiff命令里面会调用test命令,但又没有通过绝对路径来调用它,所以test命令被我们自己伪造的test函数覆盖了。)
当然,有能力设置任何环境变量,可以使攻击者控制一切东西——修改IFS环境变量(过去被利用过的一个漏洞),甚至修改PATH环境变量,会影响新启动的shell脚本的行为,无论如何这些手段都会导致问题。但至少前面提到的SSH和CGI攻击中,涉及的环境变量要么是有限几个(TERM、DISPLAY等),要么是以某个前缀(HTTP_USER_AGENT、HTTP_REFERRER)开头的。所以除了前缀名所代表的程序外,其他的程序受影响有限。
有些人预计Bash自动导入函数的功能还存在安全漏洞,担心未来还会有bug曝出。NetBSD在Bash中默认关闭了自动导入函数的功能,FreeBSD也这么做了,转而以新增选项( --import-functions)的方式提供这个功能。虽然这种做法破坏了后向兼容,但这个时候小心为上,而且可能过不了多久,这个选项就会出现在上游代码(upstream)中了。OSX的用户也有对应的补丁可以下载。
InfoQ会关注事态发展,报道事态变化。
9月29日更新:上面提到的缺陷已经在最新一组补丁中得到修复。除了苹果以外,所有厂商都提供了修复后的版本供下载。
查看英文原文:ShellShocked - Behind the Bug