安全

来源:互联网 发布:淘宝助理怎么合并订单 编辑:程序博客网 时间:2024/05/16 00:57

Java体系结构采用了一个扩展的内置安全模型

Java的安全模型是其多个重要结构特点之一,它使Java成为适用于网络环境的技术。

Java安全模型侧重于保护终端用户免受从网络下载的、来自不可靠来源的、恶意程序(以及善意程序中的bug)的侵犯。


为了达到这个目的,Java提供了一个用户可配置的“沙箱”,在沙箱中可以放置不可靠的Java程序。

沙箱对不可靠程序的活动进行了限制,程序可以在沙箱的安全边界内做任何事,但是不能进行任何跨越这些边界的举动。例如:

1.对本地硬盘的读写操作

2.进行任何网络连接,但不能连接到提供这个applet的源主机,

3.创建新的进程

4.装载新的动态连接库


签名和认证使得接收端系统可以确认一系列class文件(在一个JAR文件中)已经由某一实体进行了数字签名(有效,可被信赖),并且在签名过后,这些class文件没有改动。

虽然版本1.1中发布的安全API包含了对认证的支持,但实际上,除了提供完全信任和完全不信任策略以外,没有提供许多实际的帮助。

版本1.2提供的API可以帮助建立细粒度的安全策略,这种策略是在数字签名代码的认证基础上的。


基本沙箱

沙箱模式使你可以接收来自任何来源的代码,而不是要求用户避免将来自不信任站点的代码下载到机器上。
但是当来自不可靠来源的代码运行时,沙箱会限制它进行可能破坏系统的任何动作。
不必指出哪些代码可以信任,哪些代码不可以信任,也不必扫描查找病毒,沙箱本身限制了下载的任何病毒或其他恶意的、有漏洞的代码,使得他它们不能对计算机进行破坏

组成Java沙箱的基本组件如下:
类装载器结构
class文件检验器
内置于Java虚拟机(及语言)的安全特性
安全管理器及Java API

Java的沙箱安全模型中的类装载器和安全管理器是可以由用户定制的。

类装载器体系结构

在Java沙箱中,类装载器体系结构是第一道防线。
1.它防止恶意代码去干涉善意的代码
2.它守护了被信任的类库的边界
3.它将代码归入某类(成为保护域),该类确定了代码可以进行哪些操作

防止恶意代码去干涉善意的代码,通过为不同的类装载器装入的类提供不同的命名空间来实现的。
命名空间由一系列唯一的名称组成,每一个被装载的类有一个名字,这个命名空间是由Java虚拟机为每一个类装载器维护的。

命名空间有助于安全的实现,有效地在装入了不同命名空间的类之间设置一个防护罩。
在Java虚拟机中,在同一个命名空间内的类可以直接进行交互,而不同的命名空间中的类甚至不能觉察到彼此的存在,除非显示的提供了允许它们进行交互的机制。

类装载器体系结构守护了被信任的类库的边界,这是通过分别使用不同的类装载器装载可靠的包和不可靠的包来实现的。

虽然通过赋给成员受保护(或包访问)的访问限制,可以在同一个包中的类型间授予彼此访问的特殊权限,但这种特殊的权限只能授给在同一个包中的运行时成员,而且它们必须是由同一个装载器装载的。


用户自定义类装载器经常依赖其他类装载器--至少依赖于虚拟机启动时创建的启动类装载器-来帮助它实现一些类装载请求。

在版本1.2以前,非启动类装载器必须显示地求助于其他类装载器,类装载器可以请求另一个用户自定义的类装载器来装载一个类,这个请求是通过对被请求的用户自定义类装载器调用loadClass()来实现的,也可以通过调用findSystemClass()来请求启动类装载器来装载类,这是类ClassLoader中的一个静态方法。

在版本1.2中,类装载器请求另一个类装载器来装载类型的过程被形式化,称为双亲委派模式。

从版本1.2开始,除启动类装载器以外的每一个类装载器,都有一个“双亲”类装载器,在某个特定的类装载器试图以常用方式装载类型以前,它会先默认地将这个任务“委派”给它的双亲--请求它的双亲来装载这个类型。这个双亲再依次请求它自己的双亲来装载这个类型。这个委派的过程一直向上继续,直到达到启动类装载器,通常启动类装载器是委派链中的最后一个类装载器。如果一个类装载器的双亲类装载器有能力来装载这个类型,则这个类装载器返回这个类型。否则,这个类装载器试图自己来装载这个类型。


在版本1.2以前的大多数虚拟机的实现中,内置的类装载器(原始类装载器)负责在本地装载可用的class文件。

