您的位置:首页 > 理论基础 > 计算机网络

网络安全之如何发现安全漏洞

2013-01-08 09:50 701 查看
如果一个程序有错误,并且只在某些特殊的情况下面出现,它并不是什么大问题。通常,你能够避开这些特殊的情况,使得程序中的错误故障不会发生危害。你甚至可以按照你的意愿,在你的程序中加入这些小小的“臭虫”。
但是,有时有些程序处于安全界限的边缘位置。他们从另外的程序作为输入,但不是按照程序本来的存取方法。
我们常见的一些例子:你从的邮件阅读器读取任何人给你发的邮件,然后显示在你的显示器上,而它们本不应当这样做的。任何被接在因特网上的计算机的TCP/IP栈都从因特网上的获得任何人的输入信息,并且能够直接存取你的计算机,而你的网上邻居们确不能这样。
任何具有这种功能的程序都必须小心对待。如果在其中有任何的小错误,它就能在允许任何人-未被授权的人做任何的事情。具有这种特性的小“臭虫”被叫做“漏洞”或者更正式地被叫做“弱点”。
这里有一些漏洞的共同特点。
心理学上问题
当你写软件的正常部分的时候,如果用户的*作是正确的,那你的目的是完成这件事。当你写软件的安全敏感部分的时候,你一定要使得任何没有被信任的用户都不可能完成*作。这意味着你的程序的很大部分必须在很多情况下功能正常。
编制加密和实时程序的程序员精于此道。而最其程序员则由于他们的通常的工作习惯使得他们的于使他们的软件从未考虑安全的因素,换而言之,他们的软件是不安全的。
变换角色漏洞
很多漏洞是从不同的运行着的程序中发现的。有时是一个极小的错误或者及其普通的错误也会造成安全漏洞。
例如,假设你有本来打算让你在打印你的文档之前想通过PostScript解释器预览它。这个解释器不是安全敏感的程序;如果你不用它,它一点也不会成为你的麻烦。但是一旦你用它来处理从别人那里得到的文件,而那个人你并不知道也不值得信任。这样你就可能招致很多麻烦。他人可以向你发送能删除你所有文件或者复制你所有文件到他人可以得到的地方的文档。
这是大部分Unix TCP/IP栈的脆弱性的根源-它是在网络上的每个人都值得信任的基础上开发的,而被应用在这个并不如当初所想象安全的环境中。
这也是Sendmail所发生的问题的根源。直到它通过审查,它一直是很多是漏洞的根源。
再更进一步讲,当函数在合理的范围内使用时是安全的,如果不这样的话,他们将造成无法想象的灾难。
一个最好的例子就是gets()。如果你在你控制输入使用gets()函数,而你正好输入比你预定输入大得多的缓冲区,这样,你就达到了你的目的。对付这个得最好的补丁就是不要做类似这样的事或者设定比原先大的多的缓冲区,然后重新编译。
但是,当数据是来自非信任的数据源的时候,gets()能使缓冲器溢出,从而使程序能做任何事情。崩溃是最普通的结果,但是,你通常能地精巧地安排使得数据能象代码一样执行。
这就是它所带给我们的...
缓冲区溢出漏洞
当你往数组写入一个字符串,并且越过了数组边界的时候,会发生缓冲器溢出。
几个能引起安全问题的缓冲器溢出情况:
1.读*作直接输入到缓冲区;
2.从一个大的缓冲区复制到一个小的缓冲区;
3.对输入的缓冲区做其他的*作。
如果输入是可信的,则不成为安全漏洞,但也是潜在的安全隐患。这个问题在大部分的Unix环境中很突出;如果数组是一些函数的局部变量,那么它的返回地址很有可能就在这些局部变量的堆栈中。这样就使得实现这种漏洞变得十分容易,在过去的几年中,有无数漏洞是由此造成的。
有时甚至在其他的地方的缓冲区都会产生安全漏洞——尤其是他们在函数指针附近。
需要寻找的东西:
没有任何边界检查的危险的函数:strcpy,strlen,strcat,sprintf,gets;
带边界检查的危险的函数:
strncpy snprintf--这些忽视字符串结尾标记的函数往往会把其他(可能是敏感的)数据复制到缓冲区中,这样就有可能破坏程序;strncat不存在这问题,但对于snprintf不敢肯定,而strncpy是绝对存在的;错误地使用strncat,这样会把一个空的字节放在数组的后面;
安全-敏感程序的崩溃--任何崩溃都来自指针错误,而最多的这类错误来源于缓冲区溢出。
尝试给安全敏感程序输入大的的输入——在环境变量中(如果环境变量没有被信任),在命令行参数中(如果命令行参数没有被信任),在未被信任的网络连接上读取了在未被信任的文件。如果他们把这些输入解释成为很多段,并试图利用这些庞大的数据段。这时,你就要当心系统或者程序的崩溃了。如果你遇到崩溃了,看看是否在你输入的地方发生。
不正确边界检查。如果有好几百行代码都做边界检查,而不是被集中在两三处地方,那么其中出错的可能性会很大。
一个全面安全解决方案是用带边界检查的编译系统重新编译所有的安全敏感程序。
就我所知,Richard W. M. Jones 和Paul Kelly为gcc写的带边界检查的版本是业界的第一个工程。可以在http:www.doc.ic.ac.uk/~phjk/BoundsChecking.html找到。
Greg McGary(mailto:gkm@eng.ascend.com)做了一些其他的工作。
http://www.cygnus.com/ml/egcs/1998-May/0073.html

