0%

JAVA反序列化有关基础知识

这篇文章主要是记录在学习Java安全过程中需要用到的重要的知识点。

Java类加载

Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的加载机制。查找并加载类的虚拟机需要完成以下三件事情:

1) 通过一个类的全限定名称在获取定义此类的二进制字节流
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3)在java堆中生成一个代表这个类的java.lang.Class对象,作为方法去这些数据的访问入口。

对于任何一个类,都需要由加载它的类加载器和这个类来确立其在JVM中的唯一ing。也就是说,两个类来源于同一个Class文件,并且被同一个类加载器加载,这两个类才相等。

类加载器

当JVM启动时,会形成由3个类加载器组成的初始类加载器层次结构。

  • Bootstrap ClassLoader: 根类加载器
  • Extension ClassLoader: 扩展类加载器
  • System ClassLoader: 系统类加载器

自定义类加载器,只需要继承java.lang.ClassLoader类,并重写其findClass()方法即可。java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节码,然后从这些字节码中定义出一个Java类,即java.lang.Class类的一个实例。ClassLoader类中的相关方法如下:

  • getParent()
  • loadClass()
  • findClass()
  • findLoadedClass()
  • resolveClass()
  • defineClass() 将字节码转换为Class对象

动态加载Jar

Java中动态加载jar的方式比较简单

1
2
URL[] urls = new URL[]{new URL("file:libs/jar1.jar")};
URLClassloader loader = new URLClassLoader(urls,parentLoader);

Javassit

Javassist是一个Java库,提供了一种操作应用程序的Java字节码的方法。Java字节码以二进制的形势存储在class文件中,每个class文件包含一个Java类或接口。

其中Javassist.CtClass类表示class文件。如何获取一个CtClass类对象呢?

首先获取一个ClassPool对象。它是CtClass对象的容器,按指定条件读取类文件来构造CtClass对象。

ClassPool

ClassPool是一个存储CtClass的Hash表,类名为key,CtClass对象为value。

获取搜索路径

ClassPool.getDfault()获取到的是JVM的类搜索路径。当存在tomcat这样的web服务时,它使用多个类加载器作为系统类加载器。在这种情况下,ClassPool需要添加额外的类搜索路径。

1
2
3
4
5
6
7
8
9
10
11
//加入某个类的路径
pool.insertClassPath(new ClassClassPath(this.getClass()));

//加入目录
pool.insertClassPath(new ClassClassPath("/usr/local/javalib"));

//加入URL
pool.insertClassPath(new URLClassPath("www.javassist.org",80,"/java/","org.javassist."));

//加入类的字节码以及类名
pool.insertClassPath(new ByteArrayClassPath(name,b));

获取CtClass对象

1)使用get()函数。可以从Hash表中查找对应的CtClass对象,若未找到则会创建并返回一个新的CtClass对象,将其保存在Hash表中。

1
CtClass cc = pool.get("test.Rectangle");

2)使用makeClass()函数。它可以返回从给定输入流构造的CtClass对象。

1
2
InputStream ins = an input stream for reading a class file;
CtClass cc = pool.makeClass(ins);

添加代码

CtConstructor提供了insertBefore()insertAfter()addCatch()方法。将Java编写的代码片段插入到现有的方法中。

1
2
String cmd = "xx";
clazz.makeClassInitializer().insertAfter(cmd);

Java反射

Class对象可以获得该类里的方法,构造器或者成员变量。

反射用来构造类的方式

  • ClassLoader.loadClass()
  • Class.forName()

获取Class对象的方法

  • Class.forName()
  • .class
  • getClass()

Java动态代理

熟悉Spring的一定知道AOP思想,AOP思想的原理就是Java的动态代理机制。
动态代理类主要涉及到两个类: java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。

InvocationHandler接口

每一个动态代理类都必须实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler。当通过代理对象调用一个方法时,这个方法的调用就会被转发为由InvocationHandler这个接口的invoke方法来进行调用。在这个invoke方法中编写调用逻辑。

看看这个InvocationHandler接口唯一的方法invoke。

1
2
3
4
5
Object invoke(Object proxy, Method, Object[] args) throws Throwable

//Porxy: 被代理的对象
//method: 被代理的对象的方法
//args: 调用被代理的对象的方法时接受的参数

举个例子
在反序列化过程中,我们会遇到AnnotationInvocationHandler这个类,它实现了InvocationHandler这个类,来看看这个类的invoke方法

和上面的一样,事实上,它实例化出来的对象就是一个handler。

Proxy类

有了动态代理类,那么如何将需要被代理的对象与这个动态代理类相关联呢?这里就要介绍到Proxy类了。Proxy类的作用是用来动态创建一个代理对象的类,其中用的最多的创建方法是newProxyInstance这个方法。来看看这个方法

1
2
3
4
5
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h)  throws IllegalArgumentException

//loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
//interfaces: 一个Interface对象的数组,表示给需要代理的对象提供一组接口,从而使得代理对象实现了该接口(多态),这样就能调用这组接口中的方法
//h: 一个InvocationHandler对象,表示当这个动态代理对象在调用方法时,会关联到哪个InvocationHandler对象上

联想到CommonsCollections1中利用的动态代理机制,可以得知,目的是为了将LazyMap中的接口关联到AnnotationInvocationHandler类实例化的InvocationHandler对象上。代码如下:

1
2
3
4
5
6
7
8
//获取LazyMap对象的ClassLoader对象
Map lazymap = LazyMap.decorate(map,chainedTransformer);
ClassLoader classloader = lazymap.getClass().getClassLoader();
Class[] interfaces = lazymap.getClass().getInterfaces();

//handler为实例化AnnotationInvocationHandler类的对象
Map mapproxy = Proxy.newProxyInstance(classloader,interfaces,handler)

这样就实现了在调用LazyMap的任何方法,都会转发到AnnotationInvocationHandler的invoke方法。从而实现了动态代理。

参考