使您的软件运行起来: 了解有关缓冲区溢出方面的基础知识

来源:互联网 发布:宜家记忆海绵枕头知乎 编辑:程序博客网 时间:2024/05/18 08:58
    

使您的软件运行起来: 了解有关缓冲区溢出方面的基础知识

重新认识影响软件安全性的一个最大威胁

     developerWorks

级别: 初级

Gary McGrawReliable Software Technologies
John ViegaReliable Software Technologies

2000 年 3 月 01 日

上一专栏中,我们从较高层次讨论了软件安全性的分析,介绍了软件安全性的方法,并且说明了风险分析的关键特征。任何风险分析都必需知道最常见的风险和可能引起这些风险的原因。这一知识部分涉及到对编码器可能会产生错误和总是引起安全性问题等方面的熟悉程度。因此,在本专栏中,我们将介绍影响软件安全性的一个最大威胁:可怕的缓冲区溢出。

本周专栏包括了最初由 Tom O'Connor(Surety.Com 的软件工程师)编写的一些材料。

几十年来,缓冲区溢出一直引起许多严重的安全性问题。其中最著名的例子是:1988 年,因特网蠕虫程序在 fingerd 中利用缓冲区溢出感染了因特网中的数万台机器,使得各地的服务器管理员都对此极为头痛;请参阅本专栏稍后的 参考资料。但是,缓冲区溢出问题并非已成古老的历史。据统计,仅去年缓冲区溢出就占使 CERT/CC 提出建议的所有重大安全性错误的百分之五十以上。(CERT/Coordination Center 是位于匹兹堡的“软件工程协会”的一个部门;请参阅 参考资料。)并且数据显示这一问题正在扩大,而不是在缩减;请参阅 “缓冲区溢出: 卷土重来”

很明显,至此您不会认为缓冲区溢出错误将是过时的。所以,为什么现在仍产生缓冲区溢出这种致命错误呢?这是因为引起灾难的方法总是惊人地简单。一部分采用错误的语言设计(通常是 C 和 C++),再结合程序员拙劣的编写手段,就会发生这种大问题。尽管象 Java 这样不具备某些令人难以置信的异常编程方式的、现代“安全”的语言避免了缓冲区溢出,但这一问题缓冲区溢出也会发生在非 C 和 C++ 的语言。在任何情况下,合理的原因通常都证明诸如 C 和 C++ 等语言的使用是正确的,因此了解它们的缺陷就非常重要了。

引起缓冲区溢出问题的根本原因是 C(与其后代 C++)本质就是非安全的。没有边界来检查数组和指针的引用,也就是开发人员必须检查边界(而这一行为往往会被忽视),否则会冒遇到问题的风险。标准 C 库中还存在许多非安全字符串操作,包括:

  • strcpy()
  • strcat()
  • sprintf()
  • gets()


出于这些原因,对编写苛求安全性代码的 C 和 C++ 程序员必须自我进行有关缓冲区溢出问题的教育。最佳的防范就是关于这些问题的良好的教育。这就是下面四个专栏将讨论缓冲区溢出的原因。本专栏概述了缓冲区溢出问题。下一专栏将涉及防范的编程技巧(用 C),并说明为什么某些系统调用容易出问题以及应如何解决。本系列的最后两个专栏,将检查引擎的工作并说明缓冲区溢出的攻击在特定体系结构上是如何进行破坏的。

什么是缓冲区溢出?

缓冲区溢出开始于每个程序都需要的一些情况:放置位元的空间。多数计算机程序都在内存中创建多个地址用于信息存储。C 编程语言允许程序员在运行时在内存的两个不同部分(堆栈和堆)中创建存储器。通常,分配到堆的数据是那些 malloc() 或新建时获得的数据。而分配到堆栈的数据一般包括非静态的局部变量和所有按值传递的参数。大部分其它信息存储在全局静态存储器中。(从现在开始,我们将在两个专栏中论述它们的实质细节。)在分配同一数据类型的相邻块时,这块内存区域称为缓冲区。

