在当今联网的世界中,软件开发人员必须知道如何编写安全的程序,然而人们还未广泛认识到这一信息,也未向人们广泛讲授过这一信息。本文介绍了如何编写安全的应用程序的基本思想,并讨论了如何针对特定的应用程序确定安全性需求。
欢迎来到不太友善的世界
案例 #1
味道闻起来很糟糕。两个多月以来,数十万加仑的污水流到了澳大利亚的公园、河流以及一家旅馆的地面上,而无人知晓其原因。水生动植物不断地死亡,而且有一条小河中的水已经变黑了。2000 年 4 月 23 日,当警方逮捕了一名男子时才解开了这个谜团,这名男子一直在使用计算机和无线电设备对管理污水和饮用水的机器进行完全控制。他的动机是什么呢?对其审讯的口供表明他正试图获得一份获利丰厚的咨询合同以解决他所造成的问题。事实原本还要糟糕得多。
案例 #2
有一个小偷(只知道他叫“Maxus”)从在线音乐公司 CD Universe 偷了 35 万个信用卡号,然后勒索 10 万美元赎金。当 CD Universe 拒绝支付赎金时,Maxus 就公开张贴了这些信用卡号 - 这损害了 CD Universe 的顾客并使这些顾客有合理的理由转而光顾其它商店。
案例 #3
CIA 最近了解到奥萨马·本·拉登的基地恐怖分子组织对网络恐怖活动有着“非常浓厚的兴趣”,这是人们先前所不相信的。链接到基地组织的计算机都可以获得各种计算机“破解”工具,旨在引起灾难性的破坏。
它们正在攻击您的程序 - 您准备好了吗?
计算机攻击已成为一个非常严重的问题。1997 年,CERT/CC 报告发生了 2134 起计算机安全性事件,并报告了 311 个截然不同的安全性漏洞;到 2002 年,所报告的计算机安全性事件已上升至 82094 起,而安全性漏洞则上升到 4129 个。计算机安全性协会(Computer Security Institute,CSI)和旧金山联邦调查局(Federal Bureau of Investigation,FBI)计算机入侵小组于 2003 年调查了 503 家大型公司和政府机构,发现其中有 92% 的被调查者都报告受到过攻击。这些被调查者中,有 78% 确认他们的因特网连接经常受到攻击,并有 36% 确认他们的内部系统经常受到攻击。有 75% 的被调查者承认他们受到了经济损失,尽管只有 47% 的被调查者可以确定他们损失的具体数目;但这些可以确定具体数目的组织的损失超过了 2 亿美元。
攻击持续升温的原因有许多。计算机在不断地联网,这使攻击者能比较容易地攻击世界范围内任何联网的计算机,而不会有什么风险。计算机已经非常普及;它们现在控制着比较多的具有价值的东西(这使它们值得进行攻击)。过去,顾客十分愿意购买不安全的软件,所以根本没人愿意出钱开发安全的软件。
电子世界现在是一个危险程度更加高的地方。今天,我们要求几乎所有的应用程序都是安全的应用程序。例如,实际上我们要求每个 Web 应用程序都是安全的应用程序,因为不可信的用户可能向它们发送数据。甚至那些显示或编辑本地文件的应用程序(如字处理器)都必须受到保护,因为有时用户将显示或编辑以电子邮件方式发送给他们的数据。
如果您在开发软件,那么您就是身处战场,需要知道如何保护您自己。遗憾的是,大多数软件开发人员从未知晓如何编写安全的应用程序。
本专栏将帮助您了解如何编写安全的应用程序。学校里很少会教这类信息,其它地方也不太会讲授这一主题。如果您学习了本专栏,那么您将能够保护您的程序,避免当前所用的最常见的攻击。尽管我们主要讨论 Linux 操作系统(也称为 GNU/Linux),但是几乎所有的内容都适用于任何类 UNIX 系统,而且其中的许多内容也适用于其它操作系统(象 Microsoft Windows)。
对于这第一篇文章,我将先介绍一些基础知识:安全性术语、改变您的理念、自由/开放源码软件(Free-Libre/open source software,FLOSS)的影响以及确定安全性需求。
#p#术语:这些词表示什么意思?
每一领域都有其自己的术语,而计算机安全性领域中的术语则有些杂乱无章,其中充斥着首字母缩写词和易混淆的词。以下几个定义应该有所帮助:
•攻击者(attacker)(也称为 非法闯入者)是这样的人:他试图使程序或计算机做某些事情,而这些事情是明确禁止做的,如为了获得或更改私有数据而闯入不属于他们的计算机。
•黑客(hacker)是计算机专家或计算机狂热者。并非所有攻击者都是黑客 - 有些攻击者根本不了解计算机。同样,并非所有黑客都是攻击者 - 许多黑客编写保护您的程序!媒体公司只关注那些攻击计算机系统的黑客,而往往忽视那些计算机的保卫者,所以有些人就使用术语“黑客”表示只进行攻击的黑客。但是,如果您认为所有的黑客都是攻击者,那么您在理解许多安全性文章时会碰到很多麻烦,所以我将使用这里指出的定义。
•缺陷(flaw)是程序中的错误,或是程序的安装方法中的错误。并不是所有的缺陷都与安全性有关。
•安全性漏洞(vulnerability)就是一个缺陷,它使程序有可能无法满足其安全性需求。
•漏洞利用(exloit)是一个揭示或利用安全性漏洞的程序。
改变您的理念
在学习如何编写安全的软件时遇到的最大挑战是改变您对软件开发的观点。以下几点应该会有所帮助:
•偏执狂是一种优点。在自己进行调查研究之前不要相信任何事情。不要想当然地以为您的输入遵守您所依赖的规则;去检验它。不要忽略来自库的错误报告;通常,在遇到意想不到的错误时,您需要终止正在进行的处理。不要以为您的程序没有错误;限制您的程序能够完成的事情,这样错误成为安全性缺陷的可能性就会比较少。
•一般的测试通常无法发现安全性缺陷。大多数测试方法都假定用户设法使用程序来帮助他们完成一些工作。因此,测试假定用户将以某种“随机”或“有用”的方式工作,检查程序在“一般”情况下或者在某些最大值下如何工作。与此相反,安全性缺陷通常只出现在使用极其古怪的值的情况时,传统的测试完全不会检查这样的值。有些开发人员会编写非常糟糕的代码,然后希望通过测试它来进行纠正。这种方法根本不会产生安全的代码,因为您无法创建足够多的测试来涵盖攻击者能做到的所有稀奇古怪的事情。
•小玩意(象防火墙)和技术(象加密)是不够的。
•从过去的失败确定和了解漏洞。事实证明几乎所有软件的安全性漏洞都是由相对较小的一组常见错误引起的。如果您了解了那些是什么错误 - 以及如何避免这样的错误 - 您的软件就会安全得多。事实上,本专栏将集中讨论如何避免以前出现的常见错误,这样您就不会犯同样的错误。
#p#FLOSS 会使我们安全吗?
自由/开放源码软件程序是带有某种许可证的程序,这样的许可证允许用户以任何目的自由地运行程序,自由地研究和修改程序以及自由地重新分发原始程序副本或修改过的程序副本(而不必向以前的开发人员支付版税)。FLOSS 的同义词包括开放源码软件(OSS),大写时是“自由软件(Free Software)”(FS)以及 OSS/FS。“自由软件”和“开放源码软件”可在本文中互换使用,但是推荐使用“FLOSS”,因为它包含了这两个术语。典型的 FLOSS 程序都是由开发人员社区开发的,他们一起工作并评审彼此的工作。Linux 内核是 FLOSS,Apache Web 服务器和许多其它程序也是 FLOSS;FLOSS 正逐渐在许多市场特殊领域流行起来。
人们可以评审 FLOSS 程序的源代码,这样会如何影响安全性存在着激烈辩论。由于受到大众所有可能的详细审查,所以 FLOSS 会更安全吗?或者,FLOSS 是否会更不安全,因为攻击者获得了更多的信息 - 这会使得进行对程序的攻击更容易吗?
这些问题开始有了答案,与象“FLOSS 总是比较安全的”这样的简单声明相比,这些答案更为细致且更复杂。确实有事实证明 FLOSS 能比具有专利权的软件安全。例如,据报告 FLOSS OpenBSD 操作系统的安全性漏洞比 Microsoft Windows 少得多。但是有一个合理的“反诉”:由于 Windows 用户比较多,所以对 Windows 的攻击就可能比较多,这意味着更有可能发现 Windows 的安全性漏洞。这就是比较的全部内容吗?很值得怀疑,不过这也显示出作同等对比是多么困难。
一个更佳的示例是 Apache Web 服务器:它比 Microsoft 的具有专利权的 IIS Web 服务器流行得多,但是 Apache 的严重安全性漏洞却比 IIS 少。
还有一点很清楚,攻击者实际上并不需要源代码。只要研究可获得的所有 Microsoft Windows 漏洞利用就可以了!更重要的是,如果攻击者需要源代码,那么他们会使用反编译器,来重新创建源代码,这样重新创建的源代码对攻击目的而言足够了。
但是答案也并非就是“FLOSS 总是比较安全的”。毕竟,您可以将具有专利权的程序的许可证更改成 FLOSS,而不更改其代码,而它不会突然就变得更安全。相反,有几个因素看来是使 FLOSS 程序拥有良好的安全性所必不可少的:
•必须多人真正地评审代码。有多种因素会减少进行评审的可能性,如用于特殊领域或很少用到的产品(可能的评审者很少)、开发人员很少、使用罕见的计算机语言或实际上不是 FLOSS(如“共享源码”许可证)。如果每次代码更改都由许多开发人员进行检查,这通常有助于安全性。
•至少某些开发和评审代码的人必须知道如何编写安全的程序。一个人可以帮助培训其他人,但是您必须有一个起点。
•一旦发现安全性漏洞,需要快速地开展修补工作并将修补成果进行分发。
简而言之,程序是否安全最重要的因素是 - 不管它是 FLOSS 还是具有专利权的 - 其开发人员是否知道如何编写安全的程序。
如果您需要安全的程序,那么使用 FLOSS 程序就相当合理 - 但您需要以某种方式评估它以确定它对于您的目的是否足够安全。
确定您的安全性需求
在您可以确定程序是否安全之前,需要先确切地确定其安全性需求是什么。事实上,有关安全性的实际问题之一是安全性需求会根据不同的程序和不同的环境而迥然不同。文档查看器或编辑器(如字处理器)可能需要确保查看数据不会使程序运行任意命令。购物车需要确保顾客不能自己定价,而且顾客不能查看有关其他顾客的信息等等。
事实上您可以使用一个国际标准来正式确定安全性需求并确定它们是否满足要求。这个国际标准的正式标识符是 ISO/IEC 15408:1999,但人们都称之为“通用标准(Common Criteria,CC)”。
有些合同特别要求您使用 CC 的所有细节,这样的话,您需要知道的就要比本文所涉及的多得多。但对于许多情况,帮助您确定安全性需求所需的全部就是一个非正式的简化方法。所以,我将根据 CC,来描述一个用于确定安全性需求的经过简化的方法:
•确定您的安全性环境。
•确定您的安全性目标。
•确定您的安全性需求。
即使您在非正式地这样做,也请写下您的结果 - 它们以后也可以向您和您的用户提供帮助。
安全性环境
程序实际上不会在真空中工作 - 在某个环境中是安全的程序可能在另一个环境中就不安全了。因此,您必须确定您的程序要在什么样的环境(或多种环境)下工作。特别地,请考虑:
•威胁。您的威胁有哪些?
◦谁会进行攻击?可能的攻击者也许包括无知的用户、计算机业务爱好者、罪犯、对公司不满的雇员、其他内部人员、没有职业道德的竞争对手、恐怖分子的组织或甚至是外国政府。尽管攻击者的危险程度会有不同,但是每个人都可能成为攻击者的目标。设法确定您所信任的人;明确您不该信任其他任何人。确定谁是不可信的人是一个好主意,因为这会帮助您确定真正的问题是什么。商业组织不能忽略恐怖分子或外国政府实施的电子攻击 - 国家军队根本不会花费他们的资源来设法通过电子方式保护您。在电子世界中,我们所有的人都要靠我们自己,而且我们每个人都必须保护我们自己。
◦他们如何进行攻击?您是否担心会有特殊类型的攻击,如攻击者假扮合法用户?有没有那些在类似的程序中已经存在的安全性漏洞呢?
◦您正设法保护的是什么资产?所有信息都是不相同的 - 您正设法保护的是哪几种不同的信息,以及如何保护(防止被读取?防止被更改?)?您担心被窃取、破坏或被狡猾地修改吗?根据您正设法保护的资产以及攻击者可能会如何处理这些资产来考虑安全性。
•假设。您需要做怎样的假设?例如,您的系统是否受到保护而免受物理威胁?您的支持环境(平台和网络)是什么 - 它们安全吗?
•组织安全性策略。有没有希望程序遵守或实现的法规或法律呢?例如,美国或欧洲的医疗系统必须(在法律上)使某些医疗数据保密。
安全性目标
一旦您知道了身处什么环境;就可以确定您的安全性目标,它们基本上是高级需求。典型的安全性目标涉及多个方面,比如:
•机密性:系统将防止在未授权情况下泄露信息(“不能读”)。
•完整性:系统将防止在未授权情况下更改信息(“不能更改”)。
•可用性:系统将持续工作,即使在受攻击的时候也是如此(“工作的持续性”)。能够在所有可能的攻击下继续工作的系统是不存在的,但是系统可以抵御许多攻击或在受攻击后迅速恢复至可用状态。
•认证:系统将确保用户是可信的。
•审计:系统将记录重要事件,以允许稍后跟踪所发生的事情(例如,捕获攻击者或对攻击者提起诉讼)。
通常,在您确定了安全性目标时,您的程序单靠自己还不能完成某些事情。例如,可能您正在运行的操作系统需要进行加强,或者可能您要依靠一些外部认证系统。这样的话,您就需要确定这些环境需求并确保您告诉了用户如何使这些需求成为现实。然后您可以将精力集中在您程序的安全性需求上。
功能性需求和保证需求
一旦知道了程序的安全性目标,就可以通过更详细地填充其内容来确定安全性需求。CC 确定了两类主要的安全性需求:保证需求和功能性需求。事实上,CC 的大多数内容是一个可能的保证需求和功能性需求的列表,您可以针对给定的程序从中进行选取。
保证需求 是用来确保程序完成它该做的事情 - 而不做别的事 - 的过程。这可能包括评审程序文档以查看其前后的一致性、测试安全性机制以确保它们按计划工作,或者创建并运行渗透测试(为设法攻破程序而专门设计的测试)。CC 预先创建了几组保证需求,但是如果其它保证措施有助于您满足要求,那么也可以自由使用这些措施。例如,您可以使用工具在您的源代码中搜索可能的安全性问题(这些工具称为“源代码扫描工具”) - 即使它不是 CC 中的特定保证需求。
功能性需求 是程序为实现安全性目标所执行的功能。也许程序会检查密码以认证用户,或者对数据加密以将它隐藏等等。通常,只有“授权”用户可以完成某些操作 - 所以请考虑程序应该如何确定谁获得了授权。
下一步是什么?
那么,一旦知道了程序必须做的事情,这就够了吗?不。只要大致阅读已知的安全性漏洞列表(如 Bugtraq、CERT 或 CVE)就可以知道,当今的大多数安全性漏洞都是由相对小的一组常见实现错误引起的。这些错误没有单一的标准术语,但是还是有常见的短语来描述它们,如“不能验证输入(failing to validate input)”、“缓冲区溢出(buffer overflow)”以及“竞态状态(race condition)”等等。遗憾的是,大多数开发人员对于这些常见错误是什么没有任何概念,而且他们重复着别人以前已经犯过的错误。
本专栏未来的文章将深入讨论这些常见错误是什么,而且更重要的是,如何避免犯这样的错误。在许多情况下,避免这样错误的方法是细致加简单 - 但是如果您不知道如何避免错误,那么就有可能重犯这样的错误。
在本专栏中,我一般不会设法将多种不同的应用程序(如“Web 应用程序”、“基础结构组件”、“本地应用程序”或“setuid 应用程序”)分门别类。原因是什么呢?今天的应用程序正不断相互联系,它们由许多不同的部分组成。其结果是,您的“一个”应用程序可能有几个不同的部分,每个部分都属于不同的一类,这是十分可能的!但是,我认为更好的做法是,了解如何在任何情况下都能开发安全的应用程序,并随后注意何时应用特定的指导原则。
接下来的一篇专栏文章将由讨论如何验证输入开始。这比听上去还要棘手。例如,我们将看到为什么寻找不正确的输入是一个错误,而且攻击者如何能经常使用非法的负数(而不使用“-”字符)就可以潜入。以后的主题将包括避免缓冲区溢出、使特权最小化以及避免竞态状态。要通过好几篇文章才能涵盖常见的错误,但通过阅读本专栏,您将能够避免那些造成目前几乎所有的软件安全性漏洞的错误。
【编辑推荐】