JAVA安全-05反序列化篇(1)

JAVA安全-05反序列化篇(1)

序列化与反序列化

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

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

计算机的早期只是单机作战,没有传输的概念。随着计算机的发展,发展成了多个计算机之间分布式通信,在网络上所有的通信相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。当两个Java进程进行通信时,实现进程间的对象传送,就需要Java序列化与反序列化了。

序列化实现

只有实现了Serializable或者Externalizable接口的类的对象才能被序列化为字节序列。(不是则会抛出异常)

Serializable 接口

public interface Serializable {
}

Serializable 用来标识当前类可以被 ObjectOutputStream 序列化,以及被 ObjectInputStream 反序列化。

Serializable 接口的基本使用

通过 ObjectOutputStream 将需要序列化数据写入到流中,因为 Java IO 是一种装饰者模式,因此可以通过 ObjectOutStream 包装 FileOutStream 将数据写入到文件中或者包装 ByteArrayOutStream 将数据写入到内存中。同理,可以通过 ObjectInputStream 将数据从磁盘 FileInputStream 或者内存 ByteArrayInputStream 读取出来然后转化为指定的对象即可。

代码实现

定义一个Person类进行测试。

import java.io.Serializable;

public class Person implements Serializable {
    public String name;
    public int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

测试类

把序列化和反序列化写在一个文件进行测试。

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializableTest {
    public static void main(String[] args) throws Exception {
        Serialize();
        Unserialize();



    }

    private static void Serialize() throws Exception{
        Person p1 = new Person("Roderick",100);
        System.out.println("序列化前"+p1.toString());
        System.out.println("=================开始序列化================");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("/Users/roderick/IdeaProjects/webtest/Serialize/src/main/java/ptest"));
        oos.writeObject(p1);
        oos.flush();
        oos.close();
    }

    private static void Unserialize() throws Exception{
        System.out.println("=================开始反序列化================");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/Users/roderick/IdeaProjects/webtest/Serialize/src/main/java/ptest"));
        Person p2 = (Person) ois.readObject();
        ois.close();
        System.out.println(p2);

    }


}

ObjectOutputStream代表对象输出流:它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中
ObjectInputStream代表对象输入流:它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
很显然,writeObject和readObject是序列化和反序列化的关键。

使用SerializationDumper查看序列化文件,aced字符串直接文本打开序列化文件可以拿到

java -jar SerializationDumper-v1.13.jar aced000573720006506572736f6e2ea784aa92bdd6240200024900036167654c00046e616d657400124c6a6176612f6c616e672f537472696e673b787000000064740008526f64657269636b
STREAM_MAGIC - 0xac ed
STREAM_VERSION - 0x00 05
Contents
  TC_OBJECT - 0x73
    TC_CLASSDESC - 0x72
      className
        Length - 6 - 0x00 06
        Value - Person - 0x506572736f6e
      serialVersionUID - 0x2e a7 84 aa 92 bd d6 24
      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)100 - 0x00 00 00 64
          name
            (object)
              TC_STRING - 0x74
                newHandle 0x00 7e 00 03
                Length - 8 - 0x00 08
                Value - Roderick - 0x526f64657269636b

可以看到,classdata存储类的相关数据,序列化重新取出使用。

安全问题

为什么会产生安全问题?

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

传递类产生漏洞的形式:

1)入口类的reaadObject直接调用危险方法。Person类加入readObject重写方法,reaadObject(反序列化)调用命令执行方法。

import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.Serializable;

public class Person implements Serializable {
    public String name;
    public int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    private void readObject(ObjectInputStream ois) throws Exception{
        ois.defaultReadObject(); //该方法从该流中读取当前类的非静态和非瞬态字段。
        Runtime.getRuntime().exec("open -a Calculator");
        System.out.println("命令执行成功!");
    }

}


2)入口类参数中包含可控类,这个类有危险方法,readObject时调用。

​ 入口A HashMap接收参数O (O.func) ->目标类B URL->目标类调用B.func->A.readObject.invoke->B.func ==== URLDNS

3)入口类参数中包含可控类,该类又调用其他有危险的类,readObject时调用。

​ 目标类B.func

​ 入口A[O]->O.abc->B.func

​ O[B] invoke->B.func

​ O 是动态代理

4)构造函数/静态代码块等类加载时隐式执行。

上面的4个类别,入口类 Source 的共同的特征是:

  • 实现Serializable;
  • 重写readObject方法,调用一个常见的函数;
  • 参数类型宽泛;
  • 最好JDK自带;

下篇继续

posted @ 2022-05-12 18:08  九天揽月丶  阅读(197)  评论(0编辑  收藏  举报