第三章安全性(ChapterThree:Security)

Java是一门迎合网络需要而诞生的语言,他的各种特定性的选择都极大的倾斜于网络这一面。而网络环境意味着极大的危险性。为了保护最终用户,Sun提出了沙箱(sandbox)的概念。所有的程序必须在沙箱中执行,不能做任何沙箱不容许的事情,比如写磁盘,开进程等等。
一、沙箱(sandbox)
     一般认为,你只能运行一个被信任的程序。你必须保证从被信任的渠道获得程序,并且定期查毒,以保证获得程序是否可以被信任。但是一旦程序运行起来,他就获得了完全的控制权。如果是恶意的程序,它几乎可以做任何破坏,因为在程序运行后就没有任何限制。因此在传统的安全模式下,你必须在获得程序的时候就尽量隔离恶意代码。
    但是沙箱安全模式可以让你放心的运行未知来源的程序,就不用费劲的去判断一个程序是否可以包含恶意的程序了;也不用去执行病毒扫描,因为Java的沙箱本身就不会容许病毒或者恶意代码做任何可能具有破坏性的事。
你可以还怀疑Java的沙箱本身是否存在漏洞,是否有方法可以让开发者绕过沙箱。为了了解沙箱你必须要理解构成Java沙箱的几个基本结构:
    1、类加载器的结构(theclassloaderarchitecture)
    2、类文件的验证(theclassfileverifier)
    3、Java虚拟机和Java语言内建的安全体制(safetyfeaturesbuiltintotheJavaVirtualMachine(andthelanguage))
    4、安全管理器和JavaAPI
    其中的类加载器和安全管理器都是可订制的。通过定制这些组建你可以制定自己的安全策略。作为开发人员可能并不需要自己订制这些组件,但是你可能会常常使用到其他人制定的沙箱,比如当开发Applet时,你就会被浏览器所使用的沙箱所制约。
二、类加载器的结构(TheClassLoaderArchitecture)
    作为Java平台的第一道防线,类加载器通过两种途径实现安全策略:
    1、保护受信任的类库不受侵害
    2、从信任的代码中隔离不受信任的代码
    Java 类加载器通过防止非信任代码伪装为被信任代码,来实现保护被信任的类库。如果你一段恶意代码能够欺骗Java虚拟机,让其认为自己是被信任的 JavaAPI,那他就能够通过沙箱设置的防线。通过预防让不被信任的类伪装为被信任的类,Java的类加载器结构能够杜绝这种不安全隐患。
    Java 的类加载器结构能够通过使用不同的类加载器为被信任的代码构建一个受保护的命名空间(name-spaces)。一个命名空间是一个有Java虚拟机维护的一族不重复的被加载类。如果一个命名空间中已经加在了一个“Volcano”类,那你就不能在同一个命名空间中加在另一个“Volcano”类。但是通过创建多个命名空间来加在多个“Volcano”类。
    在Java虚拟机中,一个类只能访问同一个命名空间中的其他类。不同命名中的类甚至都不同相互察觉,除非你提供一种途径容许他们相互影响。这样即使一个含有恶意代码的类被加载到虚拟机中,他也不能够妨碍你的程序的正常执行。
    通常一个类加载器要依赖于其他的类加载器(往往事原始类加载器),以实现订制的加载工作。为了实现订制的安全策略而实现一个类加载器往往通过以下四个步骤。
    1、判断请求的类是否在一个被禁止加载的包中,如果是就抛出一个安全异常(securityexception),否则继续第二步
    2、类加载器将请求委派给原始类加载器。如果原始类加载器成功加载,就直接返回这个类,否则继续第三步
    3、如果一个要加载的在一个受信任但不容许添加类的包中,抛出一个安全异常,否则继续第四步
    4、最后类加载器尝试用自己的方法加载这个类,比如通过网络下载。如果成功就返回这个类,否则抛出(noclassdefinitionfounderror)。
