跳到主要内容

(译) 拯救我们计算的未来

· 阅读需 28 分钟
Thomas Hintz

本译文采用与原文相同的许可协议进行授权和传播。

本译文不会对原文做任何除格式调整和拼写错误以外的调整和修改,以确保原文内容的完整性,保证原文所要阐述的事实和思想不被曲解。

StagefrightHeartbleedGHOSTVENOM,这些有何共同点? 它们是最近出现的严重的安全漏洞,这些本来都是可以很容易地加以预防的。

随着更多的计算机连入互联网,更多的个人信息和金钱转到线上,安全也就变得愈发重要。 漏洞会变得越来越昂贵。我们必须从现在开始对未来进行投资,以免事情失去控制。 作为程序员,我们是想将时间花在与黑客玩猫捉老鼠上,还是去构建新的更好的软件呢? 我们必须开始使用我们已有的工具,并投身于新的工具,以使我们能够持续构建我们想要的以及世界所需要的东西, 不要浪费时间在玩打地鼠上面了。

在涉及到安全时,有许多攻击手段必须被解决掉。一些是社会性的,一些是技术性的。 我们可以做很多我们没有做过的事情来阻止技术性攻击手段。 也有很多我们能够使用的工具,用来限制由更加复杂的技术性和社会性攻击所造成的损害。

编程语言

一些非常常见的、通常非常严重的漏洞是很容易预防的。 可以通过使用一种语言来防止算术溢出,这种语言可以在每个操作后检查溢出并引发异常,或者采用无缝升级到 大数运算(译注:也称高精度运算)的方式。 通过使用进行边界检查的语言,可以防止缓冲区溢出和缓冲区超读等内存安全漏洞。 仅仅做这两件事就可以防止许多严重的备受瞩目的事件,包括 Stagefright(整数溢出)、Heartbleed(缓冲区超读)、 KCodes NetUSB (缓冲区溢出)、GHOST(缓冲区溢出)、VENOM(缓冲区溢出)和一些 Shellshock (与内存安全相关的)漏洞,以及其他无以计数的漏洞。

算术溢出和不安全内存访问以及其他技术漏洞,很容易通过使用不同的编程语言或语言运行时来完美地阻止掉, 但是总会有其他漏洞,主要像 Shellshock 这样的漏洞,它们不会那么容易地被阻止。 还是有很多工作可以做的,至少是可以用于限制其他漏洞造成的损害。 限制漏洞损害最简单的防御方式是沙盒,但是我们并没有充分或有效地使用它。

沙盒

沙盒有许多不同的类型和级别。 有一些非常生硬和笨重的形式,比如全虚拟化机;有一些中级的解决方案,像操作系统级的虚拟化, 比如用于 Linux 的 LXC(译注:即,Linux 容器技术) 和用于 FreeBSD 的 Jails (译注:即,FreeBSD 平台上的一种基于容器的虚拟化技术,其早于 LXC 但没有流行起来); 还有用户空间系统服务,如微内核、访问控制列表(ACL)、容量系统等等。有一些沙箱总比没有的好。 但是,更安全的形式,比如全虚拟化,通常更难以维护,并且会使用极其多的资源, 所以,经常得作出妥协以节约维护和配置成本。 事实上,由于成本太高,几乎不进行组件的完全隔离。 相反,我们拥有的最常见的“沙盒”是非常临时性的部分解决方案系统。 大多数系统仅通过隔离 root 和用户帐户、用户组、文件权限、系统范围的防火墙 以及其他生硬而又经常相互冲突的沙盒方法来进行保护。 最终,我们拥有了一大堆相互竞争、复杂且常常不兼容的沙盒方法。 这就阻碍了系统管理员和用户了解所有选项以及如何成功地实现它们。 而且,即使你确切地知道这些选项以及如何有效地使用它们,当你将它们一起使用时,它们也是如此复杂, 以至于几乎不可能完全正确地做到点上。

如果我们要继续数字化我们的生活,我们需要做得更好,而且我们能够做得到。 最好的解决方案会是系统范围的、简单的、非常细粒度的并且通常默认情况下是安全的。 在 3L 项目中,我们为沙箱采用头等环境来实现这些目标; 系统范围内的、简单的进程环境配置;限制每个进程的资源使用; 从内核到应用程序的类型、边界和算术溢出的检查;硬件驱动程序中的内存寻址边界检查;禁止对已分配内存进行直接内存访问; 带运行时检查的加密模块和应用程序代码签名; 以及使用已验证的编译器进行编译。 尽管像 3L 项目所采取的那样彻底的、全面的方法更为理想,但也还有其他选择。

译注:头等环境,即将环境作为操作系统的头等公民, 与之对等的概念是编程语言中的头等函数

对于沙箱,一种更常见的方法是微内核,尽管通常不太全面,但与传统应用程序更兼容。 还有一些更广为人知的传统风格的版本,比如 Minix, 也有一些正确性验证的版本,如 SEL4。 尽管它们在基本的操作系统功能中添加了更多的沙箱,但它们通常仍会遭遇 Unix 风格架构所固有的同样复杂的配置问题, 并且在内核和应用程序之间也不存在统一的安全机制。