这些class文件通常包括哪些要运行的Java应用程序的class文件,以及这个应用程序所需要的任何类库,这些类库中包含Java API的基本class文件。

许多实现都是按照类路径(class path)指明的顺序查找目录和JAR文件


在版本1.2中,装载本地可用的class文件的工作被分配到多个类装载器中,原始类装载器的内置的类型装载器被重新命名为启动类装载器,表示它现在只负责装载那些核心Java API的class文件,核心Java API的class文件是用于“启动”Java虚拟机的class文件。


在版本1.2中,由用户自定义类装载器来负责其他class文件的装载,例如用于应用程序运行的class文件,用于安装或下载标准扩展的class文件,在类路径中发现的类库的class文件等。当1.2版本的Java虚拟机开始运行时,在应用程序启动以前,它至少会创建一个用户自定义类装载器,也可能创建多个。

所以这些类装载器被连接在一个双亲-孩子的关系链中,关系链的顶端是启动类装载器,关系链的末端是“系统类装载器”,有时也被成为原始类装载器。

系统类装载器,指由Java应用程序创建的、新的用户定义类装载器的默认委派双亲,它装载应用程序的初始类。


Java虚拟机只把彼此访问的特殊权限授予由同一个类装载器装载到同一个包中的类型。

运行时包,它指由同一个类装载器装载的、属于同一个包的、多个类型的集合。

启动类装载器装载核心Java API的class文件,这些class文件是最可信任的。

已安装扩展的类装载器装载来自于任何已安装扩展的class文件,这个也是可信的。

由类路径装载器中发现的代码不能访问已安装扩展或Java API中的包内可见成员。


类装载器可以简单地拒绝装载特定的禁止类型就可以了。


类装载器必须将每一个被装载的类放置在一个保护域中,一个保护域定义了这个代码在运行时将得到怎样的权限。


class文件检测器

class文件实质上是一个字节序列,class文件检验器的实现的目标之一就是程序的健壮性。

Java虚拟机的class文件校验器在字节码执行之前,必须完成大部分检验工作。

它只在执行前对字节码进行一次分析(并检验它的完整性),每一次遇到一个跳转指令时都进行检验。

虚拟机将确认所有的跳转指令会达到另一条合法的指令,而且这条指令是在这个方法的字节流中的。


class文件检验器要进行四趟独立的扫描来完成,

第一趟 是在类被装载时进行的,检查这个class文件的内部结构,以保证它可以被安全地编译。

第二趟+第三趟是在连接过程中进行的,确认类型数据遵从Java编程语言的语义,包括校验它所包含的所有字节码的完整性。

第四趟 是在进行动态连接的过程中解析符号引用时进行的,确认被引用的类、字段以及方法确实存在。


第一趟:class文件的结构检查

对每一段将被当作类型导入的字节序列,class文件检验器都会确认它是否符合Java class文件的基本结构。

例如每个class文件必须以4个同样的字节开始:魔数0xCAFEBABE。这个魔数的用处是让class文件分析器很容易分辨出某个文件有明显问题而加以拒绝。

校验器还必须确认在class文件中声明的主版本号和次版本号,这个版本号必须在这个Java虚拟机实现可以支持的范围以内。


class文件检验器 必须检验确认这个class文件没有被删节,尾部也没有附带其他的字节。

class文件中包含的每一个组成部分都声明了它的长度和类型,校验器可以使用组成部分的类型和长度来确定整个class文件的正确的总长度,这样来检查一个装入的文件 其长度是否和它里面的内容相一致。


第一趟扫描的主要目的就是保证这个字节序列正确地定义了一个新类型,它必须遵从Java的class文件的固定格式,这样才能被编译成在方法区中的(基于实现的)内部数据结构。第二、第三、第四趟扫描 不是在符合class文件格式的二进制数据上进行的,而是在方法区中、由实现决定的数据结构上进行的。


第二趟:类型数据的语义检查

在第二趟扫描中,class文件校验器进行的检查 不需要查看字节码,也不需要查看和装载任何其他类型。

在这趟扫描中,校验器查看每个组成部分,确认它们是否是其所属类型的实例,它们的结构是否正确。

例如,方法描述符(它的返回类型,以及参数的类型和个数)在class文件中被存储成一个字符串,这个字符串必须符合特定的上下文无关文法。

校验器对每个组成部分进行检查,为了确认每个方法描述符都是符合特定语法的、格式正确的字符串。


class文件校验器检查这个类本身是否符合特定的条件,它们是由Java编程语言规定的。

例如,检验器强制除object类以外的所有类,都必须有一个超类。

