java基础--反序列化漏洞原理

原理

根据上篇文章可以了解到,一个类想要实现序列化和反序列化,必须要实现 java.io.Serializable 或 java.io.Externalizable 接口。

Serializable 接口是一个标记接口,标记了这个类可以被序列化和反序列化,而 Externalizable 接口在 Serializable 接口基础上,又提供了 writeExternal 和 readExternal 方法,用来序列化和反序列化一些外部元素。

其中,如果被序列化的类重写了 writeObject 和 readObject 方法,Java 将会委托使用这两个方法来进行序列化和反序列化的操作。

导致反序列化漏洞的出现:在反序列化一个类时,如果其重写了 readObject 方法,程序将会调用它,如果这个方法中存在一些恶意的调用,则会对应用程序造成危害。

所以导致反序列化漏洞的出现就是重写readObject方法。

过程

在如下的代码中,在Person中重写了readObject方法,并在其中进行了命令执行,下面就探究一下这条命令是否能执行

Person.java

import java.io.IOException;
import java.io.Serializable;
public class Person implements Serializable {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
        Runtime.getRuntime().exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
    }

}

SerializableTest.java

import java.io.*;
public class SerializableTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Person person = new Person("zyer", 22);
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.txt"));
        oos.writeObject(person);
        oos.close();
        FileInputStream   fis = new FileInputStream("1.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
        ois.close();
    }
}

我们直接在ois.readObject()处下断点,

然后直接跟进readObject (command/Ctrl + 左键),发现存在readObject的使用,于是在这再下断点并运行,使用单步步入的方式(F7)

进入到该函数,先是两个if判断,根据传入的type值,我们可以知道会进入try中,其中调用了 readObject0函数,继续跟进

 进入到readObject0,大致看一下代码,关键就是对byte tc进行判断,且该方法是以字节将文件读入,于是继续向下单步步入

 继续向下执行会到case TC_object

上边对TC_object有定义0x73,根据上篇文章可以知道,0x73是序列化的对象标志符

 然后进入readOrdinaryObject,可以看到

obj = desc.isInstantiable() ? desc.newInstance() : null;

在这条代码处就有newInstance方法,将其实例化 

 继续向下运行发现到这果然已经有了Person的obj对象了,正常的反序列化的话到这就应该结束了,但是因为我们的代码中有重新readObject的方法,所以我们还需要继续跟进

然后继续向下,存在desc.isExternalizable()的判断是不是Externalizable 接口,如果是,则调用 readExternalData 方法去执行反序列化类中的 readExternal,如果不是,则调用 readSerialData 方法去执行类中的 readObject 方法。

继续向下跟进,因为我们使用的是java.io.Serializable,所以进入到else语句的readserialData方法

然后进入readSerialData,在 readSerialData 方法中,首先通过类描述符获得了序列化对象的数据布局。通过布局的 hasReadObjectMethod 方法判断对象是否有重写 readObject 方法,如果有,则使用 invokeReadObject 方法调用对象中的 readObject 。

继续跟进invokeReadObject,发现使用了invoke方式将输入流作为参数,将其生成一个新对象,从而实现命令执行


参考su18师傅(Java 反序列化漏洞(一) - 前置知识 & URLDNS | 素十八) 

通过对反序列化漏洞形成的原理分析,我们可以有个基本思路,就是直接找readObject的方法,再在其内部寻找一些可以利用的漏洞函数,下一篇文章就针对HashMap进行利用。

你可能感兴趣的:(代码审计学习,java,开发语言)