在写入缓冲区时,C 程序员必须注意存储在缓冲区中的数据不能超过它所能容纳的量。缓冲区只能容纳一定数量的位,就象一个杯子只能盛一定量的水。如果放到杯子中的水太多,多余的水就会溢出到别的地方。相似地,如果试图放入缓冲区的数据比它能装入的要多,额外的数据就会溢出到别处,并且您不希望它到其它地方!

当程序写入超过缓冲区的边界时,这就是所谓的“缓冲区溢出”。发生缓冲区溢出时,会覆盖下一个相邻的内存块。由于 C 语言本质上的不安全性,所以它允许程序随意(或者更准确地说是完全出于偶然)溢出缓冲区。没有运行时检查来这一防止写入超过缓冲区末尾,所以程序员必须在其自己的代码中执行这一检查,否则继续下去会遇到问题。

读取或写入超过缓冲区的末尾时,会导致许多不同(并且通常是不可预料的)行为:1) 程序的执行很奇怪,2) 程序完全失败,或者 3) 程序可以继续,而且在执行中没有任何明显不同。缓冲区溢出的副作用取决于:

  • 写入的数据中有多少超过缓冲区边界
  • 当缓冲区已满并且溢出时,覆盖了哪些数据(如果有的话)
  • 程序是否试图读取溢出期间被覆盖的数据
  • 哪些数据最终替换被覆盖的内存

存在缓冲区溢出的程序的不确定行为使得对它们的调试异常棘手。最坏的情况是:程序可能正发生缓冲区溢出,但根本没有任何副作用的迹象。因此,缓冲区溢出问题常常在标准测试期间是发现不了的。认识缓冲区溢出的重要一点是:在发生溢出时,会潜在地修改碰巧分配在缓冲区附近的任何数据。





为什么缓冲区溢出是安全性问题?

您可能会想:“有什么大不了,一点点水的溢出根本不会对任何人造成伤害。”将我们的类推再引深一点,试想水溢出在有许多暴露在外的电线的工作台上。根据水滴溅的位置,火花会四处飞散。同样地,当缓冲区溢出时,额外的数据会摧残程序将来可能要访问的其它有用的数据。有时,这些其它数据的更改会导致安全性问题。

最简单的情况就是考虑直接在缓冲区后面的内存中分配一个布尔标志。这个标志决定运行程序的用户是否可以访问专用文件。如果有不怀好意的用户覆盖缓冲区,则会更改标志的值,从而指出攻击者是非法访问专用文件。

缓冲区溢出导致安全性问题的另一个方法是通过摧毁堆栈。摧毁堆栈的目的是导致一个特定的编程故障:不仔细使用分配在程序运行时堆栈上的数据缓冲区,即局部变量和函数自变量。有效的摧毁堆栈所造成的后果比上一示例中提到的改变布尔访问控制标志的后果更为严重。有创造力的攻击者会通过摧毁堆栈利用缓冲区溢出的弱点,然后运行任何代码。这种想法是相当直接的:在某处插入一些攻击代码(例如,调用 shell 的代码)并以将控制传递给攻击代码的方式来覆盖堆栈。(我们将在缓冲区溢出的第三和第四专栏中详细讨论堆栈的摧毁。)

一般地,攻击者利用缓冲区溢出得到机器上的交互式会话 (shell)。如果被利用的程序以较高的优先权在运行(如 root 用户或管理员),则攻击者就会在交互式会话中得到该优先权。最惊人的缓冲区溢出是堆栈的摧毁,它会在超级用户或 root、shell 中造成后果。许多可以利用脚本都能在网络(请参阅 参考资料)上找到,它们对特定体系结构上的堆栈进行摧毁。





缓冲区溢出: 卷土重来

根据 David Wagner、Jeffrey Foster、Eric Brewer 和 Alexander Aiken 在今年的“网络与分布式系统安全性”会议 (NDSS2000) 上发表的一篇论文中的分析,当今被广泛利用的薄弱环节中多达百分之五十都是缓冲区溢出。而且,该分析指出这一比例正随着时间而不断上升。由于缓冲区溢出问题近年来在安全性领域中已受到瞩目,这一数据是相当令人灰心的。由于某些原因,开发人员尚未积极推动消除缓冲区溢出这一软件安全性的主要陷阱。


