1.6.3 缺陷和后门攻击
网络蠕虫传播的方式之一是通过向finger守护程序(Daemon)发送新的代码实现的。显然,该守护程序并不希望收到这些代码,但在协议中没有限制接收这些代码的机制。守护程序的确可以发出一个gets呼叫,但并没有指定最大的缓冲区长度。蠕虫向“读”缓冲区内注入大量的数据,直到将gets堆栈中的返回地址覆盖。当守护程序中的子程序返回时,就会转而执行入侵者写入的代码。
缓冲器溢出攻击也称为“堆栈粉碎”(Stack Smashing)攻击。这是攻击者常采用的一种扰乱程序的攻击方法。长期以来,人们试图通过改进设计来消除缓冲器溢出缺陷。有些计算机语言在设计时就尽可能不让攻击者做到这点。一些硬件系统也尽量不在堆栈上执行代码。此外,一些C编译器和库函数也使用了许多对付缓冲器溢出攻击的方法。
所谓缺陷(Flaws),就是指程序中的某些代码并不能满足特定的要求。尽管一些程序缺陷已经由厂家逐步解决,但是一些常见问题依然存在。最佳解决办法就是在编写软件时,力求做到准确、无误。然而,软件上的缺陷有时是很难避免的,这正是今天的软件中存在那么多缺陷的原因。
Morris蠕虫及其许多现代变种给我们的教训极为深刻,其中最重要的一点是:缺陷导致的后果并不局限于产生不良的效果或造成某一特定服务的混乱,更可怕的是因为某一部分代码的错误而导致整个系统的瘫痪。当然,没有人有意要编写带有缺陷的代码。只要采取相应的步骤,我们可以降低其发生的可能性。
第一,在编写网络服务器软件时,要充分考虑如何防止黑客的攻击行为。要检验所有输入数据的正确性。如果程序中使用了固定长度的缓冲器,要确保这些缓冲器不会产生溢出。如果使用了动态分配存储区的方法,要考虑内存或文件系统的占用情况,同时还要考虑到在系统恢复时也要占用内存和磁盘空间。
第二,必须对输入语法做出正确的定义。如果不能真正理解“正确”这两个字的含义,就不能做出正确性检查。如果不知道什么是合法的,就无法写出输入语法。有时,对于语法正确性的检查需要借助于某些编译工具。
第三,必须遵守“最小特权”原则。不要给网络守护程序授予任何超出其需要的权限。特别在设置防火墙的访问控制规则时,轻易不要授予用户超级用户权限。例如,我们会给本地邮件转发系统的某些模块授予一定的特权,使其能将用户发送的信息复制到另外一个用户的邮箱里。而对于网关上的邮件服务器,我们通常不设置任何特权,它所做的事情仅局限于将邮件从一个网络端口复制到另一个网络端口。
如果进行恰当的设计,即使是那些好像需要授权的服务器,也不再需要授权。例如,UNIX的FTP服务器,允许用户使用root权限登录,并能够绑定到20端口的数据通道上。对于20端口绑定是协议的要求,但我们可以采用一个更小的、更简单的和更明确的授权程序来做这件事。同样,登录问题也可以由一个前端软件来解决。该前端软件仅处理USER和PASS命令,放弃授权要求,并执行无特权程序。
最后需要指出:不要为了追求效率而牺牲了对程序正确性的检查。如果仅仅为了节约几纳秒的执行时间而将程序设计得既复杂又别出心裁,并且又需要特权,那么你就错了。现在的计算机硬件速度越来越高,节约的这点时间毫无价值。一旦出现安全问题,在清除入侵上所花费的时间和付出的代价将是非常巨大的。