操作系统级别的另一种方法是单体内核 (译注:即,在一大块代码中实际包含了所有操作系统功能,并作为一个单一进程运行,具有唯一地址空间)。 单体内核编译应用程序所需要的操作系统组件和系统库的最小集合,并作为单个映像运行。 这极大地限制了攻击面,沙箱组件也相当有效。 不幸的是,它们通常是用 C 和其他静态语言编写的,这使得仅使用运行应用程序所需的最小代码集进行编译变得很困难, 而即使在大部分库可能都不会被使用的情况下,也要在全部库中进行编译,这极大地增大了它本来应该有的攻击面。 一种更安全的方法是以更细粒度的方式编写库,并且使用不会受到 C 语言通常会引入的漏洞的影响的语言。 (一个有趣的实验可能是,基于头等环境机制,构建一个将 3L 项目编译为单体内核的系统,因为它会尽可能地做到细粒度。)

在应用程序级别可能会发生更常见的漏洞利用,例如注入和 XSS 漏洞利用, 但是每次漏洞利用,操作系统和系统级漏洞都会影响更大数量的用户, 并且因为系统更新的速度通常比许多 Web 应用程序慢得多,更新有漏洞的系统需要花非常长的时间才能使许多人免于暴露在外。 系统级安全措施还可以限制应用程序级漏洞的影响,而无需程序员进行操作或拥有任何特殊的系统级知识。

配置错误

如今另一个主要的问题是系统配置错误。 例如,设置一台安全的服务器需要配置许多组件,并且很容易出错。 任何做过琐碎设置的人都知道,试图弄清楚为什么某些程序没有启动或正在做一些意外的事情, 结果却发现设置用于试图使系统更安全的某些文件权限是错误的,是一种怎样的感觉。 最终,人们会感到沮丧,从而放弃安全方面的努力,或者只是为了使事情正常工作而打开了大坑。

拥有一个减少了必需配置的数量的沙盒系统,将极大地改善这种情况。 3L 项目通过一个系统范围的、统一的配置机制来解决这个问题,用于为每个应用程序配置沙盒。 可以为传统系统开发类似的方法。 启动应用程序时,系统可以查询用户指定的权限配置,系统使用该配置为新进程授予权限。 尽管没有理想的那么细粒度,但它在短期内的影响要大得多,因为它可以应用到更多的软件上。

论性能

是的,是的,是的,是的,我知道,安全性通常伴随着性能损失。 我知道你们中的很多人会坚持认为其他所有应用程序都必须用 C 编写,因为每个 CPU 周期和寄存器都必须被最优化地利用以最大化性能。 (等等,那你为什么不用汇编写呢?) 在一般情况下,考虑什么是有风险的,会是一个可怕的争论,但显然在某些情况和领域中,这又是普遍正确的。 然而,并不是一切都失去了。我们可以有我们的蛋糕,也可以吃掉一半。

许多具有更多安全特性的语言的编译器都提供了编译器标志或语言指令,允许在性能更重要的情况下禁用安全特性。 也许你希望你的系统的大部分是更加安全的,但是,你又不得不最大化最新游戏的帧率, 你可以在禁用安全特性的情况下编译它(或者使用预编译的二进制文件)。还可以使用作用域声明, 类似于 Common Lisp 中的优化形式, 对期望的安全性和性能进行非常细粒度的控制。(这就是 3L 项目所采用的方法。) 它仍然不会像你为 Intel 编译器手工调优的代码那样快,但可能真的没有必要这么快。

代价是什么?

当然,安全是有成本的,但是当安全性被破坏时,安全性差也会带来不可预见的成本。 在应用程序速度(或人的工作时间)和安全性之间总是存在权衡。 仔细选择你的优先级,并考虑现在和将来不为安全买单的隐性成本。 也要做一个体面的人,这不仅仅是底线。你的用户也是人,就像你一样。

现在我们需要做什么?

虽然不会在明天就出现一个好的长期的解决方案,但我们现在可以开始做一些事情了。 原则上,无论是在应用程序中还是在数据中,始终要以某种程度的隔离作为目标。 至少要尝试将东西放在一部分沙盒中。

首先,看在上帝的份上,不要再用 C 写代码了。 你不是上帝,你会犯错误的。 当开始你的下一个项目时,问问你自己,它真的需要用 C 写吗? 它真的,真的需要用 C 写吗? 你能不能至少把它限制在用 5%、2%、1% 的 C ? (如果作为程序员,我们真的像上帝一样,可以安全地用 C 编写代码,那现在还不会发生吗?)

如果你正在进行 Web 开发,那么至少要花一点时间了解常见的漏洞以及如何防止或减弱它们。 如果你正在运行服务器,请学习如何通过虚拟机、容器甚至用户组进行某种程度的沙盒操作。 如果你正在编写 Web 应用程序,请了解常见的漏洞,并始终使用防止用户输入和代码 交叉污染的库, 有效地将用户数据放到它自己的沙盒中。 永远不要手动调用转义程序。 使用一个库来哈希和管理密码。 即使花一两天的时间做一些基础研究,也将对每个人都有很大帮助。

不要调用 Shell 来运行另一个进程。 使用类似于 exec 的方法直接运行程序, 并将其参数作为单独的参数传递给用于启动新进程的函数。

使用库来处理诸如哈希和解析之类的安全性工作。

虽然这些并不全面,并且我们还需要做得更好,但从长期来看,至少这是我们现在可以做的。

更好的做法是,投入时间或金钱,为能提供更好的长期解决方案的项目做出贡献。 最终,我们所有人,无论是程序员还是非程序员,都会好起来的, 只要我们现在就开始解决这个问题,而不是把问题踢到马路上。

译文-扩展阅读

版权声明