1.57k likes | 1.69k Views
第 3 章 程序安全. 本章要点. 具有安全隐患的编程错误 恶意代码 防止恶意代码和脆弱性的程序开发控制 控制程序运行时的缺陷. 程序构成了一个计算机系统的很多部分,因此多种形式保护程序是计算机安全的核心。我们将所有代码片称之为 “程序” (program) 。 这里需要提出两个重要问题:怎样消除程序中的缺陷?在程序有缺陷的情况下,如何保护计算机资源? 本章讨论的是一般的程序话题,包括程序编写。程序安全中一个重要问题是 信任 。信任问题是指:假设提供一个已完成的程序,如何来确定它的安全性或者说如何以最安全的方式使用它?答案是采用独立的第三方的评估。.
E N D
本章要点 • 具有安全隐患的编程错误 • 恶意代码 • 防止恶意代码和脆弱性的程序开发控制 • 控制程序运行时的缺陷
程序构成了一个计算机系统的很多部分,因此多种形式保护程序是计算机安全的核心。我们将所有代码片称之为“程序” (program)。这里需要提出两个重要问题:怎样消除程序中的缺陷?在程序有缺陷的情况下,如何保护计算机资源? 本章讨论的是一般的程序话题,包括程序编写。程序安全中一个重要问题是信任。信任问题是指:假设提供一个已完成的程序,如何来确定它的安全性或者说如何以最安全的方式使用它?答案是采用独立的第三方的评估。
3.1 安全程序 评估程序安全与软件质量的评估相似,一种评估软件安全性或质量的方法是让人们列出能反映该软件整体安全的那些特性。然而,不同的人有不同的答案。人们对软件质量的总体设想也会影响对安全性的评估。一般而言,开发者把错误的数量和种类作为衡量产品质量的依据。
当一个人在从事软件活动的过程中有所疏失时,我们称之为过失(error)。过失可能会在计算机程序中导致一个错误(fault),或者一个不确定的步骤、命令、过程或数据定义。一个单独的过失可以产生很多错误,一个错误可以隐藏在任何产品的开发和维护中。当一个人在从事软件活动的过程中有所疏失时,我们称之为过失(error)。过失可能会在计算机程序中导致一个错误(fault),或者一个不确定的步骤、命令、过程或数据定义。一个单独的过失可以产生很多错误,一个错误可以隐藏在任何产品的开发和维护中。 一次失效(failure)就是对系统要求行为的一次违反。一次失效意味着系统没有按照需求执行。 错误是对系统的内部观点而言的,是开发者所见到的;而失效则是对系统的外部观点而言的,是用户看到的问题。
3.1.1 修改错误 修改错误是评估安全质量的一种方法。计算机安全工作建立在“查找错误并打补丁” (penetrate and patch)的模式上。分析专家在该模式下寻找错误,并给错误打上补丁。
3.1.1 修改错误 (续) 缺陷: 1) 修补指定问题的压力使得人们仅仅关注错误本身,而不是与之相关的上下文环境。 2) 在离错误较远的区域,其副作用不明显。 3) 修补一个问题时常导致其他地方的失败,或者说补丁仅仅解决一个地方的问题,而没有解决相关地方的问题。 4) 打补丁可能会影响系统的功能和性能,所以补丁经常不能恰当地修补系统错误。
3.1.2 意外行为 “程序安全缺陷” (program security flaw)是由于程序的脆弱点而引起的不恰当的程序行为。 “脆弱点”和“缺陷”这组词与“错误”和“失败”这组词之间并没有直接的对应关系。一个缺陷可以是一个错误或是失败,而脆弱点经常用来描述某一类缺陷,诸如缓冲区溢出。尽管不一致,但应该记住:我们必须从两个方面来看待脆弱点和缺陷,即起因和影响。 程序安全性缺陷可以来源于任何种类的软件错误。有必要将程序缺陷划分为两种逻辑类别:由开发者的疏忽所引起的错误和恶(有)意设计的缺陷。
3.1.2 意外行为 (续) 分类帮助我们理解一些方法,用以防止将缺陷有意或无意地引入到以后的代码编写中。一个因疏忽而引起的错误给用户及其组织带来的损失可能与恶意漏洞带来的损失相当。计算机攻击(cyber attack)即有意的安全事件,是如今最大的安全威胁。 从根本上说安全是困难的,安全时常与有效性和性能相冲突,而且错误的安全解决方案会阻碍安全编程的真实进展。我们还没有技术来消除或解决所有的程序安全缺陷。原因有两个:
3.1.2 意外行为 (续) 1) 程序控制是在单独的程序和程序员的级别上实施的。功能测试是解决程序“应该做什么”的问题,而安全测试还要确定 “不应该做什么”的问题。要保证程序精确地按照设计者和用户的意图来执行而没有其他的行为,这几乎是不可能的。在一个复杂的大型系统中,不管设计者或程序员的意图何在,许多相关模块都以各种各样难以控制的方式交织着。巨大的规模和复杂性使得我们不可能去预防和检查所有缺陷。想要植入恶意代码的程序员可以利用系统的不完整性成功地将漏洞隐藏其中。
3.1.2 意外行为 (续) 2) 程序设计和软件工程技术的发展迅速远远超过了计算机安全技术的发展速度。所以我们经常发现自己还在努力保护去年的技术,而软件开发者却正迅速地采用当前最新的技术了。 # 尽管如此,我们可以通过理解什么导致错误以及怎样阻止它,来开发技术和工具,保护大多数计算机的应用程序。
3.1.3 缺陷类型 Landwehr等提出了一种关于程序缺陷的分类法,首先分为有意和无意的缺陷。进一步将有意缺陷细分为恶意和非恶意的类型。无意的缺陷分为6类: 失效过失(不完善或不一致):许可权核查。 域过失:对于数据的受控访问。 序列化和混淆:程序流顺序。 不充分的识别和鉴别:授权的基础。 边界条件违规:首次或末次案例失效。 其他可以利用的逻辑过失。
3.2 非恶意的程序漏洞 3.2.1缓冲区溢出 问题说明 定义 1 缓冲区(或数组、字符串)是一个用来存储数据的空间。缓冲区位于内存中。由于内存是有限的,所以缓冲区的容量也是有限的。因此,在许多程序设计语言中,程序员必须声明缓冲区的最大容量,以使编译器能留出所需的空间。
3.2.1缓冲区溢出(续) C程序中: char sample[10]; sample[10]=‘B’; sample[i]=‘B’; //执行过程确定i的值
3.2.1缓冲区溢出(续) 若有: for(i=0;i<10;i++) sample[i]=‘A’; sample[10]=‘B’; 图3.1 缓冲区会溢出的位置
3.2.1缓冲区溢出 (续) 安全含义 一个非恶意的过失造成的缺陷仍然会带来危害,因为一个恶意攻击者会利用这些缺陷。攻击者可以把数据插入到紧跟缓冲区的空间中以造成损害,可以诱使系统执行那些已经计划好的指令代码。这里,给出两类经常使用的缓冲区溢出攻击。 一类是,攻击者可能替换系统空间中的代码,并有可能提高自身的权限。 另一类是,攻击者可能会利用堆栈指针或返回值的寄存器。
3.2.1缓冲区溢出 (续) 另一种缓冲区溢出在参数值被传递到程序中时发生,特别是当参数值被传递到Web服务器上时。例如,当参数转递到URL中,语法是: http://www.somesite.com/subpage/userinput.asp?parm1=(808)555-1212&parm2=2009Jan17 (808)555-1212可能是电话,2009Jan17可能是日期。攻击者可能想了解一个非常长的电话号码,服务器做何反应,如,500位或1000位。这时程序会崩溃吗?会按照可以接受的方式崩溃吗?
3.2.1缓冲区溢出 (续) 带数组的高级语言出现时,缓冲区溢出就随之出现了。起初给程序员和用户带来的困扰很小,最多不过出现系统崩溃。而攻击者利用它首先使系统崩溃,然后制造出可控制的故障,这就导致了严重的安全问题。
3.2.2 不完全验证 问题说明 还是,http://www.somesite.com/subpage/userinput.asp?parm1=(808)555-1212 &parm2=2009Jan17,参数parm2表示日期,若输入1800Jan01、1800Feb30、2048Min32、1Aardvark2Many会发生怎样的情况? 一种可能是,系统将由于尝试处理不正确的数据类型,而发生灾难性故障。另一种可能是接收到这些错误参数的程序将继续运行而得出错误的结果。
3.2.2 不完全验证 (续) 一种解决方法是预测出所有潜在可能发生的问题。通过提交验证技术,程序员可能避免用户无意制造的错误。然而,由于用户可以操作和修改URL,所以这种改进仍然脆弱。在这种情况下,提交的数据并没有经过完善验证,那些敏感的数据处于公开和不受控制的状态。
3.2.2 不完全验证 (续) 安全含义 不完全的参数验证很容易被利用。例如,Things公司专门经营客户产品。如果客户想要购买20个编号555A的物品。每个物品的售价10元,则网站可以计算总价格是200元,客户端的浏览器会自动按格式填充数据:http://www.things.com/order.asp?custID=101&part=555A&qy=20&price=10&ship=boat&shipcost=5&total=205 攻击者可以做如下修改http://www.things.com/order.asp?custID=101&part=555A&qy=20&price=1&ship=boat&shipcost=5&total=25
3.2.3 “检测时刻到使用时刻”错误 问题说明 现代处理器和操作系统经常改变指令和程序的执行顺序。访问控制是确定具有访问权限的人或进程才能进行相应的访问,主要由访问策略实现。访问策略的执行代理对访问请求进行仲裁。检查时刻到使用时刻(time-of-check to time-of-use, TOCTTOU)的漏洞与仲裁在执行中受到诱骗有关。该漏洞也称为序列化(serialization)漏洞或同步(synchronization)漏洞。
3.2.3 “检测时刻到使用时刻”错误 (续) 计算系统的攻击例子 图 3.2 文件访问的数据结构 图 3.3 被修改的数据 # 在访问被检查和检查结果被使用之间的时段,发生一些变化,并导致了检查结果的错误。
3.2.3 “检测时刻到使用时刻”错误 (续) 安全含义 检查一个动作而执行另一个动作 ,是一个无效访问控制的原因。 解决的办法有:1) 确保关键参数在失控时不暴露。访问控制软件一直掌握请求数据直到请求动作完成。2) 确保序列的完整性。也就是在验证期间禁止中断,或验证程序复制数据到程序空间,并基于该数据完成验证。
3.2.4 非恶意程序漏洞的结合使用 以上三种漏洞还可以结合起来使用,以三叉式攻击出现。攻击者先借助缓冲区溢出来破坏机器上运行的任意代码,同时利用检查时刻至使用时刻的漏洞来添加一个新的系统用户。接下来,攻击者以新的用户身份登录系统并应用不完整的参数检查漏洞获得一定权限或做其他事情。因此,对任何一个小漏洞我们都必须了解并保护它不受攻击。
3.3 病毒和其他恶意代码 就程序本身而言很少对安全性构成威胁。程序对数据进行操作,只有当数据和状态发生改变并满足其触发条件时,才采取行动。程序所做的大部分工作对用户来说是不可见的,所以一般用户不太可能发现它们在进行危险的活动。由于用户不能对计算机的程序和数据进行观察,攻击者可以编写自己的程序作为工具来访问和修改其他程序的数据。
3.3.1 关注恶意代码的原因 恶意代码可以以意料之外的方式运行,我们把恶意代码看作是系统中的潜伏者,它可能是正在运行的程序的全部或一部分。 恶意代码是如何进入系统的?当安装一个大型软件包,会运行诸如INSTALL或SETUP之类的命令;当浏览站点并盲目地从中下载应用程序,如Java applet或Active X等。在这些情况下,大量的程序与数据被传送,大量新的文件被创立,已经存在的文件被修改,而作为用户其实对这些实际的过程并不知情。
3.3.1 关注恶意代码的原因 (续) 恶意代码极具破坏性 其他程序能做到的事情,恶意代码也能做到。恶意代码可能什么也不做,直到产生一些触发事件来驱动它运行。恶意代码每一次都可以做不同的事情。通常说,恶意代码行为的可预测性和两岁小孩行为的可预测性相似:大体我们知道他想做什么,甚至知道某些特定环境下的行为,但是他也经常能做出超乎想象的事情。 恶意代码在非法获取的用户授权下运行,与用户有相同的权限,但无需用户授权,甚至无需用户知道。
3.3.1 关注恶意代码的原因 (续) 恶意代码的长期存在 恶意代码不是现在才出现的一种新现象,早在1970年Ware和Anderson就准确地描述了病毒的威胁、程序的脆弱点和程序安全缺陷,特别是那些恶意部分。恶意代码将仍然继续存在下去。研究它们的外在表现和工作原理对我们阻止它们进行破坏活动,或者至少减弱它们的影响十分重要。
3.3.1 关注恶意代码的原因 (续) 产品漏洞和漏洞利用代码出现的时间间隔正在缩短。一般利用过程符合如下序列: 1) 攻击者发现一个先前的未知漏洞 2) 生产商知道这个漏洞 3) 有人开发出代码(称概念证明)证明在特定设置下的漏洞 4) 生产商开发并发布一个针对该漏洞的补丁或者对策 5) 用户采取相应保护措施 6) 有人扩展概念证明或者原始漏洞定义得到一个实际攻击 一旦用户在实际攻击前接受并采取了相应保护措施,就可以避免灾难发生。一种攻击在保护措施可获得之前发生就是0天利用(Zero day exploit)。
3.3.2 恶意代码的种类 恶意代码(malicious code)和欺诈程序(rogue program)是以破坏为目的的一类程序或部分,由一个代理(agent)编制,在软件中造成不期望的结果。代理是指该类程序的作者或是促成程序发布的人。
3.3.2 恶意代码的种类 (续) 病毒是一种复制自身并将恶意代码传递给正常程序的程序,它通过修改正常程序做到这一点。术语“病毒”(virus)的得名是因为它与生物学中的病毒具有相似特征,被它感染的系统将继续感染其他系统以破坏其或使病毒与其共存。 瞬时病毒(transient virus)的生命周期依赖于它的宿主的生命周期,病毒所依附的程序开始运行时,病毒也开始运行;病毒所依附的程序终止时,病毒也终止运行。 长期性的寄居病毒(resident virus)常驻在内存里,保持激活状态或作为独立的程序被激活而不管它寄宿的程序是否终止运行。
3.3.2 恶意代码的种类 (续) 特洛伊木马(Trojan horse)是这样一种恶意代码:它除了程序所具有的基本功能之外,还有一些不易被人发觉的破坏作用。 逻辑炸弹(logic bomb)也是恶意代码中的一种,它将在特定条件产生时被“引爆”。 定时炸弹(time bomb)是逻辑炸弹的一种,它的触发条件是时间或日期。
3.3.2 恶意代码的种类 (续) 陷门(trapdoor)或后门(backdoor)是某些程序所具有的特征,人们可以利用这个特征进入该程序,而不是通过明显、直接的调用,或其他需要特殊权限的进入方式。 蠕虫(worm)是一种通过网络大量传播自身副本的程序。蠕虫与病毒的基本区别在于蠕虫的操作都是通过网络来进行的,而病毒可以通过任何介质进行传播(但通常利用的是已复制的程序或数据文件)。另外,蠕虫的自身复制都是以独立程序的形式传播的,而病毒的自身复制则必须嵌入到其他程序中来传播。 “兔子”(rabbit)定义为一种病毒或一种蠕虫,“兔子”会无限制地复制自己以消耗计算机的系统资源。
3.3.2 恶意代码的种类 (续) 表 3.1 恶意代码的类型 #由于“病毒”常常是对所有恶意代码的统称,且各类恶意代码之间的界限很模糊,因此讨论中通常不严格区分这些概念。另外,两种或两种以上的恶意代码结合可以产生新的后果。
3.3.3 病毒是怎样植入到正常程序中的 病毒要复制必须处于激活状态,完成自己的破坏活动,并进行传播。要使病毒运行有很多方法。例如,在CD-ROM上的含有病毒的程序,可以在CD-ROM运行时将自己安装到目标系统的硬盘,并感染所有正在内存中的运行的程序。这个过程开始需要人为干预,之后就可以自动传播。另一种更普遍的病毒植入方式是将其作为附件附加在电子邮件中,只要受害者打开电子邮件,病毒就可以开始工作。病毒可以伪装成各种文件,而邮件处理软件为了用户方便,常常为用户自动打开附件。
3.3.3 病毒是怎样植入到正常程序中的 (续) 挂接性病毒 一旦病毒程序注入到其他程序中,只要那些程序一运行,病毒就将被激活。最简单的情形是,病毒将自己的一份副本添加到可执行文件的第一条指令之前。这种植入方式非常简单有效,病毒编写者无需知道被注入程序的情况,被注入程序不过是病毒的一个载体。而用户也不会知道病毒的存在,因为原始程序会像往常一样工作。
3.3.3 病毒是怎样植入到正常程序中的 (续) 挂接性病毒 (继) 图 3.4 病毒被添加到程序
3.3.3 病毒是怎样植入到正常程序中的 (续) 包裹性病毒 另一种植入方式是在原始程序运行之前和之后都由病毒来控制运行。常见的如,对组成文件清单的程序感染。 图 3.5 病毒包裹程序
3.3.3 病毒是怎样植入到正常程序中的 (续) 整合并替换性病毒 第三种植入方式是病毒用自身替换目标代码,并将自己整合进原始程序中。编写病毒的人必须了解原始程序的结构。病毒可以模仿目标程序的功能,同时进行自己的工作,也可以忽略目标程序的功能,完成自己的工作。 图3.6 病毒整合程序
3.3.4 文档类病毒 文档类病毒(document virus)是目前流传很广的病毒。这种病毒出现在标准格式的文档中,如文字文档、数据库文件、幻灯片、图片或电子表等。这些文件高度结构化,包含高级程序语言的一部分,病毒编写者正是利用这部分进行恶意操作。普通用户只能看见文件内容,于是病毒编写者将病毒注入文档命令部分,如同上面的整合性病毒一样。
3.3.5 病毒如何获得控制权 病毒必须替代目标程序而被用户或系统调用。病毒可以通过在文件结构中替换目标程序T的代码(或加入到T的代码中去)来使用T的名字,这种技术对普通程序适用。 图3.7 病毒完全替换程序
3.3.6 病毒的藏身之地 病毒希望具有下列特性: 1) 不易被检测 2) 不易被清除,也不易失效 3) 传播感染的范围广 4) 可以重复感染宿主程序和其他程序 5) 容易创建 6) 与平台和操作系统无关 几乎没有病毒可以完全符合上述标准,病毒编写者需要考虑主要功能和将植入何处,而根据需要挑选出其中一部分。
3.3.6 病毒的藏身之地 (续) 以前,病毒制造者面临的挑战是如何使病毒代码重复执行以大量繁殖。然而现在,一次执行已经可以确保病毒大量繁殖。许多病毒都是通过电子邮件传播的,主要有两种方式:在第一种方式中,病毒编写者生成新的电子邮件并发送给受害者地址簿里所记录的用户。在第二种方式中,病毒编写者让用户在不知到的情况下将被感染的文件转发给其他用户。只要病毒的效果没有显现,被感染的文件就有可能被用户发出。
3.3.6 病毒的藏身之地 (续) 一次运行方式 目前大多数病毒只要运行一次就能感染系统。例如,邮件附件中的病毒只要将其打开,内藏的病毒就能运行。 引导扇区病毒 在计算机启动时,首先由固件取得控制权以决定哪些硬件组件将被使用,并预先测试它们,之后再把控制权交给操作系统。操作系统是存放在硬盘上的软件,一些代码会把操作系统从硬盘复制到内存中,并使操作系统取得控制权,这些代码就是引导程序(bootstrap),通常也被称为引导(boot)。
3.3.6 病毒的藏身之地 (续) 固件的控制权转交过程是这样完成的:固件从磁盘上一处叫引导区(boot sector)的特定位置读取一定数目的字节,并把它写入内存中的特定位置,然后跳转至该地址(该地址包含引导程序装载器的第一条指令)。 然后装载器将磁盘中操作系统的剩余部分读入内存。硬件设计者在磁盘上为引导程序装载器保留了空间,PC上引导区略小于512字节,但装载器大小超过这个大小,因而设计采用了“链式”(chaining)技术。链式 技术在保证大规模数据存储的同时,也使得病毒安装变得简单。
3.3.6 病毒的藏身之地 (续) 图 3.8 引导扇区病毒重载代码 # 病毒在计算机启动初期就获得控制权,这样可以避开检测, 且由于在启动区域对用户“不可见”,不易为用户察觉。
3.3.6 病毒的藏身之地 (续) 驻留在内存中的病毒 对于经常使用的操作系统程序和一些特殊的用户程序,需要时再装载入内存将会非常浪费时间。所以对于这些代码,就让它们留在内存里,称之为“常驻内存的代码”。常驻内存的程序通常称为“终止后依然留守”(Terminate and stay resident ,TSR)程序。将病毒植入到常驻内存的代码中,每次运行这些代码,病毒就随之运行,寻找并感染新载体。 病毒也可以修改操作系统要运行的程序表,如,windows的系统注册表,在其中添加自己的表项。这样即使在内存中清除了病毒副本,在每次机器启动时,病毒仍将再次出现。
3.3.6 病毒的藏身之地 (续) 病毒的其他藏身之地 病毒的另外一个寄居的地方是应用程序。许多应用程序,如文字处理软件、电子数据表格等都有“宏”功能,它允许用户自定义一系列的命令并通过一个调用来重复使用它们。病毒编写者可以创建一个宏病毒,该宏可以把自己添加到应用程序的启动指令中,而且它还可以把自身副本嵌入到数据文件以感染所有接收文件的用户。 库也是恶意代码的藏身的一个绝佳的地方。此外,编译器、加载器、链接器、在线监视器、在线调试器甚至病毒控制程序都是病毒喜欢寄居的地方。
3.3.7 病毒特征 病毒不可能完全隐身,病毒代码必须存储在某个地方,而代码必须在内存中被执行。此外,病毒是以很特别的方式来运行、传播的。病毒的所有这些性质提供了一个暴露它们行踪的特性,称为“病毒特征”(signature)。病毒特征对编写所谓的病毒扫描(virus scanner)程序是非常重要的,病毒扫描程序可以自动侦测到病毒,有时候还可以自动清除病毒。扫描程序搜索内存、永久性存储器,监控程序运行并留意是否有病毒特征。