java反序列化基础

温馨提示:点击页面下方以展开或折叠目录~

java反序列化基础

  • 序列化:将对象转化为字节流以便传递
  • 反序列化:序列化逆过程,字节流转化为对象
  • 漏洞成因:反序列化类中有执行系统命令的代码,而没有对序列化字节流进行检测
  • 工具ysoserial

序列化与反序列化

  • 实体类实现java.io.Serializable接口

  • serialVersionId用于标识序列化类的版本,防止类名相同导致反序列化错误

  • 静态属性(static)、transient修饰的属性不参与序列化过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // 实体类
    public class User implements Serializable {
    public static final long serialVersionUID = 1L;
    public String name;
    public int id;
    public String sex;

    public User() {
    this("alice",123, "female");
    }

    public User(String name, int id, String sex) {
    this.name = name;
    this.id = id;
    this.sex = sex;
    }

    private void writeObject(ObjectOutputStream s) throws IOException {
    s.defaultWriteObject();
    }

    private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
    s.defaultReadObject();
    }
    }
    • 序列化通过ObjectOutputStream.writeObject(Object)实现
    • 反序列化通过ObjectInputStream.readObject(Object)实现
    • java在序列化和反序列化时,若实体类实现了Serializable接口,则会调用其中的writeObjectreadObject方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // Serialize是自己编写的接口
    public interface Serialize {

    void output(Object obj,String file) throws IOException;

    void input(String file) throws IOException, ClassNotFoundException;

    }
    // SerializeImpl实现了自己写的接口
    public class SerializeImpl implements Serialize {

    public void output(Object obj, String file) throws IOException {
    ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(file));
    outputStream.writeObject(obj);
    outputStream.close();
    }

    public void input(String file) throws IOException, ClassNotFoundException {
    ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file));
    Object outUser = inputStream.readObject();
    inputStream.close();
    System.out.println(outUser);
    }
    }
  • 序列化文件

image

serialVersionUID详解

  • Java序列化机制通过判断类的serialVersionId来验证版本是否一致

  • serialVersionUID可以显式声明,一般默认声明为1L;若不显式声明,则java序列化机制会根据Class自动生成一个serialVersionId

  • 显式声明:public static final long serialVersionUID= 1L;

声明与否的几种情况

  • 当不做显式声明时,会自动生成一个serialVersionUID

情况一不做显式声明的实体类序列化后修改实体类再反序列化

结果:抛出异常Exception in thread "main" java.io.InvalidClassException: test.User; local class incompatible: stream classdesc serialVersionUID = 592275709804988011, local class serialVersionUID = -7989443708455138686

情况二有显式声明的实体类序列化后修改实体类(减少属性)再反序列化

  • 使用两个项目表示A端(序列化端)B端(反序列化端)
  • A端的实体类User比B端多一个属性,其他均一致

结果:B端成功反序列化,执行了B端自己的toString()方法

  • 左边为A端,右边为B端
  • 控制台输出为各自序列化同一个字节流文件的结果,可以看到B端在缺少一个属性的情况下仍然序列化成功

image

情况三有显示声明的实体类序列化后修改实体类(增加属性)再反序列化

  • 和上面反过来,B端序列化,A端反序列化,A端比B端多一个属性

结果:A端成功反序列化,执行了自己的toString(),多的属性被赋了null初始值

  • 左A右B,B序列化,A反序列化

image

情况四:任意一方没有显式声明

结果:抛出异常,因为没有声明的一方会自动生成一个serialVersionUID

定制序列化数据

  • 当我们不需要把整个类的所有属性都序列化时可以自定义需要序列化的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
private void writeObject(ObjectOutputStream s) throws IOException {
System.out.println("alice write");
s.writeUTF(name);
s.writeInt(id);
s.writeUTF(sex);
}

private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
System.out.println("alice read");
name = s.readUTF();
id = s.readInt();
sex = s.readUTF();
}
  • 反序列化时会按照顺序读出属性的值,如果没有按顺序赋值也可以反序列化成功,但是属性的值会错乱
  • 可以看到下面反序列化操作赋值顺序错乱,但是反序列化仍然会成功,只是赋值错乱而已

image

transient关键字

  • 被transient修饰的属性不会被序列化

image

  • 可以看到被transient修饰的aTransient反序列化后变为初始值null

image

  • transient修饰name,单个写入也一样,还是无法被序列化