Richard Jones和Herman ten Brugge做了其他的工作。
http://www.cygnus.com/ml/egcs/1998-May/0557.html

Greg在
http://www.cygnus.com/ml/egcs/1998-May/0559.html
中比较了不同的实现方法。混乱的代表当你让一个常规程序去打开一个文件,程序会请求*作系统打开此文件。由于这个程序是以你的权限运行的,如果你没有权限打开这个文件,系统就会拒绝你的请求。
但是,如果你让安全敏感程序打开——CGI脚本,一个setuid程序,一个setgid程序,任一网络服务程序——它不能依靠系统内置的的自动保护。因为它能做一些你不能做的事情。就以web网络服务器来说,它能做,而你不能做的事是非常少的。但是它至少能读取一些文件的私人信息。
很多这样程序会在他们收到的数据上做某种检查。但常常有以下的几种缺陷:
* 由于它们的检查具有时间依赖性,所以存在竞争条件。如果一个程序首先对一个文件用stat()来核查你是否具有写的权限,然后(假设你是这样做了)打开它,这样你就很有可能同时能改变这个文件,尽管你本来是没有这个权限。(一种可行的解决方案是在打开文件之前就对文件stat()或lstat(),以非破坏的打开方式打开文件,用fstat()来带开文件句柄,然后比较你是否是对同一个文件stat()。)
* 它们会通过分析文件名来检查,但是它们的检查和*作系统的又不一样。这个在很多Microsoft*作系统的web服务器上有很大的问题;这些*作系统对文件和它们的关联不会做很严格的分析。Web服务器通过查看文件名来决定你的下一步的动作;通常,你可以运行一些特定的文件(基于文件名的分析),但是你不能读取它们。如果默认的*作允许你读取一个文件,然后改变文件名使得服务器认为它是另一种类型的文件,但是*作系统还是认为它是本来的文件,这样你就可以成功地读取那个文件了。
* 它们会用及其复杂的方法检查,但是由于设计者的无知,这种方法带有漏洞。
* 它们只进行相当普通的检查。
* 它们检查相当简单。比如,很多老式的Unix Web服务器能够让你下载用户public_html目录除非*作系统禁止这样做。如果你做了符号连接或硬连接的话到其他人的目录的话,就有可能在Web服务器允许的情况下,下载他人的目录。
在任何情况下,如果程序由于你的权限限制而不能完成时,可以使用setfsuid(), setreuid()来帮助。
另一个问题是标准库频繁地查看用来打开文件的环境变量,而且在进行这些*作的时候没有改变他们的权限(事实上,他们也不能。)。这样,我们被迫去分析文件名以便知道它是否合理。
一些*作系统会用错误的权限core dump,如果你能使setuid的程序崩溃,你就能象文件主人一样改写文件。
Fail-openness
最安全敏感的系统不能在一定条件下做正确的事情。他们能用两种不同的方法失败:
* 当他们允许存取的时候,动作被拒绝;这被叫做fail-open。
* 当他们不允许存取的时候,动作被拒绝;这被叫做fail-closed。
CGI脚本通常要执行另外一个程序来处理传递给他们的命令行用户数据。为了避免这些数据被shell当成指令来解释,CGI脚本会除掉特殊字符如‘<’‘|’‘“以及其他等等。你能以fail-open方法通过有被除掉的“坏特性”的一览表来过滤这些字符。如果漏了其中的一个,这个就成为一个安全漏洞了。你还可以通过fail-closed的方法制作一个“好特性的”一览表来是合法的字符通过。这样,即便是忘了一个,也只不过是一个小麻烦,而不会构成安全上的漏洞。例子(Perl在中)在
http://www.geek-girl.com/bugtraq/1997_3/0013.html

与Fail-open的系统相比,Fail-closed的系统得不是很方便,尤其是失败比较频繁的情况下,但比较安全。
基本上所有我看到的为了防护MAC或MICROSOFT*作系统的台式计算机的程序都是fail-open类型的。-如果你是这个程序停止工作,你就能完全控制计算机。相对应,如果你使Unix‘login’程序停止工作,任何人都不能控制计算机了。
吞噬资源
许多程序在编制的时候都假定系统资源是足够使用的(看上面心理学上的问题)。很多程序从不考虑资源不够的情况,因此这些程序经常出错。
经常性需要检查的情况是:
* 在存储器不够或内存分配出错的情况下,调用malloc或者new通常会返回一个空的指针。
* 如果未信任的用户能用尽系统的资源,(这个也是拒绝服务的一种,但也是很多程序的通病)
* 如果可供打开的文件句柄已经用尽--调用open()会返回-1
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息