通过上边的这些步骤,这个类加载器能够保护受保护的包。第一步:预防一个被禁止的包中的类被加载。第三步,禁止不被信任的类放入到被信任的包中。
三、类文件的验证器
     类文件的验证器能够保证被加载的类都有一个完整的结构,如果发现有问题她就会抛出异常。另一个需要防范的就是那些修改Class文件来实现特殊事情,比如直接跳转到方法的结尾,这可能导致Java虚拟机崩溃。为了保证Java程序的健壮性,Java虚拟机必须验证所导入的文件。虽然Java虚拟机的实现容许在任何时间完成这个验证,但是多数都是在类加载完成后进行验证。多数情况下,一起验证所有字节码,要比在每次执行之前验证更有效率。
    通常验证分为两步:第一步,检查类文件的结构完整性和字节码的正确性,通常在加载完后进行检查。第二步,检查所有引用、变量、方法是否存在,通常在执行前进行检查。
    1、第一步:内部检查(InternalChecks)
    在第一步中,检查器通过检查类文件本身检查所有能够检查的东西。此外还检查字节码完整性,类文件的结构和内部的一致性,比如文件开头的“0xCAFEBABE”。
    类检查器也检查文件没有被删除或者增加过。类文件中保存了类文件长度的信息,这个信息让文件长度的检查成为可能。
    类检查器也检查每个小的组件是否具有完好的定义,比如方法的声明。类检查器在运行时也检查了哪些在编译时检查的语言特性。
     当类检查器检查完这些结构完整性和内容一致性(properformatandinternalconsistency)之后,会进行字节码验证(bytecodeverifier)。字节码将Java的方法描述为一串称为操作码(opcodes)的单字节的指令。每一个操作码都可能跟着一个或者多个操作数(operands)。操作数提供了Java虚拟机执行操作码必须的数据。一个接一个连续执行的操作码构成了Java虚拟机的线程。每个线程都有自己的Java堆栈(JavaStack),堆栈是由一些不连续的块(frames)组成的。每一个方法的调用都会获得自己的块,用于保存本地变量和计算的中间变量。块中保存中间变量的区域被称为操作数堆栈(operandstack)。一个操作码和他的操作数可能依赖于存储于操作数堆栈或者方法块中的本地变量。
    字节码检查器作了非常多的检查。他保证无论程序在那里执行,她总能够取得确定的操作码,操作数堆栈总是包含相同类型和数的项目。他保存在变量被初始化之前不能够被访问。他保证类中的字段总是被绑定合适的类型,类中的方法被调用时,总是被赋予合适得参数。字节码检查器也检查每个操作码是否可用,是否有合适的操作数,是否有合适的本地变量和操作数堆栈。这仅仅是所有检查中的一部分,所有的检查执行后,就能够保存所有的字节码能够被 Java虚拟机安全的执行。
    第一部分的检查,确保了类文件有合适的格式,统一的内容,受到Java语言规则的约束,并且字节码能够被Java虚拟机安全的执行。如果发现了任何问题或者不确定的因数,他都会抛出一个错误(error),并且这个类文件不会被程序使用。
    2、第二步:符号引用的检查(VerificationofSymbolicReferences)
     第一步的检查在类被Java虚拟机加载后立即执行,而第二步检查被延迟到包含字节码的类真正被执行时。在第二部分的检查中,Java虚拟机跟着类文件中的引用去检查被引用的类文件,以确保类引用的正确性。因为第二步检查往往需要加载其他的类,所有多数提供商在字节码实际被执行时,才执行检查。
     第二步的检查也是动态连接(dynamiclinking)的一部分。符号引用是一些包含命名和其他可能信息的字符串。为了确定被引用的项目,引用类的符号引用必须包换类的全名;引用其他类的成员变量的类必须提供类名,变量名和变量的描述符;引用方法的必须提供类名、方法名和方法的描述符。