过去十一年中 CERT/CC 提出建议的弱点数目

以上图表中,显示了可以直接归为缓冲区溢出的弱点数。正如数据所显示,这一问题并没有任何好转。事实上,缓冲区溢出正越来越普遍。





堆溢出和堆栈溢出的对比

堆溢出的利用通常比堆栈溢出更困难(虽然存在一些成功的堆溢出攻击)。由于这个原因,有些程序员从来不静态分配缓冲区。而是用 malloc() 或新建所有东西,并相信这样会防止出现溢出问题。他们通常是对的,因为没有多少人具有利用堆溢出所需的专门技术。但是,动态缓冲区分配本质上并不比其它方法更安全。不要依靠动态分配所有一切,而遗忘缓冲区溢出问题。动态分配并不能根本解决所有问题。

让我们更深入地了解某些缓冲区溢出会造成严重安全性隐患的原因。许多有趣的 UNIX 应用程序都需要特殊权限来完成它们的作业。它们可能需要写到具有某些特权的位置(如邮件队列目录)或打开一个有特权要求的网络套接字。这样的程序通常是 suid(设置 uid)root,也就是即使常规的老用户运行该程序时系统也会按请求将特殊权限扩展给应用程序。从安全性来看,无论何时授予特权(即使是临时的),都有发生特权扩大的潜在可能。因此,可以说在特权扩大时,成功的缓冲区溢出攻击发挥到极至。许多用得很好的 UNIX 应用程序,包括 lpr、xterm 和 eject,都被滥用, 而在代码的 suid 区域中利用缓冲区溢出放弃了 root 权限。

常用的破坏技术是在 suid root 程序中查找缓冲区溢出,然后利用缓冲区溢出偷取交互式 shell。如果程序以 root 权限运行时运行了利用脚本,则攻击者将得到 root shell。利用 root shell,攻击者可以执行任何操作,包括查看专用数据、删除文件、设置监视站、安装后门(用 root 工具箱)、编辑日志以隐藏痕迹、伪装成其他人、意外地破坏材料等等。非常可怕。

同时,许多人认为,如果他们的程序不运行 suid root,他们就不必担心他们代码中的安全性问题了,因为不会使程序得到更高的访问级别。这种想法部分正确,但仍是一个危险的主张。有一点,您从不会知道谁将执行您的程序并在二进制中设置 suid 位。当人们无法得到某些东西以正确工作时,他们会变得不顾一切。我们已经看到这类情况会导致程序的整个目录不必要地设置 suide root。再说一次,这是非常可怕的。

也有一些软件的用户根本没有任何特权。这意味着任何成功的缓冲区溢出攻击都将使他们比原来拥有更多的特权。通常,这样的攻击涉及到网络。例如,外部用户可以利用的网络服务器程序中的缓冲区溢出,可能使攻击者登录到机器。产生的会话具有运行危及网络服务的进程的特权。这类攻击随时都会发生。通常,这些服务以 root 权限运行(并且一般是利用有特权的低端口)。即使这样的服务不是以 root 权限运行时, 一旦攻击者得到机器上的一个交互式 shell,通常机器被“占有”只是一个时间问题――“占有”即攻击者获得机器的完全控制,如 UNIX 操作系统上的 root 访问权或 Windows NT 操作系统上的管理员访问权。 这样的控制一般是通过利用交互式 shell 运行另一个利用脚本而扩大特权所获得的。





现实世界问题的范围

非常不幸,缓冲区溢出在 C 程序中是太常见了。如上面所述,即使是用得很好、经过仔细检查的程序,仍容易发生缓冲区溢出。

