GadgetInspector改造中反序列化三个source点的原理分析
字数 1812 2025-08-26 22:11:57

GadgetInspector改造中反序列化三个source点的原理分析

前言

在改造自动化Gadget挖掘工具GadgetInspector的过程中,针对反序列化漏洞的source点收集时,发现存在三个可能的触发点:readObjectreadResolvereadExternal。本文将对这三个方法的调用原理进行详细分析。

实验环境

我们创建了三个测试类来演示这三个source点的调用机制:

Test1类

实现了Serializable接口,并定义了readObjectreadResolve方法:

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

public class Test1 implements Serializable {
    private Object readResolve() {
        System.out.println("readResolve.....");
        return new Test1();
    }
    
    private void readObject(ObjectInputStream inputStream) 
        throws IOException, ClassNotFoundException {
        inputStream.defaultReadObject();
        System.out.println("readObject.....");
    }
}

Test2类

实现了Externalizable接口,并重写了writeExternalreadExternal方法:

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class Test2 implements Externalizable {
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("writeExternal.....");
    }

    @Override
    public void readExternal(ObjectInput in) 
        throws IOException, ClassNotFoundException {
        System.out.println("readExternal....");
    }
}

Test类

执行序列化和反序列化操作的主类:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Test {
    public static void main(String[] args) throws Exception {
        Test1 test1 = new Test1();
        Test2 test2 = new Test2();

        // 序列化
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(test1);
        byte[] bytes = byteArrayOutputStream.toByteArray();

        // 反序列化
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        objectInputStream.readObject();

        System.gc();
    }
}

原理分析

readObject调用机制

  1. 反序列化入口ObjectInputStream#readObject方法是反序列化的主要入口,它会调用readObject0方法执行核心反序列化逻辑。

  2. 读取对象类型readObject0方法通过tc值(从BlockDataInputStream获取的字节)判断对象类型。对于普通对象,tc值为TC_OBJECT(十进制115)。

  3. 处理普通对象:当tc值为TC_OBJECT时,调用readOrdinaryObject方法处理普通对象的反序列化。

  4. 读取类描述符readOrdinaryObject调用readClassDesc方法读取类描述符,tc值为TC_CLASSDESC(十进制114)。

  5. 处理非代理类描述符readNonProxyDesc方法读取并返回非动态代理类的类描述符:

    • 从流中读取类描述
    • 调用resolveClass方法(内部使用Class.forName)加载对应的类
    • 将得到的类传递给desc对象
  6. 实例化对象:回到readOrdinaryObject方法,实例化得到obj对象。

  7. 判断接口类型:通过isExternalizable方法判断是否实现了Externalizable接口:

    • 如果未实现(如Test1),进入else子句调用readSerialData方法
  8. 调用readObject方法:在readSerialData方法中:

    • 通过hasReadObjectMethod检查类中是否存在readObject方法
    • 如果存在,通过反射调用该方法

调用栈

readObject:14, Test1 (pers.test_01)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)

readResolve调用机制

  1. 在readOrdinaryObject中:调用完readSerialData方法后,会继续调用hasReadResolveMethod检查序列化类是否存在readResolve方法。

  2. 反射调用:如果存在readResolve方法,将通过反射调用该方法。

readExternal调用机制

  1. 前提条件:目标类必须实现Externalizable接口(如Test2)。

  2. 接口判断:在readOrdinaryObject方法中,isExternalizable方法返回true

  3. 调用readExternalData:与readObject不同,这里调用readExternalData方法处理序列化数据。

  4. 直接调用readExternal:如果目标类不为空,直接调用其readExternal方法读取外部化数据。

总结

  1. 三个source点

    • readObject:实现Serializable接口并定义该方法
    • readResolve:实现Serializable接口并定义该方法
    • readExternal:必须实现Externalizable接口
  2. 自动化工具实现:可以通过模糊匹配方式,筛选实现了Serializable接口且包含这三个方法之一的类作为Gadget链的source点。

  3. 调用顺序:在反序列化过程中,这些方法的调用顺序和条件各不相同,理解其底层机制有助于准确识别潜在的漏洞触发点。

通过这种详细分析,我们可以更准确地识别和利用Java反序列化漏洞中的各种source点,为自动化Gadget挖掘工具提供更全面的检测能力。

GadgetInspector改造中反序列化三个source点的原理分析 前言 在改造自动化Gadget挖掘工具GadgetInspector的过程中,针对反序列化漏洞的source点收集时,发现存在三个可能的触发点: readObject 、 readResolve 和 readExternal 。本文将对这三个方法的调用原理进行详细分析。 实验环境 我们创建了三个测试类来演示这三个source点的调用机制: Test1类 实现了 Serializable 接口,并定义了 readObject 和 readResolve 方法: Test2类 实现了 Externalizable 接口,并重写了 writeExternal 和 readExternal 方法: Test类 执行序列化和反序列化操作的主类: 原理分析 readObject调用机制 反序列化入口 : ObjectInputStream#readObject 方法是反序列化的主要入口,它会调用 readObject0 方法执行核心反序列化逻辑。 读取对象类型 : readObject0 方法通过 tc 值(从 BlockDataInputStream 获取的字节)判断对象类型。对于普通对象, tc 值为 TC_OBJECT (十进制115)。 处理普通对象 :当 tc 值为 TC_OBJECT 时,调用 readOrdinaryObject 方法处理普通对象的反序列化。 读取类描述符 : readOrdinaryObject 调用 readClassDesc 方法读取类描述符, tc 值为 TC_CLASSDESC (十进制114)。 处理非代理类描述符 : readNonProxyDesc 方法读取并返回非动态代理类的类描述符: 从流中读取类描述 调用 resolveClass 方法(内部使用 Class.forName )加载对应的类 将得到的类传递给 desc 对象 实例化对象 :回到 readOrdinaryObject 方法,实例化得到 obj 对象。 判断接口类型 :通过 isExternalizable 方法判断是否实现了 Externalizable 接口: 如果未实现(如 Test1 ),进入 else 子句调用 readSerialData 方法 调用readObject方法 :在 readSerialData 方法中: 通过 hasReadObjectMethod 检查类中是否存在 readObject 方法 如果存在,通过反射调用该方法 调用栈 : readResolve调用机制 在readOrdinaryObject中 :调用完 readSerialData 方法后,会继续调用 hasReadResolveMethod 检查序列化类是否存在 readResolve 方法。 反射调用 :如果存在 readResolve 方法,将通过反射调用该方法。 readExternal调用机制 前提条件 :目标类必须实现 Externalizable 接口(如 Test2 )。 接口判断 :在 readOrdinaryObject 方法中, isExternalizable 方法返回 true 。 调用readExternalData :与 readObject 不同,这里调用 readExternalData 方法处理序列化数据。 直接调用readExternal :如果目标类不为空,直接调用其 readExternal 方法读取外部化数据。 总结 三个source点 : readObject :实现 Serializable 接口并定义该方法 readResolve :实现 Serializable 接口并定义该方法 readExternal :必须实现 Externalizable 接口 自动化工具实现 :可以通过模糊匹配方式,筛选实现了 Serializable 接口且包含这三个方法之一的类作为Gadget链的source点。 调用顺序 :在反序列化过程中,这些方法的调用顺序和条件各不相同,理解其底层机制有助于准确识别潜在的漏洞触发点。 通过这种详细分析,我们可以更准确地识别和利用Java反序列化漏洞中的各种source点,为自动化Gadget挖掘工具提供更全面的检测能力。