1、概念
1.1、序列化
序列化就是将内存对象转换为字节流的过程(使用transient关键字可以不让字段被序列化)。
1.1.1、jdk原生反序列化
jdk原生的反序列化指的是java自带的二进制序列化机制。jvm在序列化一个对象时,序列化当前类的同时,也会沿着继承链把父类的"类数据"逐层写入流中。"类数据"指的是类名、类的描述、类中定义的可被序列化的字段及字段的值。
例如:
继承链是:TTUser------>Custom------>Person
java
class Person implements Serializable {
private static final long serialVersionUID = 5652464866930818765L;
}
class Custom extends Person {
private static final long serialVersionUID = 5652464866930818765L;
}
class TTUser extends Custom{
private static final long serialVersionUID = 5652464866930818765L;
}
我对TTUser类的对象,进行序列化时,会同时序列化Custom和Person的类数据。
java
// 对TTUser对象进行反序列化
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("ttuser.ser"));
out.writeObject(new TTUser());
out.close();

可以使用HxD工具查看ttuser.ser里的内容,可以看出ttuser.ser里包含了TTUser、Custom、Person的类信息。
HxD下载地址:HxD - Freeware Hex Editor and Disk Editor | mh-nexus
如果类中有定义writeObject()方法,那么在序列化该类时,就调用类中自定义的writeObject()方法。如果类没有自定义writeObject()方法,那么在序列化该类时,就调用ObjectOutputStream.writeObject()。
1.2、反序列化
jdk原生的反序列化就是使用jdk自带的ObjectInputStream.readObject()方法把字节流还原为对象。在这个过程中可以执行一些代码逻辑,如果执行到了危险的代码逻辑,就会造成反序列化漏洞。
如果类中有定义readObject()方法,那么在序列化该类时,就调用类中自定义的readObject()方法。如果类没有自定义readObject()方法,那么在序列化该类时,就调用ObjectInputStream.readObject()。
2、jdk原生反序列化方法
2.1、readObject()
如果某个类中定义了readObject()方法,那么在反序列化该类的字节流.ser时,会执行该类内部的readObject()方法。
java
package com.wlc.seralizeTest.jdkSink;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* @author: Wulc
* @createTime: 2026-03-28
* @description:
* @version: 1.0
*/
public class ReadObjectTest {
public static void main(String[] args) throws Exception {
//先对Student进行序列化
Student student=new Student("张三",20);
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("student.ser"));
out.writeObject(student);
out.close();
//再对student.ser进行反序列化
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("student.ser"));
in.readObject(); // 触发点
in.close();
}
}
class Human implements Serializable{
private String name;
private Integer age;
public Human() {
}
public Human(String name,Integer age){
this.name=name;
this.age=age;
}
private void readObject(ObjectInputStream in) throws Exception{
System.out.println(">>> Human readObject triggered!");
try {
Runtime.getRuntime().exec("calc"); // sink点
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Student extends Human{
private String sclass;
private Integer sno;
public Student(String sclass,Integer sno){
super();
this.sclass=sclass;
this.sno=sno;
}
private void readObject(ObjectInputStream in) throws Exception{
System.out.println(">>> Student readObject triggered!");
try {
Runtime.getRuntime().exec("calc"); // sink点
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.2、readObjectNoData()
当jdk反序列化某个.ser字节流时,如果反序列化到某个类时,发现没有该类的数据,如果该类中自定义了readObjectNoData()方法,那么就会触发该类自定义的readObjectNoData()方法。
如果该类没有自定义的readObjectNoData()方法,那么jvm就什么都不会做,直接跳过该类。
readObjectNoData通常在反序列化涉及到继承链的情况下,会被触发的多一些。比如Custom类,其父类是Person类。在反序列化Custom类的.ser文件时,.ser文件被修改过了,导致里面没有Person类的字节码,所以反序列化Custom类的.ser文件时,会触发Person类中的readObjectNoData()方法。
java
class Person implements Serializable {
private static final long serialVersionUID = 5652464866930818765L;
private void readObjectNoData() throws ObjectStreamException{
System.out.println(">>> Person 的 readObjectNoData 被触发!");
}
private void readObject(ObjectInputStream in) throws Exception{
System.out.println(">>> Person 的 readObject 被触发!");
try {
Runtime.getRuntime().exec("calc"); // sink点
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Custom extends Person {
private static final long serialVersionUID = 5652464866930818765L;
private void readObjectNoData() throws ObjectStreamException{
System.out.println(">>> Custom 的 readObjectNoData 被触发!");
}
private void readObject(ObjectInputStream in) throws Exception{
System.out.println(">>> Custom 的 readObject 被触发!");
try {
Runtime.getRuntime().exec("calc"); // sink点
} catch (Exception e) {
e.printStackTrace();
}
}
}
Step1、先对Person进行序列化,生成序列化文件person.ser
java
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("person.ser"));
out.writeObject(new Person());
out.close();
Step2、使用HxD修改person.ser文件,把里面的Person类名替换为Custom,注意替换的类名要等长,这是为了不破坏.ser文件内容。Custom类继承自Person类,且两个类的序列化id是一样的。

Step3、对修改好后的person.ser文件进行反序列化,因为readObjectNoData会在"某个类被反序列化,但没有对应数据"时触发。
java
//再对person.ser进行反序列化
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("person.ser"));
in.readObject(); // 触发点
in.close();
jvm反序列化一个对象时,会先处理当前类Custom,Custom有数据,所以正常被反序列化,不会调用readObjectNoData()。再去处理父类Person,Person没有数据,反序列化Person时会调用readObjectNoData()。

2.3、readExternal()
当一个类实现了 Externalizable 接口时,在反序列化过程中,JVM 不会调用 readObject(),而是直接调用该类的 readExternal() 方法。同理,在序列化的时候直接调用该类的 writeExternal() 方法。
java
public class ReadExternalTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Food food=new Food("红色");
//先对person进行序列化为person.ser
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("food.ser"));
out.writeObject(food);
out.close();
ObjectInputStream in=new ObjectInputStream(new FileInputStream("food.ser"));
in.readObject();
in.close();
}
}
class Food implements Externalizable, Serializable {
private String colour;
public Food() {
}
public Food(String colour) {
this.colour = colour;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println(">>> Food 的 writeExternal 被触发!");
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println(">>> Food 的 readExternal 被触发!");
}
}
2.4、readResolve()
readResolve方法是在反序列化的最后一步执行,通常是跟着readObject()方法之后执行的,用于返回一个新的对象,替换原来反序列化的对象。
(1)不使用readResolve()方法
java
class Fruit implements Serializable {
private String shape;
public Fruit(String shape) {
this.shape = shape;
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
System.out.println(">>> Fruit的readObject 被调用");
// 从 classdata 中恢复字段值,不调用这个方法的化,类中所有字段都是默认值
in.defaultReadObject();
}
@Override
public String toString() {
return "Fruit{" +
"shape='" + shape + '\'' +
'}';
}
}
java
Fruit fruit=new Fruit("圆");
ObjectOutputStream outputStream=new ObjectOutputStream
(new FileOutputStream("fruit.ser"));
outputStream.writeObject(fruit);
outputStream.close();
ObjectInputStream inputStream=new ObjectInputStream
(new FileInputStream("fruit.ser"));
Object obj=inputStream.readObject();
Fruit fruit1=(Fruit)obj;
System.out.println(fruit1.toString());
inputStream.close();
反序列化后,得到的还是原来的Fruit对象。

(2)使用readResolve()方法
java
class Fruit implements Serializable {
private String shape;
public Fruit(String shape) {
this.shape = shape;
}
private Object readResolve() throws ObjectStreamException {
System.out.println(">>> Fruit的readResolve 被调用");
// 返回一个"替代对象"
return new Food("红色");
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
System.out.println(">>> Fruit的readObject 被调用");
// 从 classdata 中恢复字段值,不调用这个方法的化,类中所有字段都是默认值
in.defaultReadObject();
}
@Override
public String toString() {
return "Fruit{" +
"shape='" + shape + '\'' +
'}';
}
}
java
Fruit fruit=new Fruit("圆");
ObjectOutputStream outputStream=new ObjectOutputStream
(new FileOutputStream("fruit.ser"));
outputStream.writeObject(fruit);
outputStream.close();
ObjectInputStream inputStream=new ObjectInputStream
(new FileInputStream("fruit.ser"));
Object obj=inputStream.readObject();
Food food=(Food)obj;
System.out.println(food.toString());
inputStream.close();
反序列化后,得到的是新的Food对象。

readResolve方法,通常会用在单例模式中,即在反序列化一个类对象时,不产生新的对象,还是返回原来的那个对象。
2.5、validateObject()
validateObject在是整个反序列化流程的最后一步,通常是用于在整个对象反序列化完成之后,对对象进行最终的一致性验证或修复。
java
class Shop implements ObjectInputValidation, Serializable {
private String address;
public Shop() {
}
public Shop(String address) {
this.address = address;
}
private void readObject(ObjectInputStream in) throws Exception {
System.out.println(">>> Shop的readObject 被调用");
in.defaultReadObject();
// 注册回调validateObject方法,this回调对象为当前对象;0表示优先级
in.registerValidation(this, 0);
}
@Override
public void validateObject() throws InvalidObjectException {
System.out.println(">>> Shop的validateObject 被调用");
// 如果对象的address属性为空,则抛出异常
if(this.address==null || this.address.equals("")){
throw new InvalidObjectException("address不能为null");
}
}
private Object readResolve() throws ObjectStreamException {
System.out.println(">>> Shop的readResolve 被调用");
// 返回一个"替代对象"
return new Food("红色");
}
}
java
// Shop shop=new Shop("大街");
Shop shop=new Shop();
ObjectOutputStream outputStream=new ObjectOutputStream
(new FileOutputStream("shop.ser"));
outputStream.writeObject(shop);
outputStream.close();
ObjectInputStream inputStream=new ObjectInputStream
(new FileInputStream("shop.ser"));
Food food=(Food)inputStream.readObject();
System.out.println(food);
inputStream.close();

因此反序列化的一般执行顺序是:readObject()------>readObjectNoData(没有类数据)------>readExternal(如果实现了Externalizable接口)------>readResolve()------>validateObject
3、其他反序列化入口
除了jdk原生的反序列化入口方法,还有其他流行的java反序列化协议提供的接口,例如Hession协议的Map.put()等。

3.1、T3/IIOP
中间件协议,基于网络的jdk反序列化,即远程调用协议RMI。在传输对象时自动触发jdk反序列化。
T3是WebLogic的私有协议。WebLogic是一款java应用服务器。IIOP是一款通信协议。
客户端发送请求(请求数据中包含了序列化对象)给服务器,服务器通过T3/IIOP协议解析请求数据,提取对象字节流反序列化,然后就会触发readObject()、readResolve()等一系列入口方法。入口方法同jdk原生的入口方法一样。
3.2、Hessian
Hessian是一种二进制序列化协议,http://hessian.caucho.com/ 主要用于RPC远程调用,dubbo等。
XML
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.66</version>
</dependency>
java
public class HessianTest {
public static void main(String[] args) throws IOException {
HashMap<Object,Object> hashMap=new HashMap<>();
// 构造map,map.put()方法会触发hashCode方法。
hashMap.put(new SUser("zhangsan","123"),"SUser");
// 序列化SUser对象
HessianOutput hessianOutput=new HessianOutput(new FileOutputStream("suser.ser"));
hessianOutput.writeObject(hashMap);
// hessianOutput.writeObject(new SUser());
hessianOutput.close();
// 反序列化suser.bin,反序列化时Hessian创建新的HashMap时,会用到map.put()方法会触发hashCode方法
// Hessian 在反序列化 Map 时,会调用 put() ,put() 会触发 hashCode()
// HashMap在put一个新元素时,需要 hashCode 来决定存储位置,如果发生hash冲突,还会触发equals方法
HessianInput hessianInput=new HessianInput(new FileInputStream("suser.ser"));
Object object=hessianInput.readObject();
System.out.println(object);
}
}
class SUser implements Serializable {
private String username;
private String password;
public SUser() {
}
public SUser(String username, String password) {
this.username = username;
this.password = password;
}
@Override
public int hashCode(){
System.out.println(">>> SUser 的 hashCode 被触发!");
return 1;
}
@Override
public String toString() {
return "SUser{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}

注意,这里第一个hashCode方法,是在hashMap.put(new SUser("zhangsan","123"),"SUser");时会触发。第二个hashCode方法是Hessian 在反序列化 Map 时,会调用 put() ,put() 再触发 hashCode()。
3.3、Hessian-lite
Hessian的精简版。
3.4、Hessian-sofa
Hessian的增强版。
3.5、xstream
xstream是一个将java对象与xml相互转换的序列化框架。
XML
<!-- Source: https://mvnrepository.com/artifact/com.thoughtworks.xstream/xstream -->
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.15</version>
<scope>compile</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/xpp3/xpp3 -->
<dependency>
<groupId>xpp3</groupId>
<artifactId>xpp3</artifactId>
<version>1.1.4c</version>
<scope>compile</scope>
</dependency>
java
public class XstreamTest {
public static void main(String[] args) {
XStream xStream=new XStream();
Pet pet=new Pet("Tom","白色");
//序列化:对象------>xml
String xml=xStream.toXML(pet);
System.out.println(xml);
//反序列化:xml------>对象
Object object=xStream.fromXML(xml);
System.out.println(object);
HashMap<Object,Object> hashMap=new HashMap<>();
hashMap.put(pet,"pet");
String mapXml=xStream.toXML(hashMap);
Object objMap=xStream.fromXML(mapXml);
System.out.println(objMap);
}
}
class Pet implements Serializable {
private String name;
private String color;
public Pet() {
}
public Pet(String name, String color) {
this.name = name;
this.color = color;
}
private void readObject(ObjectInputStream in) throws Exception{
System.out.println(">>> Pet readObject triggered!");
in.defaultReadObject();
}
@Override
public int hashCode(){
System.out.println(">>> Pet 的 hashCode 被触发!");
return 1;
}
@Override
public String toString() {
return "Pet{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
}

4、总结
最近在搞反序列化漏洞检测的研究,先梳理一下常见的java反序列化入口方法。