例如,Sendmail,一个电子邮件服务程序并且是网络上使用范围最广的程序之一,存在安全性问题上是众所周知的。许多人使用这个软件,而且许多人都仔细检查过它,但是仍不断发现有严重的安全性问题(包括缓冲区溢出)。在 1996 年 9 月,在 Sendmail 中发现并修正了几个新的可利用的缓冲区溢出之后,进行了广泛的手工安全性审查。但是,不到四个月,又发现了另一个手工审查时遗漏的可利用的缓冲区溢出。

再近一点,Berkeley 加利福尼亚大学的一个研究生,David Wagner 今年在 Sendmail 中找到了一些新的缓冲区溢出(请参阅 参考资料)。但是,他发现的那些溢出好象并不是可利用的。换句话说,好象没有任何缓冲区会被精明狡猾的攻击者的输入(通常是作为一种需求的信息)而随意溢出。虽然如此,至少 Wagner 发现的一个溢出是 1996 年以来未被手工审查到的。

并且,仅仅因为训练有素的安全性专家表示这个错误“好象是不可利用”的,并不表示它就是不可利用的。 1998 年,Reliable Software Technologies 的研究者(并非作者)使用名为 FIST 的故障注入工具在华盛顿大学 ftp 守护程序 (wu-ftpd) 版本 2.4 中发现三个缓冲区溢出情况。(在我们讨论动态分析时,稍后将再次访问本系列的 FIST。) 这本身就是非常有趣的,因为 wu-ftpd 近来已经在 1993 年和 1995 年之间提出的四个关于 wu-ftpd 的 CERT 建议后进行了大量地检查。 Reliable Software Technologies 的专家检测了潜在的易受攻击的代码,并且未能掌握利用任何情况的方法。他们声明能够成功操纵用户输入到达易受攻击的缓冲区从而造成安全性破坏的可能性已非常小了。接着,一年以后,一个新的关于 wu-ftpd 的 CERT 建议出现了。他们检测到的这三个缓冲区溢出中的一个证明这完全是一个弱点!

这些轶事说明手工分析是多么困难啊!从根本看来,代码往往是复杂的。分析大型的程序(如 Sendmail,大约有 50,000 行代码)不是一个简单的任务。问题常会从粗心的开发人员身边溜过,但是问题也会从专家身边滑过。





Windows 和 UNIX 对比:晦涩的安全性好吗?

目前,我们举的所有利用缓冲区溢出的示例都是针对 UNIX 系统的。但是,我们不想遗漏 Microsoft Windows!大量的缓冲区溢出的可能性也存在于 Windows 机器上。近来,一家名为 eEye 的公司发现 Microsoft 的“因特网信息服务系统 (IIS)”有一个可利用的缓冲区溢出。这一漏洞使大约百分之九十的运行 Windows NT 的 Web 服务器机器完全开放,供任何脚本人员“占有”。(脚本人员是将来的黑客,他们的知识足够对易攻击的机器运行预编码的脚本,但还没有足够的知识创建这样的脚本。)请参阅第一专栏, “使您的软件运行起来”,获取脚本人员的详细信息。)

有些人认为在 Windows 程序中查找缓冲区溢出比在 UNIX 程序中难。的确是这样,因为 Windows 程序往往是不带源代码交付的,而 UNIX 程序是带源代码交付的。当有程序的源代码时,就非常容易找到潜在的缓冲区溢出(或者更进一步,大多数的安全性瑕疵)。当可以获得程序的源代码时,攻击者会查找所有可疑的函数调用,然后尝试确定哪些是弱点。查看该程序,攻击者更容易得出如何利用实际输入导致缓冲区溢出。在没有源代码时,做这些事就需要有更多的技巧(可能还需要有一些运气)。

当没有发布源代码而免除安全性问题通常称为“晦涩的安全性”。但是,依靠这种安全性不是个好主意,因为如果代码首先设计有问题的话,技术高超的攻击者仍能破坏它。更好的解决方案是编写可靠的代码,让其他人知道您所做的,这样他们可以帮助您找出漏洞从而保证它的安全。

