java反序列化入口方法介绍

张开发
2026/4/20 6:30:16 15 分钟阅读

分享文章

java反序列化入口方法介绍
1、概念1.1、序列化序列化就是将内存对象转换为字节流的过程使用transient关键字可以不让字段被序列化。1.1.1、jdk原生反序列化jdk原生的反序列化指的是java自带的二进制序列化机制。jvm在序列化一个对象时序列化当前类的同时也会沿着继承链把父类的“类数据”逐层写入流中。“类数据”指的是类名、类的描述、类中定义的可被序列化的字段及字段的值。例如继承链是TTUser——Custom——Personclass Person implements Serializable { private static final long serialVersionUID 5652464866930818765L; } class Custom extends Person { private static final long serialVersionUID 5652464866930818765L; } class TTUser extends Custom{ private static final long serialVersionUID 5652464866930818765L; }我对TTUser类的对象进行序列化时会同时序列化Custom和Person的类数据。// 对TTUser对象进行序列化 ObjectOutputStream out new ObjectOutputStream( new FileOutputStream(ttuser.ser)); out.writeObject(new TTUser()); out.close();可以使用HxD工具查看ttuser.ser里的内容可以看出ttuser.ser里包含了TTUser、Custom、Person的类信息。HxD下载地址HxD - Freeware Hex Editor and Disk Editor | mh-nexus如果类中有定义writeObject()方法那么在序列化该类时就调用类中自定义的writeObject()方法。如果类没有自定义writeObject()方法那么在序列化该类时就调用ObjectOutputStream.writeObject()。1.2、反序列化jdk原生的反序列化就是使用jdk自带的ObjectInputStream.readObject()方法把字节流还原为对象。在这个过程中可以执行一些代码逻辑如果执行到了危险的代码逻辑就会造成反序列化漏洞。如果类中有定义readObject()方法那么在序列化该类时就调用类中自定义的readObject()方法。如果类没有自定义readObject()方法那么在序列化该类时就调用ObjectInputStream.readObject()。2、jdk原生反序列化方法2.1、readObject()如果某个类中定义了readObject()方法那么在反序列化该类的字节流.ser时会执行该类内部的readObject()方法。package com.wlc.seralizeTest.jdkSink; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; /** * author: Wulc * createTime: 2026-03-28 * description: * version: 1.0 */ public class ReadObjectTest { public static void main(String[] args) throws Exception { //先对Student进行序列化 Student studentnew Student(张三,20); ObjectOutputStream out new ObjectOutputStream( new FileOutputStream(student.ser)); out.writeObject(student); out.close(); //再对student.ser进行反序列化 ObjectInputStream in new ObjectInputStream( new FileInputStream(student.ser)); in.readObject(); // 触发点 in.close(); } } class Human implements Serializable{ private String name; private Integer age; public Human() { } public Human(String name,Integer age){ this.namename; this.ageage; } private void readObject(ObjectInputStream in) throws Exception{ System.out.println( Human readObject triggered!); try { Runtime.getRuntime().exec(calc); // sink点 } catch (Exception e) { e.printStackTrace(); } } } class Student extends Human{ private String sclass; private Integer sno; public Student(String sclass,Integer sno){ super(); this.sclasssclass; this.snosno; } private void readObject(ObjectInputStream in) throws Exception{ System.out.println( Student readObject triggered!); try { Runtime.getRuntime().exec(calc); // sink点 } catch (Exception e) { e.printStackTrace(); } } }2.2、readObjectNoData()当jdk反序列化某个.ser字节流时如果反序列化到某个类时发现没有该类的数据如果该类中自定义了readObjectNoData()方法那么就会触发该类自定义的readObjectNoData()方法。如果该类没有自定义的readObjectNoData()方法那么jvm就什么都不会做直接跳过该类。readObjectNoData通常在反序列化涉及到继承链的情况下会被触发的多一些。比如Custom类其父类是Person类。在反序列化Custom类的.ser文件时.ser文件被修改过了导致里面没有Person类的字节码所以反序列化Custom类的.ser文件时会触发Person类中的readObjectNoData()方法。class Person implements Serializable { private static final long serialVersionUID 5652464866930818765L; private void readObjectNoData() throws ObjectStreamException{ System.out.println( Person 的 readObjectNoData 被触发); } private void readObject(ObjectInputStream in) throws Exception{ System.out.println( Person 的 readObject 被触发!); try { Runtime.getRuntime().exec(calc); // sink点 } catch (Exception e) { e.printStackTrace(); } } } class Custom extends Person { private static final long serialVersionUID 5652464866930818765L; private void readObjectNoData() throws ObjectStreamException{ System.out.println( Custom 的 readObjectNoData 被触发); } private void readObject(ObjectInputStream in) throws Exception{ System.out.println( Custom 的 readObject 被触发!); try { Runtime.getRuntime().exec(calc); // sink点 } catch (Exception e) { e.printStackTrace(); } } }Step1、先对Person进行序列化生成序列化文件person.serObjectOutputStream out new ObjectOutputStream( new FileOutputStream(person.ser)); out.writeObject(new Person()); out.close();Step2、使用HxD修改person.ser文件把里面的Person类名替换为Custom注意替换的类名要等长这是为了不破坏.ser文件内容。Custom类继承自Person类且两个类的序列化id是一样的。Step3、对修改好后的person.ser文件进行反序列化因为readObjectNoData会在“某个类被反序列化但没有对应数据”时触发。//再对person.ser进行反序列化 ObjectInputStream in new ObjectInputStream( new FileInputStream(person.ser)); in.readObject(); // 触发点 in.close();jvm反序列化一个对象时会先处理当前类CustomCustom有数据所以正常被反序列化不会调用readObjectNoData()。再去处理父类PersonPerson没有数据反序列化Person时会调用readObjectNoData()。2.3、readExternal()当一个类实现了 Externalizable 接口时在反序列化过程中JVM 不会调用 readObject()而是直接调用该类的 readExternal() 方法。同理在序列化的时候直接调用该类的 writeExternal() 方法。public class ReadExternalTest { public static void main(String[] args) throws IOException, ClassNotFoundException { Food foodnew Food(红色); //先对person进行序列化为person.ser ObjectOutputStream out new ObjectOutputStream( new FileOutputStream(food.ser)); out.writeObject(food); out.close(); ObjectInputStream innew ObjectInputStream(new FileInputStream(food.ser)); in.readObject(); in.close(); } } class Food implements Externalizable, Serializable { private String colour; public Food() { } public Food(String colour) { this.colour colour; } Override public void writeExternal(ObjectOutput out) throws IOException { System.out.println( Food 的 writeExternal 被触发); } Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { System.out.println( Food 的 readExternal 被触发); } }2.4、readResolve()readResolve方法是在反序列化的最后一步执行通常是跟着readObject()方法之后执行的用于返回一个新的对象替换原来反序列化的对象。1不使用readResolve()方法class Fruit implements Serializable { private String shape; public Fruit(String shape) { this.shape shape; } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { System.out.println( Fruit的readObject 被调用); // 从 classdata 中恢复字段值不调用这个方法的化类中所有字段都是默认值 in.defaultReadObject(); } Override public String toString() { return Fruit{ shape shape \ }; } }Fruit fruitnew Fruit(圆); ObjectOutputStream outputStreamnew ObjectOutputStream (new FileOutputStream(fruit.ser)); outputStream.writeObject(fruit); outputStream.close(); ObjectInputStream inputStreamnew ObjectInputStream (new FileInputStream(fruit.ser)); Object objinputStream.readObject(); Fruit fruit1(Fruit)obj; System.out.println(fruit1.toString()); inputStream.close();反序列化后得到的还是原来的Fruit对象。2使用readResolve()方法class Fruit implements Serializable { private String shape; public Fruit(String shape) { this.shape shape; } private Object readResolve() throws ObjectStreamException { System.out.println( Fruit的readResolve 被调用); // 返回一个“替代对象” return new Food(红色); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { System.out.println( Fruit的readObject 被调用); // 从 classdata 中恢复字段值不调用这个方法的化类中所有字段都是默认值 in.defaultReadObject(); } Override public String toString() { return Fruit{ shape shape \ }; } }Fruit fruitnew Fruit(圆); ObjectOutputStream outputStreamnew ObjectOutputStream (new FileOutputStream(fruit.ser)); outputStream.writeObject(fruit); outputStream.close(); ObjectInputStream inputStreamnew ObjectInputStream (new FileInputStream(fruit.ser)); Object objinputStream.readObject(); Food food(Food)obj; System.out.println(food.toString()); inputStream.close();反序列化后得到的是新的Food对象。readResolve方法通常会用在单例模式中即在反序列化一个类对象时不产生新的对象还是返回原来的那个对象。2.5、validateObject()validateObject在是整个反序列化流程的最后一步通常是用于在整个对象反序列化完成之后对对象进行最终的一致性验证或修复。class Shop implements ObjectInputValidation, Serializable { private String address; public Shop() { } public Shop(String address) { this.address address; } private void readObject(ObjectInputStream in) throws Exception { System.out.println( Shop的readObject 被调用); in.defaultReadObject(); // 注册回调validateObject方法this回调对象为当前对象0表示优先级 in.registerValidation(this, 0); } Override public void validateObject() throws InvalidObjectException { System.out.println( Shop的validateObject 被调用); // 如果对象的address属性为空则抛出异常 if(this.addressnull || this.address.equals()){ throw new InvalidObjectException(address不能为null); } } private Object readResolve() throws ObjectStreamException { System.out.println( Shop的readResolve 被调用); // 返回一个“替代对象” return new Food(红色); } }// Shop shopnew Shop(大街); Shop shopnew Shop(); ObjectOutputStream outputStreamnew ObjectOutputStream (new FileOutputStream(shop.ser)); outputStream.writeObject(shop); outputStream.close(); ObjectInputStream inputStreamnew ObjectInputStream (new FileInputStream(shop.ser)); Food food(Food)inputStream.readObject(); System.out.println(food); inputStream.close();因此反序列化的一般执行顺序是readObject()——readObjectNoData没有类数据——readExternal如果实现了Externalizable接口——readResolve()——validateObject3、其他反序列化入口除了jdk原生的反序列化入口方法还有其他流行的java反序列化协议提供的接口例如Hession协议的Map.put()等。3.1、T3/IIOP中间件协议基于网络的jdk反序列化即远程调用协议RMI。在传输对象时自动触发jdk反序列化。T3是WebLogic的私有协议。WebLogic是一款java应用服务器。IIOP是一款通信协议。客户端发送请求请求数据中包含了序列化对象给服务器服务器通过T3/IIOP协议解析请求数据提取对象字节流反序列化然后就会触发readObject()、readResolve()等一系列入口方法。入口方法同jdk原生的入口方法一样。3.2、HessianHessian是一种二进制序列化协议http://hessian.caucho.com/ 主要用于RPC远程调用dubbo等。dependency groupIdcom.caucho/groupId artifactIdhessian/artifactId version4.0.66/version /dependencypublic class HessianTest { public static void main(String[] args) throws IOException { HashMapObject,Object hashMapnew HashMap(); // 构造map,map.put()方法会触发hashCode方法。 hashMap.put(new SUser(zhangsan,123),SUser); // 序列化SUser对象 HessianOutput hessianOutputnew HessianOutput(new FileOutputStream(suser.ser)); hessianOutput.writeObject(hashMap); // hessianOutput.writeObject(new SUser()); hessianOutput.close(); // 反序列化suser.bin反序列化时Hessian创建新的HashMap时会用到map.put()方法会触发hashCode方法 // Hessian 在反序列化 Map 时会调用 put() put() 会触发 hashCode() // HashMap在put一个新元素时需要 hashCode 来决定存储位置如果发生hash冲突还会触发equals方法 HessianInput hessianInputnew HessianInput(new FileInputStream(suser.ser)); Object objecthessianInput.readObject(); System.out.println(object); } } class SUser implements Serializable { private String username; private String password; public SUser() { } public SUser(String username, String password) { this.username username; this.password password; } Override public int hashCode(){ System.out.println( SUser 的 hashCode 被触发); return 1; } Override public String toString() { return SUser{ username username \ , password password \ }; } }注意这里第一个hashCode方法是在hashMap.put(new SUser(zhangsan,123),SUser);时会触发。第二个hashCode方法是Hessian 在反序列化 Map 时会调用 put() put() 再触发 hashCode()。3.3、Hessian-liteHessian的精简版。3.4、Hessian-sofaHessian的增强版。3.5、xstreamxstream是一个将java对象与xml相互转换的序列化框架。!-- Source: https://mvnrepository.com/artifact/com.thoughtworks.xstream/xstream -- dependency groupIdcom.thoughtworks.xstream/groupId artifactIdxstream/artifactId version1.4.15/version scopecompile/scope /dependency !-- Source: https://mvnrepository.com/artifact/xpp3/xpp3 -- dependency groupIdxpp3/groupId artifactIdxpp3/artifactId version1.1.4c/version scopecompile/scope /dependencypublic class XstreamTest { public static void main(String[] args) { XStream xStreamnew XStream(); Pet petnew Pet(Tom,白色); //序列化对象——xml String xmlxStream.toXML(pet); System.out.println(xml); //反序列化xml——对象 Object objectxStream.fromXML(xml); System.out.println(object); HashMapObject,Object hashMapnew HashMap(); hashMap.put(pet,pet); String mapXmlxStream.toXML(hashMap); Object objMapxStream.fromXML(mapXml); System.out.println(objMap); } } class Pet implements Serializable { private String name; private String color; public Pet() { } public Pet(String name, String color) { this.name name; this.color color; } private void readObject(ObjectInputStream in) throws Exception{ System.out.println( Pet readObject triggered!); in.defaultReadObject(); } Override public int hashCode(){ System.out.println( Pet 的 hashCode 被触发); return 1; } Override public String toString() { return Pet{ name name \ , color color \ }; } }4、总结最近在搞反序列化漏洞检测的研究先梳理一下常见的java反序列化入口方法。5、参考资料https://ieeexplore.ieee.org/document/10646692

更多文章