Java安全基础


第一部分:Java的安全基础??虚拟机和字节码安全 

概论:安全问题对很多数人来说都非常重要。从其历史看,Java安全主要意味着虚拟机和字节码安全。然而这个看法忽略了两个重要方面?应用程序和网络安全。在下面一系列文章中,Todd Sundsted讲解了JAVA虚拟机安全,应用程序安全,网络安全,解释了应该采取什么样的措施来全面巩固你的Java安全。在这第一部分,他向我们解释了Java安全的基础:虚拟机和字节码安全。 

“似乎还没有人曾因为写出了不安全的Java代码而遭解雇”。这句话是我对那句流行语“没人曾因购买了IBM而遭解雇”的修正版本。那些更关心网络速度和那些更有兴趣为简历添加更多有价值项目的雇员常常犯下安全问题。 

再来看看另一个令人担心的现象:在我同管理人员和工程技术人员谈论安全问题时,我常常发现他们对自己的行为存在一些误解,他们认为不必考虑安全问题,因为“Java本身就等于安全”。在这样错误观念的指引下,工程师们没有去考虑以下三个方面的安全问题:虚拟机安全,应用程序安全,网络安全。 

在以下一系列文章中,我会尽力修正这种错误见解。接下来,我将就三方面的问题来对Java安全进行讨论,并举列说明一般安全问题是怎样窃入的。另外我也会介绍一些办法来创建安全的应用程序。 

?三种安全问题: 

在Java初次露面时,开发者,研究人员,新闻媒体界对其安全问题就反响剧烈。在早前的时候,Java安全就是意味着字节码安全和虚拟机安全。由于Java过去主要是作为下载到本地执行的小应用程序开发语言,下载下来的代码的安全性和执行环境就是异常重要的事情。这种情况下的安全意味着正确安装类装载器和安全管理器以及验证下载的代码。 

在我以前开发C/C++程序的数年里,我从没担心过虚拟机安全问题?这个问题完全是随着Java而成了人们关注的中心。谈到安全问题,我担心的总是应用程序漏洞或是危及程序或系统安全的执行情况。在C++领域,应用程序上的安全包括限制“setuid”代码范围(在Unix环境,setuid代码是作为另外的用户进程来运行?典型的情况是超级用户)并力图避免缓冲溢出及其它类型的堆栈问题。 

而分布式应用程序的引入则带来了另外一些方面的问题。正如其名字所示,分布式程序由多个部分组成,每个部分都驻留在它自己的机器上,并通过公共网络和其它部分通信。一个Web应用就是典型的列子。在网络意义上的安全则意味着签名,授权,应用程序组件,加密通信管道等。 

许多开发人员并不清楚以上几方面的不同,并以为Java在虚拟机一层安全了,那么整个应用程序就安全了。我很希望改变这种观念。下面就开始来讨论Java的虚拟机安全。 

?安全基础:虚拟机安全 

虚拟机安全,长期以来一直是开发人员注意的焦点,几乎直到现在也还是没有结果。 

我最初对讨论虚拟机安全感到有兴趣是在转向应用程序和网络安全之前。我决定给予它同另两个部分同样公平的时间来讨论,这出于两个理由:首先,优秀的编程教材因该包含过去6年来发现过的大量漏洞,第二,很多安全问题跨越了我要讨论的三个方面。为了能透彻理解,你必须要全面熟悉三个方面,包括Java虚拟机安全。 

如果你检查过去6年发现的各种安全问题(看 http://www.javaworld.com/javaworld/jw-06-2001/jw-0615-howto.html#resources的官方清单),你将发现它们被分成一系列目录。就所关注的虚拟机安全来讲,最重要的两种安全漏洞都是围绕着未被验证和可能非法的字节码以及Java类型系统破坏来展开。在实际开发中,这两者经常是关联在一起 

?未验证代码探秘 

在JVM通过网络从服务器上下载类代码时,它并没有办法知道这些字节码是否能安全执行。安全的字节码永不会指示虚拟机执行让Java运行时处于不懈调和无效的状态。 

通常,Java编译器可以确保创建的类文件里的字节码是安全的。然而也可以手工写出Java编译器不允许的字节码。Java校验器以一系列极富想像力的方法检查所有这样的字节码并验证那些不合规范的代码。一旦校验完成,JVM便知道程序代码是安全的?只要校验器正常工作。 

下面让我们来看看一个列子,以更好的理解校验器所扮演的角色,并看看一旦校验器失效会产生什么后果。 

考虑一下下面这个类: 

public class Test1 

public static void main(String [] arstring) 

Float a = new Float(56.78); 
Integer b = new Integer(1234); 
System.out.println(a.toString()); 

  } 