动态连接的过程也是将符号引用转换为直接引用的过程。Java虚拟机通过两个基本步骤来完成这种转换:
    1)、找到被引用的类,必要时加载
    2)、讲符号引用替换为字节引用,比如类、成员变量、方法的指针或者偏移
    Java虚拟机纪录这些直接引用,因此当再次使用这些引用时,它能够直接使用这个直接引用,而不用再去转换字符引用。如果在转换过程中,发现被引用的类不可用,那么检查器会抛出一个错误。
    3、二进制兼容性
     前面两个检查仅仅是根据类文件来实行检查,但是没有保证类文件是兼容得。因为Java程序是动态连接的,所以不排除在对关联的源文件的修改而导致的二进制不兼容(binarycompatibility)。Java的编译器经常从新编译那些修改后关联的其他类文件。
    当你删除,修改类中的方法时都会破坏类的二进制兼容性,添加方法不会。
四、Java虚拟机内建的安全机制
    Java程序在执行时除了上边所说的检查之外,还会做如下的检查:
    1、类型安全检查
    2、结构化的内存访问(没有指针运算符)
    3、自动垃圾收集(不一定总是能够释放已分配的内存)
    4、数组边界检查
    5、引用的非Null检查
     禁止直接内存访问为Java程序保证了程序由足够的健壮性。首先,能够直接访问内存的程序,往往导致混乱的内存、崩溃甚至其他程序的崩溃,这些都是重大的安全问题。这种层次的健壮性在嵌入式设备中同样重要,因为谁也不想去重起一个电话。其次,直接的内存访问也给了黑客一种盗取、破坏信息的机会。所以强制禁止直接内存访问,即增强了程序了健壮性,也阻扰了黑客可能的阴谋。
    没有明确的内存结构也是Java虚拟机内建安全机制的重要方面,也是禁止直接内存访问的重要补充。运行时内存空间是指那些被Java虚拟机用来保存运行程序必要数据的内存区域,包含:Java堆栈(每个线程都有自己的),一个方法区(保存字节码的区域)和一个垃圾收集堆(用于保存新建对象得区域)。Java的类文件中没有保存任何地址信息,Java虚拟机总是在加载一个类的时候才决定将字节码和对应的其他解析信息存放到何处。Java虚拟机总是在开始一个线程是才决定将在哪里存放Java堆栈,在新建一个对象的时候才去决定在哪里存放这个对象。这样黑客们就没有希望在内存中找到特定类信息的地址,因为每一次执行可能都会使用不同的内存地址。Java虚拟机的内存组织不是规范的一部分,Java虚拟机的设计者决定如果组织自己的内存。这样即使黑客搞清楚了一台Java虚拟机内存的组织方式,那他在面对另一个平台时就必须重新来过。
    虽然通过字节码不能够作什么坏事,但是本地方法可以。执行本地方法时就跳出了沙箱,不再受安全管理器的控制,他可以做任何事。但是安全管理器包含了一条规则:程序是否可以加载动态连接库(dynamiclibrary)。因为调用本地方法必须加载新的动态连接库。比如不可靠的 Applet就不能够加载动态连接库。当一个线程调用本地方法时,他就跳出了Java的沙箱,Java的安全模型也退化为传统的安全模型:运行前必须要确保他的安全性。
Java虚拟机内间的最后的安全屏障就是结构化的错误处理(structurederrorhandlingwithexceptions)。通过错误处理,即使程序抛出了一个异常甚至错误,程序也没有必要崩溃。抛出的错误(error),往往导致一个线程的终止,但是程序中的其他线程可能在做一些其他的事情,所以程序也没有必要就崩溃掉。
六、安全管理器和JavaAPI
     通过使用类加载器,可以在Java虚拟机中分割不同的命名空间,以保护命名空间之间不要相互影响。但是要想保存Java虚拟机以外的资源就需要使用安全管理器。安全管理器定义了沙箱的边界。通过定制安全管理器你可以实现程序自己的安全策略。JavaAPI作为任何可能导致不安全因素的动作之前都会去安全管理器申请许可。对应每个不安全的方法,安全管理器也提供了相应的方法判断是否容许执行。这些方法都已“check”开头,比如checkRead()、 checkWrite()。
