JAVA反序列化的简单探究

admin 2021年12月7日12:01:27评论108 views字数 3989阅读13分17秒阅读模式

本文主要是探究,在反序列化过程中是怎么调用到readObject、readResolve、readExternal方法的问题

新建一个需要被序列化的类ObjectA,写入readResolve和readObject方法:

 package com.yy.serialize.readResolve;
 
 import java.io.IOException;
 import java.io.Serializable;
 
 public class ObjectA implements Serializable {
     private ObjectA() {
    }
 
     private static final ObjectA objectA = new ObjectA();
 
     public static ObjectA getInstance() {
         return objectA;
    }
     private Object readResolve() {
         System.out.println("执行了readResolve方法");
         return objectA;
    }
 
     private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
         //执行默认的readObject()方法
         in.defaultReadObject();
         System.out.println("执行了readObject方法");
    }
 }

另一个类ObjectB则写入了readExternal方法:

 package com.yy.serialize.readResolve;
 
 import java.io.Externalizable;
 import java.io.IOException;
 import java.io.ObjectInput;
 import java.io.ObjectOutput;
 
 public class ObjectB 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");
    }
 }

测试类:

 package com.yy.serialize.readResolve;
 
 import java.io.*;
 
 public class SerializeDemo {
     public static void main(String[] args) throws Exception {
         ObjectA objectA = ObjectA.getInstance();
         ObjectB objectB = new ObjectB();
 
         // 序列化
         FileOutputStream fos = new FileOutputStream("a.txt");
         ObjectOutputStream oos = new ObjectOutputStream(fos);
         oos.writeObject(objectA);
         oos.flush();
 
         // 反序列化
         FileInputStream fis = new FileInputStream("a.txt");
         ObjectInputStream ois = new ObjectInputStream(fis);
         ois.readObject();
 
 
    }
 }

运行测试类生成a.txt文件后,可以使用SerializationDumper查看字节流的数据:

JAVA反序列化的简单探究

这里有两个关键的标识

TC_OBJECT:标记后面的数据为Object对象

TC_CLASSDESC:类描述符标识,表示一个类中的所有信息

     TC_CLASSDESC - 0x72
      className
        Length - 36 - 0x00 24
        Value - com.yy.serialize.readResolve.ObjectA - 0x636f6d2e79792e73657269616c697a652e726561645265736f6c76652e4f626a65637441
      serialVersionUID - 0xee 44 c6 e6 0d b3 64 b5
      newHandle 0x00 7e 00 00
      classDescFlags - 0x02 - SC_SERIALIZABLE
      fieldCount - 0 - 0x00 00
      classAnnotations
        TC_ENDBLOCKDATA - 0x78
      superClassDesc
        TC_NULL - 0x70

调用分析

ois.readObject下个断点

JAVA反序列化的简单探究

进入ObjectInputStream#readObject方法,看到其调用了readObject0

JAVA反序列化的简单探究

跟进readObject0,在此函数中,根据了tc值来进行switch,此时的tc值为TC_OBJECT,也就是0x72十进制数115

JAVA反序列化的简单探究

case TC_OBJECT中,调用了readOrdinaryObject

JAVA反序列化的简单探究

跟进readOrdinaryObject中,发现调用了readClassDesc方法,并把值赋给了desc

JAVA反序列化的简单探究

跟进readClassDesc,此方法用来分发处理字节流中TC_CLASSDESC的方法,用switch来选择需要处理的方法

JAVA反序列化的简单探究

tc的值就是TC_CLASSDESC的值0x72,转成10进制就是114,然后进入switch判断后转到case TC_CLASSDESC:

JAVA反序列化的简单探究

跟进readNonProxyDesc方法,调用了resolveClass方法后,把值赋给了cl

JAVA反序列化的简单探究

继续跟进resolveClass后,发现使用了Class.forName来创建了ObjectA对象,然后把创建的对象进行return返回

JAVA反序列化的简单探究

回到readNonProxyDesc,此时cl值变成了ObjectA对象,然后把cl对象传入了initNonProxy,并且赋值给了desc

JAVA反序列化的简单探究

最后readNonProxyDesc方法返回了desc的值

JAVA反序列化的简单探究

返回的desc的值赋给了调用处readClassDesc的descriptor,然后又进行了返回

JAVA反序列化的简单探究

最后回到了readOrdinaryObject方法中

JAVA反序列化的简单探究

在这里先总结下这几个方法分别做了什么操作

总结下上面的流程顺序为:

readOrdinaryObject -》 readClassDesc -》 readNonProxyDesc -》 readResolve方法

readClassDesc :分发用于处理字节流中TC_CLASSDESC的方法,用switch来选择需要处理的方法

JAVA反序列化的简单探究

readNonProxyDesc :真正用来处理字节流中的TC_CLASSDESC方法,会调用resolveClass进行创建反序列化的对象

resolveClass:是用Class.forName来创建ObjectA对象的地方,在这里可以做一个检查校验,用于反序列化拦截。weblogic中补丁拦截就是在此进行的

到这里三个方法都讲了用途后,还剩最后一个readOrdinaryObject 了

readOrdinaryObject

这里的readOrdinaryObject就是真正操作调用序列化类中,readObject、readResolve、readExternal方法的地方

接着上面debug,拿到了desc的值后往下走,做了一个判断desc.isExternalizable,如果序列化的接口是Externalizable类型,就进入readExternalData,否则进入readSerialData

JAVA反序列化的简单探究

此处的ObjectA对象接口类型是Serializable,所以进入了readSerialData方法

JAVA反序列化的简单探究

最后readSerialData方法中用了反射进行调用反序列化对象的readObject方法

JAVA反序列化的简单探究

回到readOrdinaryObject,接下来就是调用readResolve方法的地方了

JAVA反序列化的简单探究

用if进行判断,为true则用反射调用反序列化对象的readResolve方法

JAVA反序列化的简单探究

JAVA反序列化的简单探究

引用一张xz上师傅文章的流程图:

https://xz.aliyun.com/t/8443#toc-2

JAVA反序列化的简单探究

总结

方法调用的流程顺序:

readOrdinaryObject -》 readClassDesc -》 readNonProxyDesc -》 readResolve方法

readClassDesc :分发用于处理字节流中TC_CLASSDESC的方法,用switch来选择需要处理的方法

JAVA反序列化的简单探究

readNonProxyDesc :真正用来处理字节流中的TC_CLASSDESC方法,会调用resolveClass进行创建反序列化的对象

resolveClass:是用Class.forName来创建ObjectA对象的地方,在这里可以做一个检查校验,用于反序列化拦截。weblogic中补丁拦截就是在此进行的

readOrdinaryObject:用于调用序列化类中,readObject、readResolve、readExternal的方法



本文始发于微信公众号(安全羊):JAVA反序列化的简单探究

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年12月7日12:01:27
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   JAVA反序列化的简单探究http://cn-sec.com/archives/454213.html

发表评论

匿名网友 填写信息