当你写完它并运行,程序将向屏幕打印出字符串“56.78”。这是个在类里分配的一个浮点型变量。我们即将修改一处代码,欺骗虚拟机在整型变量上激活toString()方法而不是浮点型变量(你可以从网址下载并修改源代 http://www.javaworld.com/javaworld/jw-06-2001/jw-0615-howto.html#resources)。 

再来看看这段经反编译后的代码的输出: 

Method void main(java.lang.String[]) 
0 new #3 
3 dup 
4 ldc2_w #13 
7 invokespecial #8 
10 astore_1 
11 new #4 
14 dup 
15 sipush 1234 
18 invokespecial #9 
21 astore_2 
22 getstatic #10 
25 aload_1 
26 invokevirtual #12 
29 invokevirtual #11 
32 return 

上面的代码包含了main()函数的反编译输出。在这个方法的地址偏移量25处,虚拟机载入于偏移0到10处创建的浮点型变量的一个引用。这就是我们要修改的地方。 

下面就是经修改后的反编译代码: 

Method void main(java.lang.String[]) 
0 new #3 
3 dup 
4 ldc2_w #13 
7 invokespecial #8 
10 astore_1 
11 new #4 
14 dup 
15 sipush 1234 
18 invokespecial #9 
21 astore_2 
22 getstatic #10 
25 aload_2 
26 invokevirtual #12 
29 invokevirtual #11 
32 return 

这个类在偏移量25处的字节码是完全相同的,载入一个整型变量的引用。 
注意看看,修改后的代码仍然是安全的,这非常重要,这意味着JVM仍然将执行代码而不会崩溃或是将错误代码隔离开。然而校验器仍然能分辨出这些变化。在我的系统里,在我运行这片代码时,出现错误: 

 Exception in thread "main" java.lang.VerifyError: 
(class: Test1, method: main signature: ([Ljava/lang/String;)V) 
Incompatible object argument for function call 

如果你关掉校验器或是你找到一处虚拟机漏洞并非常规地通过了校验器的检查,那非法代码就要启动了。执行下面的命令,我接收到值:1234?整型变量值。 

java -noverify Test1 

这个列子并无多大害处,但潜在的危害确是巨大的。以上这样的技术如果同虚拟机漏洞联系起来,造成未被检查的代码得以执行,那么这将造成严重的类型混乱。 

?类型混乱 

类型的概念对java编程语言来说是浑然一体的。每个值都同一种类型相关联,JVM就是用值的类型来决定什么样的操作可以作用在什么样的值上。 

程序的类型信息对于虚拟机安全是至关重要的。一个被恶意的,未经验证的代码启动的类型混淆攻击会试图让JVM相信伪装成为一个类实列的内存块确实是是另一个类的实列,以此进行攻击。如果攻击成功,程序就会以设计者意想不到的方式来操作类实列。这种攻击称为“类型混淆攻击”,因为虚拟机已经闹不清被修改的类的类型。 

如果类经过了完全的验证,那么“类型混淆攻击”是不会发生的。在上面的第二个列表中,校验器捕获了这个企图并抛出了异常。也就是说,只要校验器没有被关闭或是被绕过,那么安全就是能够保障的。 

幸运的是,我所担心的Java字节码校验器最后的一个漏洞在1999年末被发现。基于这个事实,你可能会认为自己不会在陷入危险中,然而,这过于疏忽大意了。 

虽然漏洞越来越少,但还是有充足的机会留给狡猾的代码混入程序之中。记住,你可以手工关闭校验器检验。在接下来的文章中中,我列举出三种主要的java程序,以向大家示列在怎样的环境下关掉校验器。其中一个程序有一个重要的RMI(远程方法调用)组件(如你以后将要学到的,RMI可以让类通过网络载入到程序中,并让你的程序失控)。如果你能避免这种情况发生,就不要关掉校验器验证。 

JVM安全是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); 


你看出问题了吗? 

这个错误位于最后三行中(注释和空格除外)。首先,安全管理器检查可执行名,看其是否在配置文件中有执行的授权。接下来,代码执行命令。哎哟!在一个多线程环境中,参数数组内容在这两步之间就可以改变。由于这两个输入参数数组被直接使用,调用者仍然掌握着它们的引用,并且可以修改其内容。 

更正:立即复制输入数组并在拷贝中进行操作。 

?回到最好的练习: 

经由老式的软件开发练习,你可以发现很多导致安全问题的漏洞。清楚的需求,严格的设计核查,完整的代码核查,并通过详尽的测试可以挖出很多漏洞并进一步提高软件整体质量。

 
第三部分:创建更安全的网络程序 

概论:创建一个安全的网络程序,你需要考虑很多技术以外的东西。一个安全的解决方案除了前面讲到的虚拟机安全,应用程序安全外,还依赖于你对网络环境的理解以及程序使用者(包括那些居心叵测的用户)的使用技能。在这第三部分,Todd Sundsted安全讨论的最后一部分,他探究了这些问题并最后给出了解决方案。 

在这个安全系列讲解中,我一直鼓励你从三个不同方面来考察JAVA安全问题。在头两篇文章中,我主要集中于虚拟机和应用程序安全。这个月,我将讨论最后一部分?“网络安全”。 

网络安全处理的是互联的实体之间的通讯管道的安全问题——每个实体都确认来自另一方的身份验证以及基于通讯管道之上的信息真实性。 

创建安全的网络程序会面临很多挑战。那些构成现代安全技术(比如加密系统)的论题常常令开发人员感到迷惑。并且,一些关键的标准和技术还是不透明的——比如X.509认证标准。时至今日,网络安全还是一个难题,因为,网络媒体本身的角色就难以理解。 

看看下面这个推论:在我们日常交流中,沟通双方的信息交流通过多种媒介进行,比如光和声音。我们凭这些信息去判断他人的身份,鉴定沟通渠道的质量等。即使我们通过电话交流,我们还是可以通过对方在电话一端的语调,语速,走音等细微之处来辨别出信息,而不必通过交谈双方的谈话内容。 

而基于如互联网等媒体的通讯则只能提供少的多的线索。设想你和同伴在两间屋子使用一种通话的罐来说话。可以想像,可怜的信号质量湮灭了那些我们借以做出判断的线索。而在互联网或是其它网络上的程序通讯同这种情况是类似的。 

网络安全的内容包含了在匿名管道上进行身份验证,避免通讯信息遭到窃听以及意外或是恶意的修改等方面。 

我广泛涉猎了用于加强安全通讯的工具以及相应技术(参考 http://www.javaworld.com/javaworld/jw-08-2001/jw-0810-howto.html#resources)。这里我更实际的讨论在我们设计以及完成一个网络安全解决方案时所要考虑的事情。 

理解你自己要保护什么: 

设计一个安全解决方案你首先要从理解需求开始。通过互联网进行金融交易显然比你下载上周体育积分的WEB页面更需要安全。在选择一种技术和开始设计时,你应该考虑一下几个方面: 

• 安全设计会增加产品设计及实现的复杂性,以及安装,配置的复杂性,还有对于终端用户也是这样。尽管安全是值得的,但是复杂性则并不是用户所需要的。你打算怎样复杂化你的安全设计呢? 
• 安全处理,尤其是加密以及解密,常常都是CPU操作密集型的。多数桌面及服务器系统都有足够强的马力,但是那些缺乏资源的设备,比如蜂窝移动电话,PDAs等常常缺少这种能力。你的目标平台能适于处理这样的负荷吗? 
• 同样,安全设计会增加程序代码量。尽管在桌面以及服务器计算机上这个不算太严重的问题,但在嵌入式系统中,这就将会涉及到一些利害关系,特别是那些为JAVA2平台,比如Micro Edition (J2ME)而构建的应用,这样的情况下你又能处理好负荷吗? 

比如这里的一个列子,移动信息设备框架(MIDP)第一版,就缺少SSL(安全套接口层)支持——端到端WEB解决方案中最核心的组件——其主要原因就是对于移动设备来说,SSL太过于苯重了。针对这个问题,SUN微系统实验室的Vipul Gupta为J2ME构建了兼容SSL标准的一个实现。他的这一成果表明了小设备也可以达到让人可以接受的SSL性能。Gupta的这一实现之所以能达到这一目标,是因为它只是支持最流行的密码套件,其余的则经过了削减以适应小设备的要求,当然,这种实现缺少服务器要求的客户端验证(一种很消耗的客户端组件),并且它通过多个到服务器的连接重复使用了RSA(Rivest-Shamir-Adleman)计算。你可以看看网址: http://www.javaworld.com/javaworld/jw-08-2001/jw-0810-howto.html#resources,可以找到更多的信息。 

使用合适的技术: 

选择一种合适的技术对于实现一种网络安全解决方案来说似乎已经不用多说了。这样的选择将会影响到安全的寿命和质量。JDK提供了大量的安全API函数。下面这张表将有助你为你的方案选择合适的技术。 

• 验证和数字签名:JAVA密码系统(java.security) 
• 加密/解密和消息验证代码:JAVA密码系统扩展(javax.crypto) 
• X.509验证:JAVA密码系统(java.security.cert) 
• 可插入的验证和授权:JAVA验证和授权服务(javax.security.auth) 

JSK1.4测试版包括: 

• Kerberos:GSS(通用安全服务)API(org.ietf.jgss) 
• 证书路径确认:证书路径确认API(java.security.cert) 
这些标准API处理了大量安全相关的WEB程序以及企业整合任务的问题。 

理解网络: 

不考虑网络来进行网络安全方案设计显然是不明智的。程序所运行的系统以及基于其上的网络都将在方案设计中给予充分的考虑。 

设想一下这个情景:你建立了一个运行在安全网络之上的程序。如果网络确实是安全的——这意味着网络在物理上给保护起来,用户是可信赖的,并经过了仔细的授权——那么在这种情况下你就可以尽量少的关心你程序本身的安全问题。但是,在多数情况下,网络并非如所想像的那样安全,而且开发人员在开发安全系统中往往失败,因为不含安全机制的系统要比有安全机制的系统创建起来要难得多。然而,一些情况证实了这个方法。比如,在一个群集系统中运行了多个程序,并相互通讯,进行负载平衡,这样的情形就有足够的理由要求这个集群成为一个安全的整体,而不是程序本身。 

更实际的讲,保护你的程序典型情况下意味着评估运行程序的机器的安全,机器安装环境的安全以及网络,包括接入点。 

对接入点进行验证非常重要。对来自针对程序的恶意攻击,我们还可以设下“陷阱”,就是提供这样的一个入口,这个接入点为蛰伏的sniffer,嗅探器活动提供了一个窗口(sniffer是一种代码片断,用于侦听网络通信信息)。通过sniffer收集来的信息可以提供程序的入口。 

理解合法使用者和滥用者: 

你要记住,所谓的安全解决方案始终是围绕人来建立的,因此你必须理解程序使用者以及不怀好意者的能力和技术。那些“滥用者”,通常比正常用户更精于技术,他们让事情变得更复杂。 

在你考虑设计你的程序安全方案时,你必须要考虑那些使用者的技能水平。比如,如果你的用户坚持要把密码记在碎纸上并在监视器上敲打进去,那么你就不必开发出强大的基于密码的安全系统。正如多数人所说,通常境况下,最好的安全是花费预算来进行培训。 

对于滥用着来说,事情又有所不同。这些人常常更具有技术天赋,并更易于受到激励,这就要求更强的技术防护,即时的升级补丁包以及警醒的告诫。 

全面掌握JAVA安全: 

没有强大的网络安全,你的程序以及数据将是不稳定的。我讲过的其它的两部分——虚拟机安全,应用程序安全——和网络安全一起为JAVA程序提供了一个全面的安全图景。为了构建安全的JAVA程序,你必须全面理解以上的三个方面。 



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值