前言
在改造自动化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点