许多 Windows 开发人员无论是否知道这一点,仍依靠晦涩的安全性模型。通常,他们不知道这一点,主要是因为他们在开发他们的应用程序时从未真正考虑到安全性。在这种情况下,他们盲目地依靠有瑕疵的方法。

安全性专家通常认为 Windows 代码比 UNIX 代码有更多的可利用缓冲区溢出问题。这一推理可以归为“许多人都来关注”现象,这个现象是指开放源码的软件往往能更好地检查,因此更安全。当然,有些警告。首先,让人们查看您的源代码并不能保证会发现错误。其次,对于 Windows,也有许多开放源码的软件;而对于 UNIX 机器,至少也有许多封闭源码的软件。出于这些原因,这一现象并非与体系结构有关。





结束语

在本专栏中,介绍了缓冲区溢出,它始终可能是最糟的软件安全性问题。这里没有提供任何有关如何在您自己的程序中避免这些问题的建议。而且,在此仅提到了堆栈溢出是如何产生的实质细节。但是,幸运的是:将在下一专栏中论述这些问题。





参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文.

  • M. Eichin 和 J. Rochlis, "With microscope and tweezers: an analysis of the Internet virus of Nov. 1988," 1989 IEEE Symposium on Security and Privacy, Oakland, CA

  • CERT/Coordination Center 是位于匹兹堡的“软件工程协会”的一个部门

  • 一些对特定体系结构上的堆栈实施摧毁的 利用脚本

  • D. Wagner、J. Foster、E. Brewer 和 A. Aiken,"A First Step Towards Automated Detection of Buffer Overrun Vulnerabilities," 2000 ISOC Network and Distributed Systems Security Symposium (NDSS2000), San Diego, CA

  • developerWorks第一个“使您的软件运行起来”专栏,其中 Gary 和 John 介绍了他们的安全性理论并说明了他们为何关注面向开发人员的软件安全性问题

  • developerWorks的“ 使您的软件运行起来:确保软件是安全的”,其中 Gary 和 John 展示了他们为安全性设计的五步过程





作者简介

Gary McGrawReliable Software Technologies 负责企业技术的副总裁,该公司位于美国弗吉尼亚州的杜勒斯 (Dulles)。他从事咨询服务和研究工作,帮助决定技术研究和开发方向。McGraw 在 Reliable Software Technologies 从一个研究科学家作起,从事软件工程和计算机安全性方面的研究。他拥有印第安那大学的认知科学和计算机科学双博士学位,弗吉尼亚大学的哲学学士学位。他为技术刊物撰写了 40 余篇经过同行检测的文章,担任过主要的电子贸易供应商,包括 Visa 和 Federal Reserve 的顾问职务,并在空军研究实验室、DARPA、国家科学基金会,以及 NIST 的高级技术项目赞助下担任其首席调研员。

McGraw 是移动代码安全性方面著名的权威人士,并且与普林斯顿的教授 Ed Felten 合作撰写了 "Java Security: Hostile Applets, Holes, & Antidotes" (Wiley, 1996) 和 "Securing Java: Getting down to business with mobile code" (Wiley, 1999)。 McGraw 和 RST 创始人之一、首席科学家 Dr. Jeffrey Voas 一起编写了“Software Fault Injection: Inoculating Programs Against Errors”(Wiley, 1998)。McGraw 定期为一些受欢迎的商业出版物撰稿,而且其文章经常在全国出版的文章中所引用。


 

John Viega 是一个高级副研究员,Software Security Group 的创始人之一,并担任 Reliable Software Technologies 的高级顾问。他是 DARPA 赞助的开发标准编程语言安全性扩展的首席调研员。John 已撰写了 30 余篇涉及软件安全性和测试领域的技术性文章。他负责在主要网络和电子贸易产品中查找一些众所周知的安全性薄弱环节,包括最近在 Netscape 安全性中的缺陷。 他还是开放源码软件社区的重要成员,编写过 Mailman、 GNU Mailing List Manager 和最近发布的 ITS4(一种在 C 和 C++ 代码中查找安全性薄弱环节的工具)。Viega 拥有弗吉尼亚大学的计算机硕士学位。