1.序列化简介
所谓的序列化过程,就是将某一个对象按照某种约定好的结构转换成二进制数据进行传输,这段二进制数据中就包含了数据类型、和值的描述信息。使用序列化和反序列化能够方便数据的传输和存储。Java中最常用的序列化和反序列化方法是java.io.ObjectOutputStream#writeObject
和java.io.ObjectInputStream#readObject
方法。
2.writeObject
java中一个对象能够被序列化首先它所属的类需要实现了java.io.Serializable
接口。该接口中没有定义任何方法,只是为了说明是否能够被序列化。例如这里自己编写一个Person类:
//Person.java import java.io.Serializable; public class Person implements Serializable{ private String name; private int age; public Person(String name,int age){ this.age = age; this.name = name; } public void printInfo(){ System.out.println(this.name); System.out.println(this.age); } }
之后可以实例化一个Person类的对象,我们就可以使用writeObject
方法来将这个对象进行序列化。
//testObject.java import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.util.Arrays; public class testObject { public static void main(String[] argv) throws Exception{ Person p = new Person("join",18); ByteArrayOutputStream baos = new ByteArrayOutputStream();//保存序列化结果的对象 ObjectOutputStream objout = new ObjectOutputStream(baos);//执行序列化的对象 objout.writeObject(p);//执行writeObject方法将对象p写入baos for (int i=0;i<baos.toByteArray().length;i++){ System.out.print(String.format("%02x", baos.toByteArray()[i]));//以十六进制形式输出序列化数据 } } }
执行完成后将会输出如下结果:
使用SerializationDumper
工具对输出16进制结果进行解析可以看到如下内容,可以看到这段序列化后的数据保存的主要内容就是字段的名称、类型、数值等信息:
λ java -jar SerializationDumper-v1.12.jar -f 1.txt STREAM_MAGIC - 0xac ed STREAM_VERSION - 0x00 05 Contents TC_OBJECT - 0x73 TC_CLASSDESC - 0x72 className Length - 6 - 0x00 06 Value - Person - 0x506572736f6e serialVersionUID - 0xd4 9c 72 9f 45 ae c4 3b newHandle 0x00 7e 00 00 classDescFlags - 0x02 - SC_SERIALIZABLE fieldCount - 2 - 0x00 02 Fields 0: Int - I - 0x49 fieldName Length - 3 - 0x00 03 Value - age - 0x616765 1: Object - L - 0x4c fieldName Length - 4 - 0x00 04 Value - name - 0x6e616d65 className1 TC_STRING - 0x74 newHandle 0x00 7e 00 01 Length - 18 - 0x00 12 Value - Ljava/lang/String; - 0x4c6a6176612f6c616e672f537472696e673b classAnnotations TC_ENDBLOCKDATA - 0x78 superClassDesc TC_NULL - 0x70 newHandle 0x00 7e 00 02 classdata Person values age (int)18 - 0x00 00 00 12 name (object) TC_STRING - 0x74 newHandle 0x00 7e 00 03 Length - 4 - 0x00 04 Value - join - 0x6a6f696e
3.readObject
readObject
方法属于ObjectInputStream
类。反序列化一个对象时,实例化ObjectInputStream
类的时候需要传入一个Inputstream
类型的参数来初始化该实例的值。反序列化Person的代码如下:
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class testObject { public static void main(String[] argv) throws Exception{ Person p = new Person("join",18); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream objout = new ObjectOutputStream(baos); objout.writeObject(p);//本行代码运行完成,序列化对象便写入了baos变量中 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream objin = new ObjectInputStream(bais); Person p2 = (Person)objin.readObject(); p2.printInfo(); } }
4.类中的writeObject和readObject方法
之所以会有各种反序列化漏洞的出现,主要就是因为java中在对某个对象进行序列化和反序列化的过程时,会自动调用该对象的writeObject
和readObject
方法。通过在对象中编写这两种方法,我们可以再序列化的输出流中自定义一段输出数据并在反序列化时读出。可以看出一个明显的区别就是PHP中如果我们想要把一段数据反序列化时存储起来,必须要添加一个属性,而这里则不需要。示例代码如下:
import java.io.*; public class Test1 { public static class Person implements Serializable { public String name; public int age; public Person(String name,int age){ this.name = name; this.age = age; } private void writeObject(ObjectOutputStream ObjOps) throws IOException { ObjOps.defaultWriteObject(); ObjOps.writeObject("Hi,this is writeObject!"); } private void readObject(ObjectInputStream ObjIs) throws IOException, ClassNotFoundException { ObjIs.defaultReadObject(); System.out.println(ObjIs.readObject()); } } public static void main(String[] args) throws IOException, ClassNotFoundException { Person p = new Person("join",18); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream objout = new ObjectOutputStream(baos); objout.writeObject(p); for(int i=0;i<baos.toByteArray().length;i++){ System.out.print(String.format("%x",baos.toByteArray()[i])); } ByteArrayInputStream bins = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream objin = new ObjectInputStream(bins); objin.readObject(); } }
使用SerializationDumper工具可以看到,这段我们自定义的数据存储在了objectAnnotation中。