0x01 前言
大多人学习Java安全是从反序列化漏洞开始说起,提到反序列化漏洞不可避免的要提一提反射
。反射通俗易懂的说就是操作class,通过得到类的class然后做出一些操作
例如执行命令:
1 | Class.forName("java.lang.Runtime").getMethod("exec", String.class).invoke(Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime")),"calc.exe"); |
拆开写:
1 | Class clazz = Class.forName("java.lang.Runtime"); |
可以看到要进行反射,那获取这个类是不可缺少的一步;通常来说我们有如下三种⽅式获取⼀个“类”,也就 是 java.lang.Class
对象:
obj.getClass()
: 当已经存在某个类的实例对象obj
时,可以通过调用obj.getClass()
方法来获取该对象所属类的Class
对象。1
2CodeSomeClass obj = new SomeClass();
Class<?> clazz = obj.getClass();类的
.class
属性:如果已经加载了某个类,可以直接使用该类的.class
属性来获取对应的Class
对象。1
CodeClass<?> clazz = SomeClass.class;
Class.forName()
: 如果已知某个类的名字,可以使用Class.forName()
来获取对应的Class
对象。1
javaCopy CodeClass<?> clazz = Class.forName("com.example.SomeClass");
今天我们要探究的主题就是Class.forName()
是如何通过类名(全限定类名)来获取对应的 Class
对象
在此之前我们需要先了解一下类加载
相关的知识
0x02 类加载
类加载是一个很大的话题,我们重点关注的还得是跟漏洞相关的!
在反序列化过程中,如果要将一个序列化的对象转换回原始的对象,首先需要读取字节流中的类信息(即对象的类描述),然后根据这个类信息来动态加载类,并创建对象实例。如果类信息对应的类尚未被加载,系统会触发类加载机制来加载该类,确保能够正确地实例化对象。因此,类加载在反序列化中也扮演着重要的角色。
2.1、类的生命周期
类从被加载到虚拟机内存中开始到卸载出内存为止,它的整个生命周期可以简单概括为 7 个阶段:
- 加载(Loading):当程序中使用某个类时,Java虚拟机(JVM)会使用类加载器将该类的.class文件加载到内存中。类加载的过程包括加载、链接和初始化等步骤。
- 链接(Linking):在链接阶段,会验证类中的字节码以确保其符合语言规范,并且会将类或接口的二进制表示合并到虚拟机的运行时状态中。链接又可细分为三个步骤:
- 验证(Verification):确保加载的类符合Java语言规范,不会造成安全问题。
- 准备(Preparation):为类的静态变量分配内存空间,并设置默认初始值。
- 解析(Resolution):将类、方法、字段等符号引用解析为直接引用,为后续的调用做准备。
- 初始化(Initialization):在初始化阶段,如果类具有父类,JVM会先初始化父类。然后按顺序执行静态变量的赋值操作和静态初始化块,从而完成类的初始化工作。
- 使用(Usage):在类初始化完成后,程序可以通过创建对象、调用静态方法等方式使用这个类。
- 卸载(Unloading):当一个类不再被程序所引用时,且满足一定条件(比如类加载器被回收、类型的引用已经不存在等),JVM会卸载该类,释放相关的内存空间。
2.2、类加载过程
Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚拟机是如何加载这些 Class 文件呢?
系统加载 Class 类型的文件主要三步:加载->连接->初始化。连接过程又可分为三步:验证->准备->解析。
在类加载阶段重点需要关注的就是类加载器 。类加载器有很多种,当我们想要加载一个类的时候,具体是哪个类加载器加载由 双亲委派模型 决定(不过,我们也能打破由双亲委派模型)。
每个 Java 类都有一个引用指向加载它的 ClassLoader
。不过,数组类不是通过 ClassLoader
创建的,而是 JVM 在需要的时候自动创建的。
2.3、类加载器
作用:加载.class
文件到 JVM 中(在内存中生成一个代表该类的 Class
对象)
字节码的本质就是一个字节数组 []byte
除了加载类之外,类加载器还可以加载 Java 应用所需的资源如文本、图像、配置文件、视频等等文件资源。
JVM 启动的时候,并不会一次性加载所有的类,而是根据需要去动态加载。也就是说,大部分类在具体用到的时候才会去加载,这样对内存更加友好。
对于已经加载的类会被放在
ClassLoader
中。在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。也就是说,对于一个类加载器来说,相同二进制名称的类只会被加载一次。
1)JVM 中内置了三个重要的 ClassLoader
:
- **
BootstrapClassLoader
(启动类加载器)**:最顶层的加载类,由 C++实现,通常表示为 null,并且没有父级,主要用来加载 JDK 内部的核心类库(%JAVA_HOME%/lib
目录下的rt.jar
、resources.jar
、charsets.jar
等 jar 包和类)以及被-Xbootclasspath
参数指定的路径下的所有类。 - **
ExtensionClassLoader
(扩展类加载器)**:主要负责加载%JRE_HOME%/lib/ext
目录下的 jar 包和类以及被java.ext.dirs
系统变量所指定的路径下的所有类。 - **
AppClassLoader
(应用程序类加载器)**:面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。
BootstrapClassLoader
由 C++ 实现,由于这个 C++ 实现的类加载器在 Java 中是没有与之对应的类的,所以拿到的结果是 null。
2)那些位于网络上静态文件服务器提供的 jar 包和 class文件,jdk 内置了一个 URLClassLoader
,用户只需要传递规范的网络路径给构造器,就可以使用 URLClassLoader 来加载远程类库了。URLClassLoader 不但可以加载远程类库,还可以加载本地路径的类库,取决于构造器中不同的地址形式。ExtensionClassLoader 和 AppClassLoader 都是 URLClassLoader 的子类,它们都是从本地文件系统里加载类库。
2.4、双亲委派
每个类加载器加载过的类都有一个缓存
向上委托查找,向下委托加载
2.5、类加载器的关系
Bootstrap 类加载器、Extension 类加载器和 System/Application 类加载器之间的关系如下图所示:
ExtClassLoader 和 AppClassLoder 继承 URLClassLoader,而 URLClassLoader 继承 ClassLoader,BoopStrap ClassLoder 是用 C/C++ 代码来实现的,并不继承自 java.lang.ClassLoader,它本身是虚拟机的一部分,并不是一个 Java 类。
JVM 加载的顺序:BoopStrap ClassLoder -> ExtClassLoader -> AppClassLoder
2.6、自定义类加载器
类的加载过程会使用到 findLoadedClass()、loadClass()、findClass() 等方法,ClassLoader 的 loadClass() 方法源码如下:
1 | protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { |
**loadClass():
**JVM 在加载类的时候,都是通过 ClassLoader 的 loadClass() 方法来加载 class 的,loadClass() 方法使用双亲委派模式。如果要改变双亲委派模式,可以修改 loadClass() 方法来改变 class 的加载方式。
**findClass():
**ClassLoader 通过 findClass() 方法来加载类。自定义类加载器实现这个方法来加载需要的类,比如指定路径下的文件、字节流等。
**definedClass():
**将定义的字节码文件经过字节数组流解密之后,将该字节流数组生成字节码文件,也就是该类文件的类名 .class。通常用在重写 findClass() 方法中,返回一个 Class 对象。
0x03 案例
案例入手,先准备一个Person类
1 | package com.huashu338.servlet.loderClass; |
无参构造
1 | new Person(); |
有参构造
1 | new Person("zahnsan"); |
静态方法
1 | Person.qwe(); |
静态参数
1 | Person.id=1; |
获取他的类
1 | Class c = Person.class; |
0x04 参考
https://javaguide.cn/java/jvm/class-loading-process.html
https://anye3210.github.io/2021/08/02/详解Java类加载过程/
https://henleylee.github.io/posts/2019/b7c8c167.html#toc-heading-3