LOADING

加载过慢请开启缓存 浏览器默认开启

readObject()到底在执行谁的代码

2025/9/15 Java

通过手动模拟 readObject() 的执行过程,来简单了解反序列化的调用过程。

readObject() 的真实执行逻辑

在IDEA中静态追踪 ois.readObject(),只能看到ObjectInputStream的源码。

ObjectInputStream ois = new ObjectInputStream(inputStream);
Object obj = ois.readObject();

但是在运行时,JVM会根据字节流信息完成以下动作:

从流中读取类名
从流中读取类描述信息
分配内存,创建对象
如果该类实现了Serializable,并且定义了 private readObject()
如果该类定义了private void readObject(ObjectInputStream in),JVM会通过反射调用它

ois.readObject()在反序列化过程中,会通过反射机制,查找并调用目标类中定义的私有readObject() 方法。

下面这个Demo可以直观看到这一点。

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;

public class SimpleObject implements Serializable {

    private void readObject(ObjectInputStream in)
            throws IOException, ClassNotFoundException {

        in.defaultReadObject();
        System.out.println("SimpleObject.readObject()被调用");
    }
}

服务端代码也有一个readObject方法,但是永远不会被调用。

import java.io.*;

public class ServerDemo {

    public static void main(String[] args) throws Exception {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(new SimpleObject());

        ObjectInputStream ois = new ObjectInputStream(
                new ByteArrayInputStream(bos.toByteArray())
        );
        ois.readObject();
    }

    //服务端自己的readObject(永远不会被调用)
    private void readObject(ObjectInputStream in) {
        System.out.println("[Server] readObject被调用了?");
    }
}

运行后输出:

SimpleObject.readObject()被调用

这一步已经能说明一个事实:反序列化过程中,JVM会执行被反序列化对象所属类的逻辑,而不是服务端的逻辑。

本地文件模拟真实的反序列化攻击过程

真实攻击中,攻击者并不是直接new一个对象给服务端,而是在远端构造序列化数据—>通过网络传输—>服务端从输入流中反序列化

这里用写文件+读文件来模拟。
先假设服务端classpath中已经存在一个有问题的类(现实中通常来自第三方库)。

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;

public class FileGadget implements Serializable {

    private String cmd;

    public FileGadget(String cmd) {
        this.cmd = cmd;
    }

    private void readObject(ObjectInputStream in)
            throws IOException, ClassNotFoundException {

        in.defaultReadObject();
        Runtime.getRuntime().exec(cmd);
    }
}

这个类本身并不恶意,但它在反序列化时执行了敏感操作。

模拟攻击者在远端构造序列化数据:

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

public class PayloadWriter {

    public static void main(String[] args) throws Exception {
        FileGadget gadget = new FileGadget("calc");

        ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("payload.bin")
        );
        oos.writeObject(gadget);
        oos.close();
    }
}

模拟服务端从网络数据(这里是文件)中读取并反序列化:

import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class FileServer {

    public static void main(String[] args) throws Exception {
        ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("payload.bin")
        );
        ois.readObject();
    }
}

运行 FileServer 时,命令被执行。

反序列化漏洞的本质不是执行了攻击者的代码,而是通过精心构造的对象结构和字段值,驱动服务端已有类中的隐式方法调用。