校验器还要检查final类没有被子类化,而且final方法没有被覆盖。

还要检查常量池中的条目是合法的,而且常量池的所有索引必须指向正确类型的常量池条目。

class文件检验器在运行时检查了一些Java语言应该在编译时遵守的强制规则。

第三趟:字节码验证

在class文件校验器成功地进行了两趟检查后,它将把注意力放在字节码上,

Java虚拟机对字节流进行数据流分析,这些字节流代表的是类的方法。


字节码流代表了Java的方法,它是由被称为操作码的单字节指令组成的序列,每一个操作码后跟着一个或多个操作数。

操作数用于在Java虚拟机执行操作码指令时提供所需的额外数据。

执行字节码时,依次执行每个操作码,这就在Java虚拟机内构成了执行的线程。

每一个线程被授予自己的Java栈,这个栈是有不同的栈帧构成的。每一个方法调用将获得一个自己的栈帧--栈帧其实就是一个内存片段,其中存储着局部变量和计算的中间结果 

在栈帧中,用于存储方法的中间结果的部分被称为该方法的操作数栈。操作码和它得(可选的)操作数可能存储在操作数栈中的数据,或存储在方法栈帧中的局部变量中的数据。

这样,在执行一个操作码时,除了可以使用紧随其后的操作数,虚拟机还可以使用操作数栈中的数据,或局部变量中的数据或两者都用。


字节码检验器要进行大量的检查,以确保采用任何路径在字节码流中都得到一个确定的操作码,确保操作数栈总是包含正确的数值以及正确的类型。

它必须保证局部变量在赋予合适的值以前不能被访问,而且类的字段中必须总是被赋予正确类型的值,类的方法被调用时总是传递正确数值和类型的参数。

它必须保证每一个操作码都是合法的,即每一个操作码都有合法的操作数,以及对每一个操作码,合适类型的数值位于局部变量中或是在操作数粘中。


在第一、第二、第三扫描中,class文件校验器可以保证导入的class文件构成合理,内在一致,符合Java编程语言的限制条件,并且包含的字节码可以被Java虚拟机安全的执行。

第四趟:符号引用的验证

Java虚拟机将追踪哪些引用--从被验证的class文件到被引用的class文件,以确保这个引用是正确的。

必须检查被检测的class文件以外的其他类,所以需要装载新的类。

大多数Java虚拟机的实现采用延迟装载类的策略,直到类真正地被程序使用时才装载。

即使一个实现确实预先装载了这些类,为了加快装载过程的速度,还是会表现为延迟装载。


class文件检验器的第四趟扫描仅仅是动态连接过程的一部分,

当一个class文件被装载时,它包含了对其他类的符号引用以及它们的字段和方法。

一个符号引用是一个字符串,它给出了名字,并且可能还包含了其他关于这个被引用项的信息--这些信息必须足矣唯一地识别一个类、字段或方法

对于其他类的符号引用 必须给出这个类的全名;

对于其他类的字段的符号引用必须给出类名、字段名以及字段描述符;

对于其他类中的方法的引用必须给出类名、方法名、方法的描述符


动态连接是一个将符号引用解析为直接引用的过程

当Java虚拟机执行字节码时,如果它遇到一个操作码,这个操作码第一次使用一个指向另一个类的符号引用,那么虚拟机就必须解析这个符号引用。

在解析时,虚拟机执行两个基本任务:

1.查找被引用的类(如果必要的话就装载它)

2.将符号引用替换为直接引用,例如一个指向类、字段或方法的指针或偏移量


虚拟机必须记住这个直接引用,这样当它以后再次遇到相同的引用时,它就可以立即使用这个直接引用


当Java虚拟机解析一个符号引用时,class文件校验器的第四趟扫描确保了这个引用是合法的。

当这个引用是个非法引用时--例如,这个类不能被装载,或这个类的确存在,但是不包含被引用的字段或方法--class文件校验器将会抛出一个错误


如果Volcano类中的某个方法调用了名为Lava的类中的某个方法,这个Lava中的方法的全名和描述符将包含在Volcano的class文件的二进制数据中。

当Volcano的方法在执行过程中第一次调用Lava的方法时,Java虚拟机必须确认类Lava中存在这个方法,并且这个方法的名字和描述符与Volcano中期待的相匹配。

如果这个符号引用(类名、方法名、描述符)是正确的,那么 虚拟机将把他替换为一个直接引用,例如一个指针,从那时开始将使用这个指针。

但如果Volcano类中的符号引用不能匹配Lava类中的任何方法时,第四趟扫描验证失败,Java虚拟机将抛出一个NoSuchMethodError

