Java原生序列化与反序列化

打印 上一主题 下一主题

主题 922|帖子 922|积分 2766

序列化与反序列化

Java序列化是指把Java对象转换为字节序列的过程;而Java反序列化是指把字节序列恢复为Java对象的过程。
为什么需要序列化?

序列化分为两大部分:序列化和反序列化。序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。恢复数据要求有恢复数据的对象实例。
序列化为什么会产生安全问题?

只要服务端反序列化数据,客户端传递类的readObject中代码会自动执行,给予攻击者在服务器上运行代码的能力。
Java序列化基本流程

有如下三个文件,Person.java:
  1. import java.io.Serializable;
  2. public class Person implements Serializable {
  3.     // 需要实现Serializable接口才可以序列化
  4.     private String name;
  5.     private int age;
  6.     public Person(){
  7.     }
  8.     public Person(String name, int age) {
  9.         this.name = name;
  10.         this.age = age;
  11.     }
  12.     @Override
  13.     public String toString() {
  14.         return "Person{" +
  15.                 "name='" + name + '\'' +
  16.                 ",age=" + age +
  17.                 '}';
  18.     }
  19. }
复制代码
序列化操作,SerializationTest.java:
  1. import java.io.FileOutputStream;
  2. import java.io.IOException;
  3. import java.io.ObjectOutputStream;
  4. public class SerializationTest {
  5.     public static void serialize(Object obj) throws IOException {
  6.         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
  7.         oos.writeObject(obj);
  8.     }
  9.     public static void main(String[] args) throws Exception{
  10.         Person person = new Person("a", 18);
  11.         serialize(person);
  12.     }
  13. }
复制代码
反序列化操作,UnserializeTest.java:
  1. import java.io.FileInputStream;
  2. import java.io.IOException;
  3. import java.io.ObjectInputStream;
  4. public class UnserializeTest {
  5.     public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
  6.         ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
  7.         Object obj = ois.readObject();
  8.         return obj;
  9.     }
  10.     public static void main(String[] args) throws Exception {
  11.         Person person = (Person) unserialize("ser.bin");
  12.         System.out.println(person);
  13.     }
  14. }
复制代码
SerializationTest中首先生成一个person对象,然后将生成的person对象进行序列化操作,得到二进制文件ser.bin,接着在UnserializeTest中实现反序列化操作得到person对象,并打印出来:
  1. Person{name='a',age=18}
复制代码
注意:

  • (1)想要序列化的对象需要实现Serializable接口才可以序列化。
  • (2)使用transient标识的对象不参与序列化。
    在Person类中,name属性之前加上transient,改为private transient String name;之后再次尝试序列化和反序列化,输出结果:Person{name='null',age=18}。
  • (3)静态成员变量不能被序列化,因为序列化是针对对象的,而静态成员变量属于类。
可能存在反序列化漏洞的形式


  • (1)入口类的readObject直接调用危险方法。
    Person类文件中重写了readObject方法,在readObject方法中执行命令:
  1. package org.example;
  2. import java.io.IOException;
  3. import java.io.ObjectInputStream;
  4. import java.io.Serializable;
  5. public class Person implements Serializable {
  6.     private transient String name;
  7.     private int age;
  8.     public Person(){
  9.     }
  10.     public Person(String name, int age) {
  11.         this.name = name;
  12.         this.age = age;
  13.     }
  14.     @Override
  15.     public String toString() {
  16.         return "Person{" +
  17.                 "name='" + name + '\'' +
  18.                 ",age=" + age +
  19.                 '}';
  20.     }
  21.     private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
  22.         ois.defaultReadObject();
  23.         Runtime.getRuntime().exec("calc");
  24.     }
  25. }
复制代码
再次执行SerializationTest和UnserializeTest实现序列化和反序列化,在反序列化时会调用重写的readObject,从而执行其中的命令,弹出计算器:


  • (2)入口类参数中包含可控类,该类有危险方法,readObject时调用。
    要实现反序列化攻击,入口类最好是继承Serializable,重写readObject,调用常见函数,参数类型宽泛,jdk自带,比如HashMap:

  • (3)入口类中包含可控类,该类又调用其他有危险的方法的类,readObject时调用。
  • (4)构造函数/静态代码块等类加载时隐式执行。
参考链接

[1] https://www.bilibili.com/video/BV16h411z7o9

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

自由的羽毛

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表