Java反序列化漏洞基础教学文档
第一章:序列化与反序列化基础
1.1 核心概念
- 序列化: 将内存中的Java对象转换为字节流的过程。这个过程可以将对象的状态(数据)保存到文件、数据库,或通过网络进行传输。
- 反序列化: 将字节流还原为内存中Java对象的过程。它是序列化的逆过程。
1.2 技术存在的意义
序列化与反序列化主要用于数据传输和持久化,常见的应用场景包括:
- 将对象状态保存到文件或数据库中。
- 通过网络套接字传输对象。
- 在RMI(远程方法调用)中传输对象。
1.3 常见的序列化协议
除了Java原生的序列化机制,还存在其他协议:
- XML
- SOAP: 基于XML的结构化消息传递协议。
- JSON
- Protobuf(Google Protocol Buffers)
1.4 Java中的序列化实现
-
实现
Serializable接口: 任何需要被序列化的类都必须实现java.io.Serializable接口。这是一个标记接口,不包含任何方法。import java.io.Serializable; public class Person implements Serializable { private String name; private int age; // ... 构造方法、getter/setter、toString ... } -
序列化过程: 使用
ObjectOutputStream将对象写入字节流。// 写法一:传统IO ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(person); // 写法二:NIO(现代推荐) ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("ser.bin"))); oos.writeObject(person); -
反序列化过程: 使用
ObjectInputStream从字节流中读取对象。ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get("ser.bin"))); Person person = (Person) ois.readObject();
第二章:反序列化漏洞的根源
2.1 核心安全问题
根本原因: Java反序列化机制在还原对象时,会自动调用特定方法(如readObject)。如果攻击者能够控制反序列化的数据源,并精心构造一个恶意的字节流,就可以诱使服务端在执行反序列化过程中执行任意代码。
2.2 漏洞触发点(攻击面)
文章指出了四种典型的漏洞触发模式:
-
入口类的
readObject方法直接调用危险方法- 描述: 如果自定义类重写了
readObject方法,并在其中直接调用了危险代码(如命令执行),那么反序列化该类的对象时就会触发漏洞。 - 示例:
public class Person implements Serializable { private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); // 调用默认反序列化 Runtime.getRuntime().exec("calc"); // 危险操作! } }
- 描述: 如果自定义类重写了
-
入口类参数中包含可控类,该类有危险方法,
readObject时调用- 描述: 入口类(如
HashMap)的readObject方法在反序列化过程中,会对其中的键值对进行某些操作(如计算哈希值hashCode())。如果键是攻击者可控的类(如URL),并且该类的hashCode或equals等方法存在危险行为,漏洞就会被触发。 - 示例:
URLDNS链就是利用这一点。反序列化一个HashMap<URL, Integer>时,HashMap.readObject()会调用URL.hashCode(),而URL.hashCode()会触发一次DNS查询,从而验证反序列化是否发生。
- 描述: 入口类(如
-
入口类参数中包含可控类,该类又调用其他有危险方法的类
- 描述: 这是上一种情况的延伸,构成一条调用链(Gadget Chain)。入口类调用可控类A的方法,类A的方法又调用类B的危险方法。
-
构造函数/静态代码块等类加载时隐式执行
- 描述: 即使不直接调用
readObject,在类被加载时,其静态代码块或构造函数也会被执行。如果这些代码块中包含危险逻辑,同样可以构成攻击。 - 示例: 重写
toString方法,在其中执行命令,当反序列化后的对象被打印(隐式调用toString)时触发。
- 描述: 即使不直接调用
2.3 挖掘漏洞的三个关键条件
要成功利用一个反序列化漏洞,需要同时满足三个条件:
-
入口类(Source):
- 可序列化。
- 重写了
readObject方法。 - 该方法中调用了常见的函数(如
put,equals,compare等)。 - 方法的参数类型宽泛(如
Object,Map等),允许传入任意对象。 - 最好是JDK自带的类,因为攻击载荷的通用性更强。
-
执行类(Sink):
- 一个可以执行危险操作的点,如:
- RCE:
Runtime.exec(),ProcessBuilder.start() - SSRF:
URL.openConnection() - 文件写入:
FileOutputStream.write()
- RCE:
- 一个可以执行危险操作的点,如:
-
调用链(Gadget Chain):
- 一条从入口类的
readObject方法到执行类的危险方法的完整方法调用链。这条链由多个类的不同方法连接而成。
- 一条从入口类的
第三章:前置知识 - Java反射
3.1 反射的概念
反射机制允许程序在运行时(而非编译时)探查、获取并操作类(Class)、方法(Method)、字段(Field)等信息。它赋予了Java动态语言的能力。
- 正射: 在编译时就知道要操作的类和具体方法。
Student student = new Student(); // 编译时类型已知 student.sayHello(); - 反射: 在运行时才动态地加载类、创建对象、调用方法。
Class clazz = Class.forName("com.example.Student"); // 运行时根据字符串加载类 Object instance = clazz.newInstance(); Method method = clazz.getMethod("sayHello"); method.invoke(instance);
3.2 反射的核心API
反射中几个极为重要的方法:
Class.forName(String className): 根据类名获取该类的Class对象。clazz.newInstance(): 通过Class对象创建类的实例(调用无参构造)。clazz.getMethod(String name, Class... parameterTypes): 获取方法对象。method.invoke(Object obj, Object... args): 调用指定对象的方法。
第四章:经典漏洞链分析 - URLDNS
4.1 链分析
URLDNS是ysoserial工具中的一个Gadget Chain,它不直接执行命令,而是用于检测目标是否存在反序列化漏洞,因为它会产生一个DNS请求,非常直观。
调用链:
HashMap.readObject() -> HashMap.put() -> HashMap.hash() -> URL.hashCode() -> URLStreamHandler.hashCode() -> URLStreamHandler.getHostAddress() -> InetAddress.getByName() (触发DNS查询)
4.2 复现关键点
- 构造Payload: 创建一个
HashMap,并放入一个URL对象作为key。 - 避免提前触发DNS: 在放入
HashMap之前,需要利用反射将URL对象的hashCode字段设置为非-1的初始值,以防止在put操作时就触发DNS查询。真正的漏洞触发应在反序列化时的readObject中。 - 序列化与反序列化: 将构造好的
HashMap序列化成字节流,然后让目标程序反序列化该流。
4.3 学习意义
URLDNS链是学习Java反序列化的最佳入门案例,因为它:
- 只依赖JDK内置类,通用性极高。
- 不涉及复杂的第三方库。
- 清晰地展示了从
readObject到最终触发网络请求的完整链条。 - 无害,仅用于探测。
第五章:从探测到利用 - 命令执行(RCE)
URLDNS链仅用于探测。要实现真正的命令执行(RCE),需要寻找更复杂的Gadget Chain,其最终会调用Runtime.exec()或类似方法。
核心思路: 寻找一条从某个可序列化入口类(如AnnotationInvocationHandler, BadAttributeValueExpException等)的readObject方法出发,最终能够动态调用到Runtime.getRuntime().exec("命令")的调用链。这通常需要结合反射和动态代理等机制来绕过各种限制。
由于这些链通常涉及多个第三方库(如Commons-Collections),构造起来比URLDNS复杂得多,但基本原理相通:控制反序列化数据,引导程序执行预设的恶意代码路径。
总结
本教学文档系统性地梳理了Java反序列化漏洞的基础知识。理解这些概念是进一步学习复杂Gadget Chain(如CommonsCollections、Fastjson等)的基石。核心要点在于:反序列化本身不是漏洞,但将不可信的数据交给反序列化机制处理,且程序中存在一条从入口点到危险操作的完整调用链,就构成了严重的安全漏洞。