通常需要检查的方法有:
    1)、checkConnect(Stringhost,intport)、checkConnect(Stringhost,intport,Objectcontext):从指定主机和端口接收Socket连接
    2)、checkAccept(Stringhost,intport):打开一个到指定主机和端口的Socket连接
    3)、checkAccess(Threadt)、checkAccess(ThreadGroupt):改变线程状态(改变优先级,终止等等)
    4)、checkListen(intport):监听指定端口,等待连接
    5)、checkMulicast(InedAddressmaddr)、checkMulticast(InedAddressmaddr,bytett):加入、离开、发送或者接受IP组播前被调用
    6)、checkSetFactory():设置被ServerSocket或Socket使用的socket类或者设置被URL使用的URL流处理器前调用
    7)、checkExit():终止程序
    8)、checkLink():加载包换本地方法的动态连接库
    9)、checkCreateClassLoader():创建一个新的类加载器
    10)、checkPackageAccess(Stringpkg):访问指定包中的类(类加载器用)
    11)、checkPackageDefinition(Stringpkg):在指定的包中添加类(类加载器用)
    12)、checkPropertiesAccess():访问和修改一般的系统属性
    13)、checkPropertiesAccess(Stringkey):访问指定的系统属性
    14)、checkRead(Stringfile)、checkRead(Stringfile,Objectcontext):读指定文件
    15)、checkWrite(FileDescriptorfd)、checkWrite(Stringfile):写指定文件
    16)、checkDelete(Stringfile):删除指定文件
    17)、checkTopLevelWindow(ObjectWindow):不出示任何警告的显示指定的窗口前被调用
    18)、checkPrintJobAccess():初始化一个打印任务请求前被调用
    19)、checkSystemClipboardAccess():访问系统剪切板前被调用
    20)、checkAWTEventQueueAccess():访问AWT事件队列前被调用
    21)、checkMemberAccess():通过映像API访问类信息前被调用
这些就是所谓的老式的验证方法,具体信息请参照java.lang.SecurityManager的JavaDoc。在版本1.2以后,定义了一些许可类,通过这些类可以判断它的操作是否被容许。在SecurityManager中也添加了两个新的方法:
    1)、checkPermission(Permissionperm):进行某个操作前被调用。
    2)、checkPermission(Permissionperm,Objectcontext):在传递的安全上下文中进行某个操作(需要的权限)前被调用
     因为所有JavaAPI在执行前总是检查安全管理器是否容许,所以通过定制安全策略能够禁止JavaAPI执行特定的操作。但是也有两个可能导致危险操作,但是没有被列在上面的,他们是:分配内存,打开新的线程。他们可能导致:一直分配内存直到崩溃,一直打开新的线程降低系统的速度。这种攻击被称为拒绝服务攻击(denialofservice),因为它能够妨碍用户妨碍使用自己计算机。安全管理器不可能阻止所有可能冒犯或者麻烦最终用户。安全管理器一旦安装,在程序执行后就不能替换,扩展和修改。
    虽然你只能为你的程序安装一个安全管理器,但是你可以定义自己安全策略。此外,安全管理器还提供了方法可以判断请求是来至那个类加载器的。这样可以让我们给不同类加载器使用不同的安全策略。这是对待那些不明来历代码的最好方法。
七、认证(Authentication)
    Java1.1中的java.security包中引入了认证的概念,通过认证你能够针对不同的程序提供者实现不同的安全策略。
下面是加密过程是解密过程的示意图:
八、技术范畴外的安全策略
     安全是一种投资和风险之间的平衡。你投入越多越安全,但是安全是否值得就要看自己的决定了。但是当你发生下列事情时,那么所有的安全策略都会失效:直接下载并运行程序,走得时候没有关门,丢弃文件的时候没有粉碎,你请的助手是间谍。实际生活中的安全不是通过技术就能够完全解决的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值