「渗透分析」GadgetInspector改造中反序列化三个source点的原理分析插图

前言

在改造自动化Gadget挖掘的过程中,在收集各种source / sink的时候,针对反序列化漏洞的source存在有readObject / readResolve / readExternal,这里就是对其调用的原理进行详细探究

原理分析

环境

我们这里创建了三个类Test1 / Test2 / Test

其中Test1类,是一个实现了Serializable接口,并定义了readObject / readResolve两个方法

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接口,并重写了writeExternal / readExternal两个方法

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();

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

        // deserialization
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        objectInputStream.readObject();

        System.gc();
    }
}

readObject调用

在反序列化的过程中,将会调用ObjectInputStream#readObject方法进行反序列化

这里首先将会调用readObject0方法来执行核心的反序列化的逻辑

这个方法就是底层反序列化的实现,主要的逻辑就是通过tc的值进行不同的方法调用

那这个tc值是从哪里来的呢?

1540行代码中可以知道,是从bin这个数据块输入流(BlockDataInputStream)获取的字节,也即是获取的是TC_OBJECT的值,十进制数为115

进入case语句中,调用readOrdinaryObject方法进行反序列化核心逻辑

之后调用了readClassDesc方法,通过读取类描述符来进行不同的操作,也即是TC_CLASSDESC

这里的得到的值为114,进入的case语句为:

调用了readNonProxyDesc方法读入并返回不是动态代理类的类的类描述符,也即是真正的处理类描述符

首先是从流中读取了类描述

之后调用了resolveClass方法进行目标类的创建

其中是通过Class.forName来加载对应的类并返回

之后将得到的这个类传入了initNonProxy方法中

将前面得到的类传递给了desc对象中,最后直到readClassDesc的逻辑完成

现在回到了readOrdinaryObject方法中

这里首先将前面得到的类实例化得到obj对象

在后面存在有一个判断,通过调用isExternalizable方法判断是否实现了Externalizable这个接口

这里我们在Test1类中并没有实现这个接口,所以我们这里进入的是else子句中调用了readSerialData进行序列化数据的反序列化操作

这个方法中也即是我们的source点的调用位置

这里首先获取了序列化的类数据之后,通过遍历这个序列化的类,存在判断其类中是否存在有readObject方法

通过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调用

接着前面通过反射调用了序列化类的readObject方法,在readOrdinaryObject方法中接着往下走

在调用完readSerialData方法之后,接着会通过调用hasReadResolveMethod方法来判断序列化类是否存在有readResolve方法

如果存在,将会反射调用该方法

readExternal调用

对于该方法的调用,必须要求序列化类实现了Externalizable接口,所以我们这里转而对Test2进行序列化和反序列化调用

前面的一大部分过程和readObject的调用类似,这里和前面不同的点在于在进行isExternalizable方法调用进行判断的时候

前面是进入的readSerialData方法进行序列化数据的反序列化操作

这里转而使用readExternalData方法进行序列化数据的反序列化操作

如果目标类不为空,将会直接调用readExternal方法进行外部化数据的读取,进而达到了我们的source的条件

总结

前面详细的分析了可以作为反序列化source点的三个方法,对于自动化工具来说,完全可以采取模糊匹配的方式,即是直接筛选实现了Serialiable接口且存在有这三个方法的任一个方法的类作为我们Gadget链的source点