在前几天看到 CVE-2014-6277 这个 BASH 的 bug 的时候,我就觉得挺奇葩的:这这种明显是有意实现的功能怎么会存在这么大的安全隐患呢?我不是专门搞安全的,同时觉得,这个 bug 可能虽然影响广泛,但并不是什么很有技术含量的利用思路。所以我就把这个事情放下没去想它。然而,随之而来的国内国外的各种媒体宣传、安全专家的联名建议、茶余饭后的坊间畅谈……对于 BASH 的这个 bug 无人不认为是个大 bug。不少人为此还在幸灾乐祸……但是我却没从各种评论中找到我的问题的答案。
但是,当我看到这篇随笔时,我不得不赞同作者的观点:这根本不是 BASH 的 bug!
所以我将原文翻译至此,希望能够带来一些思考。
也许我们今天看到的一个愚蠢的 bug,在历史上的某一天,是一个有意而为之的神奇特性。也许我们应该思考的不仅仅是这一刻的 bug 或者安全隐患本身,而是在软件项目这个***工程和创作品双重特性的活动中,如何有效的保证某个特性不会变成 bug。所以什么规范了,文档了,可真得不是纸上谈兵!
不过话说回来,无论如何,我仍然坚信:“Less is exponentially more!(大道至简)”少一点 Feature 或许就是少一点 Bug 呢?
————翻译分隔线————
我想要讨论一下,关于这个 BASH 的安全问题其实不是一个 bug。这显然是一个特性。诚然,这是一个被错误实现并被错误使用的特性。不过它仍然是个特性。
问题在于它是在 25 年前设计的。Apache 在那五年之后才出现!没错,那个时候互联网已经有了,不过那是个亲密无间的小圈子。在那时,互联网软件和协议的安全根本不在考虑之列,更不用说开发像 shell 这样的程序的时候了。
因此,我们讨论的上下文是设计一个 UNIX shell,其子进程用非正式且常见的方式创建和运行。当创建一个新的子进程,环境变量可以用于从父进程向子进程传递某些数据。在 BASH 的情况里,这是一个需求提到的特性,在 BASH 父进程中定义的函数可以传递给子进程并获得定义。使用环境变量传递这些函数的定义是自然而然的事情。
而在环境变量传递的函数定义之后可以执行任何命令,就是那个略微超出设计规格的实现了。由于大部分情况下 BASH 程序在环境变量中存放的是要传递的函数,所以通常不会有其他命令。
不过在 BASH 被设计的时候,一个用户是否能添加命令到这样的环境变量中,会被认为是一个特性。在任何情况下,子进程的行为依赖于配置这个环境变量的用户,所以没有任何关于安全方面的担忧。
这个特性被记录在用于导出内建命令的 -f 选项中。而使用内容以“() {”开始的环境变量在函数定义后可以包含更多的命令的实现细节并没有文档记录,不过仍然可以认为是一个特性。
问题在于,五年后,新的使用 BASH 作为子进程,并仍然使用环境变量传递数据的软件被开发出来(Apache,DHCP 等等)。不幸的是,一些数据不再是来自可信的本地系统用户(译注:典型的物理安全信任),而是来自这个星球上的,互联网上任意的用户或者程序。同时,没有文档记录的(但已经被发布的)BASH 的特性却被遗忘了。
这是一个关于 UNIX 系统的古老观念,环境变量可以由父进程控制,还有更古老、更通用的观念是输入的数据应当是合法的。
大概像 Apache 这样的程序都对环境变量进行了正确的过滤。但不幸的是,它们在正确校验输入的数据时失败了,因为它们并未考虑到以“() {” 开始的数据,将会被它们产生的 BASH 子进程解释。如果这里有 bug 的话,也不是在 BASH 中,而是在 Apache 以及其他面向网络的程序,没有正确的验证和控制它们传递给 BASH 的数据。
也就是说,BASH 有自己的问题,只不过跟大多数软件一样:它缺少一个规范的文档,并且拥有没有记录的特性。
我们的问题不在于 BASH bug,这基本上与 Ariane 5 的 bug 类似:使用来自早期的系统中,规范已经过期的模块。
在该特定情况中,使用过期的规范的原因似乎是由于没有任何规范被记录下来,并且对于相关的实现细节也没有文档。但从另一方面来说,这是自由软件,因此查阅源代码的难度就跟在脸上找鼻子一样容易。当复用一个没有规范和文档的模块时,检查源代码的实现应当是一个标准流程,不过显然 Apache 或 DHCP 的开发者没有这么做。
bug 还在那里,就像 Ariane 5 的 bug 不是 Ariane 4 的 bug 一样!