通过这章的学习,你将会:
.描叙软件开发过程中怎样加入安全性。
.列出编码错误类型及其根源。
.描叙好的软件开发实务及说明它们如何影响应用程序安全。
.描叙如何用一个软件开发过程来使得在一个工程中实施安全。
软件工程是一个软件的系统开发,用来创建能完成一系列功能的软件。商业方面、娱乐方面、科学方面及教育方面只是软件使用的一些方面。不管何种软件,对它的运行正常,执行所要达到的功能以及是否处于一个正确的流行趋势都在很多的要求。软件的实际功能从能精确的向数据表中加入数据到心脏起搏器刺激心脏。开发者都知道软件达到了功能规格才算满意。然而软件工程需要在项目管理计划时间期限里加入尽可能多的要求。但是由于系统分析师和开发者花了过多的时间去纠正他们所能优化的功能,而一些不起实际作用的规定却被挤到了后面或者被完全忽略。
安全就是被描叙成一个不起什么实际作用的规定。许多人都把它视为第二重要的。如果时间期限、计划和预算都,也许可以花些时间在安全编码上。随着计算机的无处不在以及我们的日常生活中需要用到大量的计算机程序,忽略安全的这种观点应该要改变。如果在我们的生活需要依赖于计算机程序那么在程序中加入安全是至关重要的。我们应该相信被建造软件将会起作用,而且一直有作用,符合我们的需要以及不会因为外部的影响变更它的行为或功能性。人们不是依靠疫苗去加强体质,他们这样做是为了防止外部因素的影响而导致他们目前身体状况的变化。因为我们越来越依靠于由软件驱动的计算机,我们也需要系统做同样的事情:不仅要有一定的功能,而且也要能防止一些会出现的一些危害。
软件工程程序
软件并不是自身建立的。这是对软件设计者、分析家,程序师及这类人的好消息,设计而且开发软件的复杂性使他们能够获得一份高薪工作。为了要达成这困难的工作环境的继续成功,软件工程程序已经得到发展。只需利用一个完整的开发过程而不是仅仅坐下开始针对着工程开始写密码。软件工程学程序有多种主要的种类。瀑布模型,螺旋形模型,而且演化模型是主要的例子。在每一个这些主要的种类里面,有很多的不同点,每个小组可以根据他们的项目需求以及团体能力来使程序完成一些特定的功能。
传统地,安全是在与一个系统所需功能都达到之后被加入的一个项目。它不是一个软件发展生命周期程序的主要部份。这把它放在机会与功能的和生命周期程序需求。对于这些问题的处理方法相对来说很简单:达到每个功能需求的同时并将安全纳入程序模型以及产品中。该如何完成这一个目标还是具有一定的挑战性。达到这一目标有两个不可或缺的因素.首先,将要用到安全需求的涉入和对于特定的程序模型所采取的措施。其次,为防止在软件设计中安全防护措施失败的机率,需采用安全编码的方法。
程序模型
有一些主要的软件工程程序模型,每个都有着微乎不同的步骤和序列,然而他们有着许多相似的项目。瀑布软件工程程序模型的特征是它有着大量的步骤,而且这些步骤遵从一种线性又单行道的方式,像在一座瀑布中水的直上而下一样。螺旋形的模型是一种螺旋形的运行周期的模式,通过在每一层不同的循环步骤中有使模型不断得获得优化。这些程序模型的的细节问题则超出了本书的范围,而且大部份的细节问题和安全问题并没有多大的关系。从系统构建的需求到编码再到测试,安全是程序的所有过程中一个重要的问题。有关于安全的重要性有一些具体的重要项目。不管是特殊的模型还是在软件创建过程中用到了方法学,四个主要的项目都有意义。如果在项目中需要介入这些需求,那么这些需求应该定义出安全的需求。不管用到了什么方法学,过程都是去得到一个完整的需求。
系统建筑师会根据需求过程中的需求来设计一个系统。同样根据系统和子系统中设计的需求,系统中软件的全部需求的各个分支也会对其作出映射。编码程序的每个部份对于系统的安全也起着重大的影响。项目的测试是可以检验是否符合需求的一次机会,而且甚至可以发现一些意想不到的可以导致安全问题的行为。不管用的什么特定的程序模型这些问题都是会出现的。关键是在需要过程中加入安全需求,而且在软件设计,编码,测试阶段都应该意识到安全的重要性。
需求程序是软件开发中的一个关于安全的主要成份。在需求程序期间被列举的有关安全的项目在软件发展程序的其它部分各处是看得见的。它们可以被构建于系统及它的子系统中,在编码测试期间可以用到。为使后续的步骤更有效,则需求安全需求具体的同时而且是正确的。诸如“make secure code”或者“no insecure code”的在整个程序过程中都是不明的而且也是不利的。特定的需求诸如“prevent unhandled buffer overflows,or unhandled input exceptions”可以在每段编码中可以具体编码。
Rol 和错误纠正
开发一组高质量的安全需求在整个的程序过程中会导致的第二个重要结果是:提高了开发的总费用。纠错会造成额外的工作和费用。反复重做的次数以及额外工作的程度是导致在程序开发过程中错误越晚才会被发现的直接因素。直到程序开发结束错误才被发现,往往这种代价是最大的。这些错误需要在一些步骤中重做修改,甚至包括设计阶段。在一些步骤需要重做,甚至包括设计。然后有编码重做,测试的重做和一个新的配置周期如果编码重新编制过.把重做的程度和在需求或者构建过程 中发现到错误比较一下,很明显会发现,需要重做工作越少越重要,因此重做程序中费用越少也是非常重要的。
开发一个软件工程程序应当尽早在程序中检测出错误。这一个检测暗示着贯穿于整个程序开发过程中听一系列测试。也暗示是被测试开发程序的每个过程中的特定需求指定的不同的测试方法。对于与安全有关的一些错误,这些需求都注重在安全问题上,可能你会想到。从考虑费用的角度来看,错误的来源或者种类并不是特别重要的,重做它的代价只是采取措施。如果商业目的是要降低总的开发费用、那么通过好的需求分析来减少错误以及尽早地检测错误、纠正遗留的错误才是降低成本的最好方法。
安全的密码技术
设计完成的重点是在软件开发过程中的编码阶段。在编码例示一个想法是导致程序出错的重要原因。这些错误可以分为两种类型:一是编码中没有实现自己想要它实现的功能,另外一个则是编码中多了一些没有期望所实现的一些功能。如果在程序的前部份需求分析被列举过,则测试第一种错误会相对简单一些。
为不想要行为的包含尝试重要地更困难。为一个未知者尝试是一件事实上不可能的工作。什么全然制造这可能者是为先前决定的错误的种类尝试的观念。通常错误的一些班级已经被观察。最通常的这些当做一个缓冲是一个已知的错误的类型泛滥。其他的通常类型是编码注入,特权错误和关于暗号的失败。
缓冲泛滥
如果在编码阶段有一个可标为“使用量最大”的项目,那它全是缓冲溢出,卡内基美隆大学的CERT/CC组估测几乎一半电脑程序中利用起源于一些形式的缓冲溢出。
籍着类型以及大概 90%的通过书籍找到抑制缓冲溢出问题方法将会彻底消除一半安全有关的事件。在1998年莫理斯手指(触摸)虫就是对缓冲溢出的一种利用,到现在仍广为人知Code Red 和Slammer.缓冲溢出的通用类包括许多不同种类的变量,如静态缓冲超时,索引错误,格式字符串错误,统一码和ANSI缓冲大小一不符合,以及堆超越误差。
导致这些易损性的原因是相对简单的。用来写入数据以存放输入程序的输入缓冲已超出它实际 能存放的数据。这一个易损性的问题根源有两个:程序编写经验太差和程序语言知识的缺乏。如C语言这样的程序语言是为空间和执行动作限制而设计的。在C语言中的有许多功能不安全,就像gets( ),因为它们会允许通过指针来直接对存储器读写,这种功能提供了很多程序巴儿驱动,但同时也给程序员加大了提供适当保护措施的负担。
第一道防线就是编写可靠的代码。不管用了哪种程序语言或外部输入来源,慎重的操作程序是把外部的输入都看成是不利的功能。使这些看似不利输入信息有效而且试图使缓冲溢出。应该意识到,虽然在程序开发过程中,每个人都处于一个团队中,而且所有人必须认真对待遵从设计规则,但是以后的维护员也不一定会很可靠。在程序功能中的了预防措施是对抗这各易损类型的基本防卫。
同样重要地,第二道防线是采取适当的字符处理。在程序中字符串处理是非常常见中,而且串处理是许多众人所知的缓冲溢出易损的来源。用Strncpy( )来代替strcpy( )是一种提高安全性的简单方法。Strncpy( )功能需要拷贝一串字母的输入长度。仅管给字符串功能块赋值会使得操作有错,但在赋值前作进一步确认会防止缓冲区越界问题。为实现程序的功能而对功能块的正确使用对于防止如缓冲溢出这样的不期望的结果的出现。使用gets( )这样的功能是不会完全的。因为它需要到stdin流中读取数据直到线程结束或数据返回,在大部分的情况下,没有办法会预测出输入的数据是否会发生缓冲溢出。较好的解决方法是使用一个C++流对象或 forgets( )。功能块fgets( ) 需要输入缓冲区长度,因此就可以避免溢出了。只需简单地用
{char buf[512]l
Gets (buf)l
}
代替{char buf[512];
Fgets (buf,sizeof (buf),stdin);
}
密码注入:
我们已经看出对没有确认的功能块的输入将是一个冒险的行为。另外一个不有效输入的问题是密码注入的情形。并非输入的数据可为功能块所适用,而是密码注入会以一个不期望的方式改变着功能,SQL注入功能是对SQL数据库的攻击,是一种代码注入方式。这种类型的攻击是从功能块何处获得用户用于输入的用户名和密码。并用它来代替SQL语句中的一个 where子句,这种做法的目的被叙述成改变一个where子句使得它对问题提供错误的答案。
假设一个所需的SQL语句为
Select count(*) from users_table where username=’JDoe’and password=”newpass”
由用户提供的值JDoe和newpass简单地被嵌入了字符序列中,虽然表面上看起来这功能很安全在,但是如果使用or 1=1 __ 功能很容易被搞乱。因为这种对where子句所做的改变会返回以下的记录:
Select count(*) from users_table where username=’JDoe’and password=’’or 1=1__
对于or子句的加入,往往是一种正确的语句,而且在阻止随后的单个引用的注解行的开始会将SQL语句改为一个带有无法得到执行的where 子句的语句。
对于防止这种易侵性的主要方法与防止缓冲溢出的方法是类似的,即使所有的输入都有效,但是不仅仅要使输入长度有效,而且也要使输入内容有效。想象一下一个需要用户输入的网页,在后续的网页建设中也需用到那个输入。现在假设用户在输入序列中加入javascript功能块的文本,并加入 个Script 的Call语句,现在,当需显示所做的网页时则所调用一个附加的Javascript,在使用这前把用户输入传送到HTML译码功能块可以防止这类的方法。
再提一下,好的编程经验对防止这些易侵性问题是有着很大的帮助的。这样不仅给程序员增加了负担,而且对程序员的培训也增加了负担,也对为找出程序错误进行检验密码和测试程序的软件工程形成了负担。这不仅仅是一个人的责任,在软件开发过程中则需要意识到这些错误的类型和因素以及要防护好这些错误的增长。
最低优先权:
安全的一个核心算法是要意识到运行一个具有最低优先权的程序。最低优先权需要开发人员明白一个应用程序需要怎样具体的优先权来执行和存取它所有必需的信息来源,明显地,从开发人员观点来看,对所有的任务使用管理权限将会容易点。这样就只可以去除方程式的访问控制了,但这也去除了为运行而设计的存取控制的最高保护,另一方面,为操作系统设计的没有嵌入一点安全性的软件。如早期的Windows系统和一些主流的OS,安全性都是以应用程序包的形式出现。当把这些应用程序移到平台上时,存取控制就成了一个问题。
当开发人员把一个没有安全嵌入的开发移到一个嵌入了安全系统的操作系统中,自然的趋向是对这个“新的”安全需求进行编码。像以前那样的方式去开发。这普遍视为一个只运行在管理administrative level count 下的程序,或者作为利用System引起Windows中许多允许操作的后台。这两种做法都是不好的。会降低安全性,使处理错误变得困难,而且会产生一些更难保留和扩展的代码。
对于存取控制,设计和编制软件程序的主要原则是规划和懂得软件和操作系统以及系统资源间的自然相互关系。每当软件存取一个文件,一个系统成分或其它的程序,则需要说明适当存取控制的问题。而且尽管指出一切根源或管理控制的简单问题可以解决这些急迫的问题同,在以后会产生更大更多而且又不易的问题。例如,由管理者帐户引起可使程序运行正确,但在一般的用户优先权下程序会运行错误。真正的错误可能起源于优先权问题。但是编码中错误点可能有许多的程序,而且诊断这些错误是一个非常困难又耗时的操作。
第一条实际上是很简单的。决定需要存取什么以及获得许的适当水平,然后把这一程度用于设计和执行中。每存取一项里都重复这一动作。最后,许多功能的管理通路是非常罕见的。一旦应用程序设计出来,整个的过程都需要重复执行安装程序,因为许多情况下,安装程序往往比执行一个软件需要更高的通路要求。设计和执行细节则需要求允许程度来决定,而不是仅仅为方便的管理底层的一个较高水平。
注意这种失败的代价可能分为两种。首选,仍有代价高的且又耗时的违反通路错误,而且这些错误很验证被追踪到以及。第二个问题是当一项使用被 发现,就会允许其它的程序以一种未经认可的方式来使用你的密码部分,一个主要的例子是在UNIX环境下的
Sendmail eoploit.因为Sendmail需要对某些功能块的根层访问,这种利用把外交代码插入到程序流中,因此要在通路中执行这个代码,因为Sendmail程序线程本身没有通路,在这种情况下,Sendmail 需要通道,但这说明风险是存在的,而且一旦发现被使用,在许多情况下,适当的设计可以消除对这些高端道路的优先水平的使用。
加密技术可以称为是所有问题的解决。密码技术的用法在大多数情况下都 是解决一切的万灵药,就如不同地区的销售员卖的万灵药一样。没有一样可以提供一切解决办法的事物,然而有一些可以提供各种类型保护的多功能工具。密码法就是这些工具中的一种非常有用的工具。正确地使用密码技术可以实现大量的程序功能。
从完整性到不拒绝接受,这些都是非常宝贵的工具而且许多程序为了实现重要的功能都 依赖于适当的加密块。这一功能性的重要使得程序员自己动手来运用加密功能块。而这是一项会引起加密错误机率大的一项工作。
加密性的错误来自一些常见的因素。一个典型的错误就是选择开发你自己的加密算法。开发一个安全的加密算法并不是一种容易的工作,即使是专家来做,一些错误的出现会合这些加密算法没用。加密算法只有经过数年的仔细研讨和使用之后才算可靠,而且任何一种新的加密算法需花很多年后才会值得可信。如果你宁愿决定坚持保密,则需注意到保密和专有算法从来没有提供过对于保护的要求程度。加密技术的一个原理是没有一种模糊定义的安全性。
决定使用一个可信的算法是一个正确的开始,但是仍有几种大的错误会出现。第一种错误是在演示算法时出现的一个错误。避免这种错误的简单方法是利用已经被正确测试过的一个函数库。这些函数库的来源是非常广泛的,而且对功能性的需要提供了一种经济解决方法。一旦你有了一个算法,而且已经选择好了一个特定的实例,一一个需要做的项目就是使用一个随机数产生一个密钥,加密技术功能使用一个算法和一个密钥,而且密钥是一把数码数字。
随机产生一串数字并不是一件琐细的任务。计算机是被誉为相同输入就会产生相同结果的机器,所以产生一个纯粹的可接受的随机数也是不简单的。在大多数的程序设计语言的函数库中都了随机功能块。但是这些只是伪随机数的产生器,而且尽管输出数字的分发显得很随机,但是它产生的是可再生的序列。如有了相同的输入,第次对功能块的运行就会产生相同序列的随机数字。限定seed 的随机序列以及使用“破坏”加密功能的这些曾不止用过一次来避开安全。这种方法曾用来过取消一个早版的Netscape’s ssl 执行。使用加密化的随机数字产生器可以再次解决这个问题,而且为设计和测试这一目的可信函数库使用才是一种正确的方法学。
现在你已经得到了一个好的算法和一个好的随机数-这样你还能出现什么错误呢?是这样的,把私钥存储在一个可以被 未经认证的人使用的地方是一下需操心的。钥匙管理的不强已经佫加密技术的落实失败过多次。一个著名的用法是从可执行处得到密钥,而且利用去破坏一个加密方案,这种做法经常是黑客采用的用法来破坏数字化视频光密码技术而且产生一个Decss程序。现已有些被 开发出的工具用来搜索“随机”钥匙的代码以及从代码或者运行程序过程中提取出钥匙。一个叫ncipher的英国公司在他们的网站www.ncipher.com上的大量的页都详细叙述这种类型的攻击。
第一步是非常简单的:在你的编码中不要使密钥太复杂。它们可能或者以后被发现。钥匙被产生后然后通过参考,将会合在网络或应用程序中合副本的利用率减到最少。把它们以非邻近的方式存储在寄存器中对防止外部检测是同样重要的。
你也许已经从“库函数”术语上推演出库函数与这部分是相近的。这只是一个意外情况。实际上,这可能是这章中最好的一条意见:使用商业上认可的功能块来实现加密功能。
什么比较出名?Canonical representation issues 是一个简单的观念:注意到名字。在一些来源的名字如文件名或URL上做一个安全决心是一个危险的看法。对于大量的原因,不止有一种方法去表示一个对象的名字。玫瑰总归是玫瑰。但是花名的字母可以心十六的进制的转义码来表现。Canonicalization是把名字最简易化的一种转换。从canonicalization错误中的程序错误会导致不为所望的行为。这种易受侵性的例子包括IIs ::$DATA错误,和基于URL上通过检查的一系列的统一码利用。避免这种错误的最好方法是在做决定时不要相信文件名,而且如果你必须使用一个文件名,则首选把这个信息在canocicalization例行程序上运行一遍。
有时错误的方式并不是使程序运行不同,而是使它们之间有联系,这样尽管程序在运行但却不起作用。这叫做Denial-of-service入侵攻击。Denial-of-service常以许多类型出现。但每一种类型都有自己的运行方式,最普通的一种是想要关掉程序的功能块,在一些情况下则会关掉整个服务器。对Denial-of-service攻击的防护是很复杂的,因为这需要你决定代码怎样是易侵的,以及怎样的保护才能使得使得代码不永执行状态。对我们有助的一个重要原则是不要相信输入信息,在将它们用于一组组件需使所有的输入信息有效。
好的操作:
在早期叙述的关于许多问题的正确操作是很平常的而且最好把重点放在技术特性上,以及直接和编码或逻辑错误有关的事上。即使这样,在软件开发过程中这一系列错误率也存在着。使用将安全功能放入程序中的软件开发程序会使得程序最终结果产生不同。程序由需求开始,随功能性变化,以测试而结束。列举且定义特定的安全需求以及如何测试人与人之间是把安全嵌入中的一个主要成分。
在以前已经提到,安全需求往往是包含在一个特定项目的最后一项要求,然后只要当时间和预算一旦允许“商业“功能后才会完成。把这个程序倒过来而且加入一组与所有程序有关的安全需求,而且列举和定义它们一次会大大有助于开发团队用指定的名字和在时间限制内而提供用户所需要一些特殊功能。把安全需求作为最重要的一个角色而且和维持安全功能的一个可接受程序共同支持作为解决大量问题的基准线。
对功能块加入如“永不要相信输入信息“这样的系统需求以及”使所有缓冲区长度有效“可以被 放入一个模块中而用于所有被开发的组件中。。建立一个”查阅代码“的需求,当延续一个程序员或者一组程序员在对代码功能块进行测试之前先查阅,这样就会捕获到许多错误。这种方法被普遍认为一个代码的游戏以及发现到早期错误的有效方法。这种程序类型的附加,虽然这个看起来管理过高,但在代码质量上却有很大的不同。为产生代码生成的这类型的工作做的越近,纠正发现的错误的代价也越小。需求阶段是软件开发程序模型中的第一个步骤,软件开发模型是记录了最终产生的需求的细节。因为它出现在模型的最初阶段,这就需要以后的步骤中都 得注意到具体的需求。
测试:
如果需求标志着编码中安全产生的起始,那么测试就标志着另一个边界。虽然在测试之后有附加的功能,但是却没有人希望用户中的错误有效。而且不管是错误严重性在代码已产生错误之后才被发现的代价是最大的,应用用例将程序的回应和已知的输入信息作比较,然后比较输出结果和所要得到的结果,这种方法就是测试软件的方法。对测试特定功能需求的用例的设计是基于需求阶段被 确定的需求上的。提供附加的与安全有关的来确定特定安全性的驱动方式也同样以测试。
测试阶段是在终端用户遇到问题前确定软件是否运行正确的最后机会。在程序开发中测试阶段发现错误太晚了,但是至少他们在用户遇到这种问题前只可以提前被检测到。测试可能会发生在开发阶段的任何阶段:组件中,子系统中,系统中和完整的应用系统中。错误越早被发现以及越早纠正,造成的代价就越小。而且对于项目计划的影响也越小。这些使得测试在开发一个好的程序成为一个至关重要的阶段。