全面保护你的Java程序安全(中)
第二部分:不要让漏洞危及应用程序安全
概论:在这个部分的安全讨论中,Todd Sundsted和我们一起讨论应用程序安全问题。在程序开发中的微小错误都可能给开发人员和用户带来很大的安全问题。Todd在这里将展示如何设计及实现这种最普通的安全漏洞类型,并描述了如何避免这些问题。他还提供了一个来自Sun自己JDK中的一个漏洞示列。
大多数软件开发人员都能意识到那些恶意或是仅仅是好奇的黑客所带来的威胁,但很少开发者认识到他们无意中带到程序中的漏洞造成的影响范围有多大。这些带到程序中的漏洞无疑纵容,帮助了所谓的黑客们。
在今年一月,一名德国的软件开发人员证实在最近开发源代码产品Borland的数据库 InterBase中有个带有严重安全隐患的设计上的漏洞。在这个InterBase版本中的漏洞甚至可以追溯回到1994年!
这个存在于登录名和密码验证窗体上的后门并非有人故意为之。然而,正是InterBase开发人员不当的设计造成了这个问题。这个程序使用了命名和密码验证来控制InterBase接入数据库。
尽管这个错误展现的是个极端的列子,但它所揭示的教训是很重要的:作为开发者,对于我们应用程序整体安全,我们的设计以及代码实现是非常关键的。并且,如上面的列子所示,由开发者带入程序中的漏洞可能会影响用户达数年时间。
·回顾
上次,我们讨论了我们必须检查三方面的安全问题。这些问题尽管互不相同,但经常要求跨越三方面进行全面的考虑。Java程序的开发者必须要明白从这三方面来考虑他们解决方案的安全性问题对其产品的重要意义。
Java和非Java的开发者对这样一个最知名的安全问题都是熟悉的,那就是虚拟机安全。这源于对于JVM和运行时环境多年来大量的关注。虚拟机安全包含JVM和提供支持的运行时环境。在过去的几年里,虚拟机安全问题得到了加强,并得到了很好的重视。
最近,由于在JVM领域明显的漏洞已少,多数编程者的注意力已经从Java虚拟机安全转向到运行于其上的应用程序安全。而这些多数Java程序员在这层面将明显的影响到Java安全。在这方面,需要处理的是设计决定以及在开发中可能出现的意外问题。当然,不是所有这样的漏洞都会危及应用程序安全。我们将集中精力于那些能够危害程序的漏洞。
最后一方面的问题,网络安全问题,其基于网络的程序和程序组件之间的通信问题是安全问题方面的一个粮亮点。再次声明,不是所有这里介绍的漏洞都会危及安全,我们只注意那些能够危及安全的方面。
上次我们讨论了虚拟机安全问题并示范了VM安全方面的漏洞如何出现及进行破坏的。现在,我将讨论应用程序安全。这里,我举出一个最普通的有漏洞的类,以助你避免这样的问题。我会包含最近在Sun自己的代码中发现的列子。
很容易列出一个各种错误设计以及实现的列表清单,这里要说的列子只是其中的一个。但我将定义和描述出大多数Java程序安全弱点的一个分类目录。这个目录主要分为两大块:“实现相关”和“设计相关”的漏洞。
·实现相关的漏洞:
实现相关的漏洞通常是在实现代码时带入到程序中的。这通常是由于粗枝大叶的代码编写,对需求理解不够充分,以及不熟练的编程技能等造成。实现相关的漏洞由于不充足的测试以及复核而常常隐藏的比较深。然而,如果系统设计良好,你就可以不改变设计而更正这些漏洞:
·定时问题:
最有害的定时问题是资源竞争问题。这样的问题在当两个没有经过适当同步的线程同时争夺同一个资源时发生。在这两个互相影响的线程或者是在不懈调,无效的状态下放弃对象,或者是当恶意代码利用了正被另一个线程使用又没有被很好的保护的资源时,安全漏洞就会出现。通常的解决办法是简单的加上同步措施。
·不充分的输入校验:
系统的输入在使用前一定要经过检验。尽管某些输入是来自信任源,但出于安全考虑,所有这些输入都应该看作不被信任的,危险源。未经完全检查的输入可能带来大量严重的安全弱点。
·不当的随机数:
好的加密系统需要高质量的随机数发生器。早前Netscape Navigator的大量安全漏洞直接就是来自于不适当的随机数发生器。随机数发生器产生的有效密匙达不到建议的长度,结果很容易遭到破解。
·设计缺陷:
源自于实现部分的安全问题已经很糟糕,但最坏的情况是由于设计上的缺乏长远考虑,对语言及函数库的理解不够充分而引起的。这种漏洞通常使程序逻辑纠缠不清,必须花掉大量时间和耐心去处它,即便这样也会是很困难的事。上面提到的InterBase漏洞就是极好的这样的列子。
·初始化问题:
大家都知道“new”操作并不是创建新实列的唯一方法。像克隆这样的方法也可以创建新的实列。你可以通过非常规的方法创建实列,以搞乱类的安全系统。
·可见性和扩展性问题:
可见性(类或是其成员是否是公共的或是私有的),扩展性(子类是否能继承一个类或是方法)为软件开发人员提供了非常重要的工具。然而,如果使用不当,这也会造成一些微妙的漏洞。
在子类化的列子中,一个子类可以改变继承自超类的约定,定义。扩展后的代码使用一些自己的约定可以破坏程序安全。解决办法就是:合理使用“final”关键字,防止子类进行重定义。
同样,你可以通过使用关键字“private”防止类内部流程和状态被修改,反之,则可能将类内部细节暴露在随后添加到包中的类下。
·绝不要放置“后门”:
InterBase的设计就给我们提供了这样一个列子。你不能私自在程序里面建立什么后门代码。这些代码通常是位于密码校验和加密数据的窗体。这对于一个固执的黑客来说根本够不成障碍。一旦后门被破解,“潘多拉的魔盒”就打开了。
·Sun自己也会犯错:
我非常尊敬Sun的工程师。然而,尽管他们都是很有天赋的人,但还是如你我一样会犯错误。
在2月23号,Sun宣布它发现了JDK中的一处漏洞。下面是他们的声明:
“在某些Java运行时版本中的漏洞可能会允许恶意Java代码执行未经授权的命令。然而,恶意命令代码一定已经取得了代码执行的授权。”
如果这种代码在某种环境下被给予执行至少一条可执行命令(如echo),这个漏洞会允许不被信任的Java代码激活任何可执行命令(如格式化FORMAT)。这样的错误很可能隐藏很久而不被发现。
下面就让我们来看看具体的代码:这个漏洞是位于java.lang.Runtime类里的exec()方法中:
public Process exec(String [] arstringCommand, String [] arstringEnvironment)
throws IOException
{
// Ensure that the array parameters aren't null, their elements
// aren't null, etc.
.
.
.
// Do some stuff.
.
.
.
// Get the security manager.
SecurityManager securitymanager = System.getSecurityManager();
// Check the first element of the command array -- which should
// be the name of the executable to invoke. Ensure that it has
// executable privilege.
if (securitymanager != null)
securitymanager.checkExec(arstringCommand[0]);
// Now, invoke the executable.
return execInternal(arstringCommand, arstringEnvironment);
}
你看出问题了吗?
这个错误位于最后三行中(注释和空格除外)。首先,安全管理器检查可执行名,看其是否在配置文件中有执行的授权。接下来,代码执行命令。哎哟!在一个多线程环境中,参数数组内容在这两步之间就可以改变。由于这两个输入参数数组被直接使用,调用者仍然掌握着它们的引用,并且可以修改其内容。
更正:立即复制输入数组并在拷贝中进行操作。
·回到最好的练习:
经由老式的软件开发练习,你可以发现很多导致安全问题的漏洞。清楚的需求,严格的设计核查,完整的代码核查,并通过详尽的测试可以挖出很多漏洞并进一步提高软件整体质量。