Java序列化与反序列化 Java序列化是指把java对象转换为字节序列的过程。这一过程将数据分解成字节流,以便存储在文件中或是在网络中传输
Java反序列化是指把字节序列恢复为Java对象的过程。就是打开字节流并重构成对象
使用场景:
把内存中的对象保存到一个文件中或者数据库中的时候 
想用套接字在网络上传输对象的时候 
想通过RMI传输对象的时候 
 
主要序列化的用途就是传递和存储
序列化的实现 
将序列化的类实现Serializabel接口,不用实现任何方法,表明类的对象是可序列化的 
并且所有属性必须是可序列化的,如果可序列化的类不是基本数据类型,也不是String,比如自定义的类,那这个引用类型必须是可序列化的,否则会导致此类不能被序列化(transient关键字修饰的属性除外,不参与序列化过程) 
 
需要序列化的类
User.java
package  trrq;import  java.io.Serializable;public  class  User  implements  Serializable   {     private  String name;     public  String getName ()   {         return  name;     }     public  void  setName (String name)   {         this .name = name;     } }
 
序列化
UserSerializable.java
package  trrq;import  java.io.File;import  java.io.FileOutputStream;import  java.io.ObjectOutputStream;public  class  UserSerializable   {     public  static  void  main (String[] args)  throws  Exception  {         User user = new  User();         user.setName("trrq" );                  serialize(user);     }     public  static  void  serialize (User user)  throws  Exception {         FileOutputStream fout = new  FileOutputStream("user.ser" );         ObjectOutputStream out = new  ObjectOutputStream(fout);         out.writeObject(user);         out.close();         fout.close();         System.out.println("序列化完成" );     } }
 
运行上面这个序列化程序后,就会生成user.ser文件
我们可以看下这个文件结构,ACED表示魔术头,0005表示版本号
序列化的数据以魔术数字和版本号开头,这个值是在调用ObjectOutputStream时,由writeStreamHeader写入。开头几位一般当作Java序列化字节的特征
反序列化 
Serializable package  trrq;import  java.io.*;public  class  UserSerializable   {     public  static  void  main (String[] args)  throws  Exception  {         User user = new  User();         user.setName("trrq" );                           User user1 = unserialize();         System.out.println(user1.getName());     }     public  static  void  serialize (User user)  throws  Exception {         FileOutputStream fout = new  FileOutputStream("user.ser" );         ObjectOutputStream out = new  ObjectOutputStream(fout);         out.writeObject(user);         out.close();         fout.close();         System.out.println("序列化完成" );     }     public  static  User unserialize ()  throws  Exception {         File file;         FileInputStream fileln = new  FileInputStream("user.ser" );         ObjectInputStream in = new  ObjectInputStream(fileln);         User user = (User)in.readObject();         in.close();         fileln.close();         return  user;     } }
 
运行结果
读取序列化文件,将二进制文件重新恢复为user对象
Externalizable 通过实现Externalizable接口进行序列化和反序列化,但必须实现writeExternal、readExternal方法,并且还要实现一个类的无参构造方法 ,Serializable 接口可以不用实现。
用法与Serializable是一致的
package  trrq;import  java.io.Externalizable;import  java.io.IOException;import  java.io.ObjectInput;import  java.io.ObjectOutput;public  class  Evil  implements  Externalizable   {     public  Evil ()   {         System.out.println(this .getClass()+"的EvilClass的无参构造方法被调用" );     }          public  void  writeExternal (ObjectOutput out)  throws  IOException  {              }     public  void  readExternal (ObjectInput in)  throws  IOException, ClassNotFoundException  {     } }
 
readObject()方法 该方法在反序列化漏洞起了大作用,在反序列化过程中,JVM虚拟机试图调用对象类里的writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化,如果没有这些方法,默认调用是ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用户定义的writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。
Java反序列化的过程中可以自动执行序列化类的四个方法,实现了Serializable接口可以执行的方法包括readObject、readObjectNoData、readResolve,以及实现了Externalizable接口的readExternal方法。这些在找反序列化漏洞时都需要重点关注。
如果readObject方法书写不当的话就有可能引发恶意代码的执行,例如
package  trrq;import  java.io.*;public  class  EvilClass  implements  Serializable   {     String name;     public  EvilClass ()   {         System.out.println(this .getClass() + "的EvilClass()无参构造方法被调用!!!!!!" );     }     public  EvilClass (String name)   {         System.out.println(this .getClass() + "的EvilClass(String name)构造方法被调用!!!!!!" );         this .name = name;     }     public  String getName ()   {         System.out.println(this .getClass() + "的getName被调用!!!!!!" );         return  name;     }     public  void  setName (String name)   {         System.out.println(this .getClass() + "的setName被调用!!!!!!" );         this .name = name;     }     @Override      public  String toString ()   {         System.out.println(this .getClass() + "的toString()被调用!!!!!!" );         return  "EvilClass{"  +                 "name='"  + getName() + '\''  +                 '}' ;     }          private  void  readObject (ObjectInputStream in)  throws  Exception  {                  in.defaultReadObject();         System.out.println(this .getClass() + "的readObject()被调用!!!!!!" );                  Runtime.getRuntime().exec(new  String[]{"cmd" , "/c" , name});              } }
 
序列化与反序列化
package  trrq;import  java.io.ByteArrayInputStream;import  java.io.ByteArrayOutputStream;import  java.io.ObjectInputStream;import  java.io.ObjectOutputStream;public  class  EvilSerialize   {     public  static  void  main (String[] args)  throws  Exception  {         EvilClass evilObj = new  EvilClass();                  evilObj.setName("calc" );                           byte [] bytes = serializeToBytes(evilObj);                  EvilClass o = (EvilClass)deserializeFromBytes(bytes);         System.out.println(o);     }     public  static  byte [] serializeToBytes(final  Object obj) throws  Exception {         final  ByteArrayOutputStream out = new  ByteArrayOutputStream();         final  ObjectOutputStream objOut = new  ObjectOutputStream(out);         objOut.writeObject(obj);         objOut.flush();         objOut.close();         return  out.toByteArray();     }     public  static  Object deserializeFromBytes (final  byte [] serialized)  throws  Exception  {         final  ByteArrayInputStream in = new  ByteArrayInputStream(serialized);         final  ObjectInputStream objIn = new  ObjectInputStream(in);         return  objIn.readObject();     } }
 
成功弹窗
在真实场景中,不会有人会写命令执行在readObject()中,反序列化通常会需要java的特性进行配合,然后通过利用链的寻找。反序列化漏洞需要三个东西