Java readObject方法分析

Java readObject方法分析,第1张

Java readObject方法分析 Java readObject方法分析

WebLogic 反序列化漏洞用到了有关 readObject 相关的知识,那么这里来分析一下 ObjectInputStream#readObject 方法,为后续 WebLogic 的反序列化漏洞分析打下基础。

编写调试类

User 类

public class User implements Serializable {
    private String name;
    private int age;

    protected User() throws IOException, SecurityException {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        var1.defaultReadObject();
        System.out.println(1);
    }

    private Object readResolve(){
        System.out.println(2);
        return null;
    }
}

Under 类

public class Under {
    public static void main(String[] args) throws Exception{
        User user = new User();
        user.setAge(10);
        user.setName("liming");
        FileOutputStream fileOutputStream = new FileOutputStream("1.ser");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(user);
        FileInputStream fileInputStream = new FileInputStream("1.ser");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        objectInputStream.readObject();
    }
}

在 ObjectInputStream#readObject 方法下断点,调试启动 Under 类即可看到断点停在 ObjectInputStream#readObject 方法上

判断 enableOverride 值是否为 true ,是的话则调用 readObjectOverride 方法,enableOverride 属性在构造方法中默认设置为 false,那么就调用 readObject0 方法,跟进一下

这里调用 peekByte 方法获取 bin 的第一个字节,如果第一个字节等于 TC_RESET 属性值,也就是等于 121 的话则进入 while 循环,这里获取到的第一个字节为 115,那么不会进入第一个循环。接下来就是一个 switch 根据获取到的第一个字节进入到相应的 case 分支

这里进入到的是 TC_OBJECT 分支,调用 readOrdinaryObject 方法,这个方法是整个反序列化过程中的重点,跟进一下这个方法

这里再读取一下字节是否为 TC_OBJECT,确保是正常进入到这个方法的,然后调用 readClassDesc 方法,跟进 readClassDesc 方法

同理,获取第二个字节,switch 判断看进入哪个分支,这里进入到的是 TC_CLASSDESC 分支,调用 readNonProxyDesc 方法,跟进一下 readNonProxyDesc 方法

关键的地方用红框圈出来了,先判断是否正常进入的这个方法,然后创建一个 ObjectStreamClass 对象和 Class 对象,之后再调用 resolveClass 方法,传入的参数为 readDesc ,通过 readClassDescriptor 方法获取到的 readDesc 存在着反序列化的类名和字段名等信息,跟进一下 resolveClass 方法

先获取到反序列化的类名,然后用 Class.forName 加载 Class 类对象,这里提一嘴的是 WebLogic 的一部分防御就是在这个方法进行过滤检查的,如果检查未通过则直接 Class 类对象都没有,更别谈后面的一系列 *** 作了。

回到 readNonProxyDesc 方法,用获取到的 Class 类对象初始化 desc ,其中就把 Class 类对象赋值到 desc.cl 属性中

至此 readNonProxyDesc/readClassDesc 方法就完成了,回到 readOrdinaryObject 方法

从 desc 中获取到 Class 类对象,然后看类对象是否可以实例化的,如果是可以实例化的则进行实例化,继续往下看

根据 desc.isExternalizable() 返回值决定 readExternalData 方法还是 readSerialData 方法,这里调用到的是 readSerialData方法,我们跟进一下 readExternalData 方法看一下是怎么个逻辑

这里是调用对象的 readExternal 方法,应该是如果反序列化对象存在 readExternal 方法,则会调用 readExternal 方法,这个在后面的 WebLogic 绕过中有利用到。

那么这里回到 readSerialData 方法,跟进一下这个方法

这里根据 hasReadObjectMethod 方法的返回值决定是否调用 invokeReadObject 方法,如果反序列化对象重写了 readObject 方法则 hasReadObjectMethod 方法的返回值为 true,这里进入 invokeReadObject 方法看一下

就是反射调用反序列化对象的 readObject 方法,那么回到 readOrdinaryObject 方法

这里看一下根据 hasReadResolveMethod 方法返回值决定是否调用 invokeReadResolve 方法,跟前面的调用重写 readObject 方法差不多,如果反序列化对象重写了 readResolve 方法,则 hasReadResolveMethod 方法返回值为 true,跟进 invokeReadResolve 方法看一下

跟前面的一样,反射调用反序列化对象的 readResolve 方法,至此就反序列化过程就基本完成了

总结一下,反序列化过程会尝试调用反序列化对象的 readResolve/readObject/readExternal 方法,而在后续的 WebLogic 的绕过过程也是用到了反序列化会调用对象重写的 readResolve/readObject/readExternal 方法进行绕过。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5719707.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-18
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存