二进制兼容

正因为Java程序是动态连接的,所以class文件校验器在第四次扫描中,必须检查相互引用的类之间是否兼容。


在一个类中,哪些可以被修改、增加和删除,而并不破坏这个被修改的类与依赖它的那些事先已存在的类之间的二进制兼容性。

例如,向一个类中增加一个新的方法始终是一个影响二进制兼容性的改动,但是不能删除一个正在被其他类使用的方法。

Java虚拟机内置的安全特性

Java虚拟机装载一个类,并且对它进行了第一到第三趟的class文件检验,这些字节码就可以被运行。

除了对符合引用的校验(class文件校验的第四趟扫描),Java虚拟机在执行字节码时还进行一些内置的安全机制的操作。

这些机制大多数是Java的类型安全的基础。

1.类型安全的引用转换

2.结构化的内存访问(无指针算法)

3.自动垃圾收集(不必显示地释放被分配的内存)

4.数组边界检查

5.空引用检查

通过保证一个Java程序只能使用类型安全的、结构化的方法去访问内存,Java虚拟机使得Java程序更为健壮。


Java虚拟机并未指明运行时数据空间在Java虚拟机内部是如何分布的。

运行时数据空间是指一些内存空间,Java虚拟机用这些空间来存储一个Java程序时所需要的数据:

Java栈(每个线程一个)、

一个存储字节码的方法区,

以及一个垃圾收集堆(它用来存储由运行的程序创建的对象)


查看一个class文件的内部,将找不到任何内存地址。

当Java虚拟机装载一个class文件时,由它决定将这些字节码以及其他从class文件中解析得到的数据放置在内存的什么地方。

当Java虚拟机启动一个线程时,由它决定将它为这个线程创建Java栈放到哪里。

当它创建一个新的对象时,也是由它决定这个对象放到内存中什么地方。

对每个Java虚拟机的实现来说,由它的设计者来决定使用什么数据结构来表示运行时数据空间,并且将它们放在内存的哪个位置。


禁止对内存进行非结构化访问,是字节码指令集本身的内在本质。


本地方法没有经过Java API,所以当它视图做些具有破坏性的动作时,安全管理器并未检查。

安全管理器中包含一个方法,用来确定一个程序是否能装载动态连接库,因为在调用本地方法时 动态连接库是必须的。

在调用本地方法前必须确认它是可信任的。


异常的结构化处理。因为Java虚拟机支持异常,所以当一些违反安全的行为发生时,它会做出一些结构化处理,Java虚拟机将抛出一个异常或一个错误,而不是崩溃。

这个异常或错误导致这个错误线程的死亡,而不是使整个系统陷入瘫痪

抛出一个错误 总是导致抛出错误的这个线程死亡,这对一个运行的Java程序来说是一个不便因素,但它不会导致整个程序的中止。

如果这个程序还有一些线程正在正常工作,则这些线程有可能继续正常工作,即使它的同伴已经死亡。

而抛出y一个异常可能导致这个线程的死亡,但是他经常作为一个手段被使用,使程序能够将控制从发生异常的地方转到处理这个异常的情况。


安全管理器和Java API

它主要用于保护虚拟机的外部资源不被虚拟机内运行的恶意或有漏洞的代码侵犯。
安全管理器定义了沙箱的外部边界。
因为它是可定制的,所以它允许程序建立自定义的安全策略。
当Java API进行任何可能不安全的操作时,它都会向安全管理器请求许可,从而强制执行自定义的安全策略。
要向安全管理器请求许可,Java API将调用安全管理器对象的"check"方法(因为这些方法名都是以"check"开头)。
例如,安全管理器的checkRead()方法决定了线程可否读取一个特定的文件,
checkWrite()方法决定了线程能否对一个特定的文件进行写操作。
这些方法的实现定义了应用程序的定制安全策略。

Java API在进行一个可能不安全的操作前,总是检查安全管理器。

当java应用程序启动时,它还没有安全管理器,但是,应用程序通过将一个指向java.lang.SecurityManager或是其子类的实例传给setSecurityManager(),依次来安装安全管理器,这个动作是可选的。

如果应用程序没有安装安全管理器,那么它就不会对请求Java API的任何动作做限制--Java API将做任何被请求的事(这就是Java应用程序在默认情况下将不会有任何安全限制的原因)

如果应用程序确实安装了 安全管理器,那么它将负责应用程序整个剩余的生命周期,它不可被替代、扩展或者修改。
Java API将只执行那些被安全管理器同意的请求。

0 0
原创粉丝点击