序列化和反序列化

5.8不安全的反序列化

简介

Java 序列化及反序列化处理在基于Java 架构的Web应用中具有尤为重要的作用。例如位于网络两端、彼此不共享内存信息的两个Web应用在进行远程通信时,无论相互间发送何种类型的数据,在网络中实际上都是以二进制序列的形式传输的。为此,发送方必须将要发送的Java 对象序列化为字节流,接收方则需要将字节流再反序列化还原得到Java 对象,才能实现正常通信。当攻击者输入精心构造的字节流被反序列化为恶意对象时,就会造成一系列的安全问题。

序列化与反序列化

序列化是指将对象按照一定格式转化为字节流或字符串

反序列化是序列化的逆过程,将具有一定格式的字节流或字符串还原成对象。

Fastjson可以将对象转换成Json字符串,XMLDecoder 可以将XML字符串还原成字符串,所以也是序列化和反序列化。

序列化可以将对象转换成字节流后保存、传输。反序列化则可以将字节流转换成对象,注入进程序之中,也就是说,不加以控制的反序列化,可以在程序中注入任意一个对象。

Java原生的序列化和反序列化

JDK中将一个对象进行序列化时,默认调用的是writeObject,而反序列化则是调用readObject。当被类中有自定义的writeObject和readObject,则在序列化和反序列话的过程中调用自定义的方法。而一个类必须实现Serializable接口,来表示该类可以被序列化。如下一个实例Student类,实现了Serialiazable接口,所以它可以被序列化。序列化的过程默认调用writeObject,反序列化默认调用readObject,当然, 我们也可以重写该方法,自定义反序列化的过程。

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Student implements Serializable {
    String name = "";
    int age = 0;
    byte sex = 1;
    public Student(){}
    public Student(String name,int age,byte sex){
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

​    @Overridepublic String toString() {
​        return "Student{" +
​                "name='" + name + '\'' +
​                ", age=" + age +
​                ", sex=" + sex +
​                '}';
​    }

​    private void writeObject(ObjectOutputStream out) throws IOException, IOException {
​        out.defaultWriteObject();
​        System.out.println("自定义的序列化过程");
​    }
​    private void readObject(ObjectInputStream ins ) throws IOException, IOException, ClassNotFoundException {
​        ins.defaultReadObject();
​        System.outprintln("自定义的反序列化过程");
​    }
}
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Demo {
    public static void main(String[] args){
        Student stu = new Student("zhhhy",18, (byte) 1);
        saveStudent(stu);
    }
    public static void saveStudent(Student stu){
        try (
             ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("stu.txt"))) {
            oos.writeObject(stu);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Student getStudent(){
        try (
             ObjectInputStream ois = new ObjectInputStream(new FileInputStream("stu.txt"))) {
            Student student = (Student) ois.readObject();
            return student;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

序列化字节流的格式

序列化后的数据格式,使用SerializationDumper可以将字节序转换成可读的文本。

java -jar SerializationDumper-v1.13.jar -r stu.txt > result.txt

格式如下,具体字段内容,可以参考Panda师傅翻译的反序列化流格式的文章,也可以看先知社区上的这篇


STREAM_MAGIC - 0xac ed
STREAM_VERSION - 0x00 05
Contents
  TC_OBJECT - 0x73
    TC_CLASSDESC - 0x72
      className
        Length - 7 - 0x00 07
        Value - Student - 0x53747564656e74
      serialVersionUID - 0xa3 9c 4e 9d 18 72 ac 82
      newHandle 0x00 7e 00 00
      classDescFlags - 0x03 - SC_WRITE_METHOD | SC_SERIALIZABLE
      fieldCount - 3 - 0x00 03
      Fields
        0:
          Int - I - 0x49
          fieldName
            Length - 3 - 0x00 03
            Value - age - 0x616765
        1:
          Byte - B - 0x42
          fieldName
            Length - 3 - 0x00 03
            Value - sex - 0x736578
        2:
          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
      Student
        values
          age
            (int)18 - 0x00 00 00 12
          sex
            (byte)1 - 0x01
          name
            (object)
              TC_STRING - 0x74
                newHandle 0x00 7e 00 03
                Length - 5 - 0x00 05
                Value - zhhhy - 0x7a68686879
        objectAnnotation
          TC_ENDBLOCKDATA - 0x78

我们可以自定义序列化过程,例如可以在序列化的过程中给流写入内容

    private void writeObject(ObjectOutputStream out) throws IOException, IOException {
        out.defaultWriteObject();
        System.out.println("自定义的序列化过程");
        out.writeObject("Hello!!!!");
    }

得到的格式如下


STREAM_MAGIC - 0xac ed
STREAM_VERSION - 0x00 05
Contents
  TC_OBJECT - 0x73
    TC_CLASSDESC - 0x72
      className
        Length - 7 - 0x00 07
        Value - Student - 0x53747564656e74
      serialVersionUID - 0xa3 9c 4e 9d 18 72 ac 82
      newHandle 0x00 7e 00 00
      classDescFlags - 0x03 - SC_WRITE_METHOD | SC_SERIALIZABLE
      fieldCount - 3 - 0x00 03
      Fields
        0:
          Int - I - 0x49
          fieldName
            Length - 3 - 0x00 03
            Value - age - 0x616765
        1:
          Byte - B - 0x42
          fieldName
            Length - 3 - 0x00 03
            Value - sex - 0x736578
        2:
          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
      Student
        values
          age
            (int)18 - 0x00 00 00 12
          sex
            (byte)1 - 0x01
          name
            (object)
              TC_STRING - 0x74
                newHandle 0x00 7e 00 03
                Length - 5 - 0x00 05
                Value - zhhhy - 0x7a68686879
        objectAnnotation
          TC_STRING - 0x74
            newHandle 0x00 7e 00 04
            Length - 17 - 0x00 11
            Value - Hello???????????? - 0x48656c6c6fefbc81efbc81efbc81efbc81
          TC_ENDBLOCKDATA - 0x78

对比后会发现多了一些内容。写入的Hello在objectAnnotation块之中。以下是引用Panda师傅的文章

objectAnnotation:
 endBlockData endBlockData
 contents endBlockData contents endBlockData // 由 writeObject 或 writeExternal PROTOCOL_VERSION_2 编写的内容。
  这部分数据的内容和 classAnnotation 的数据结构是⼀致的;表该对象所属类中的 Annotation 的描述信息, endBlockData 为存储对象的数据块【 Data-Block 】的结束标记,为终⽌符, contents 表示该类中多个内容的⼀个集合【contents】

漏洞产生的必要条件

一条可以产生安全问题的利用链。

在程序中,通过方法调用、对象传递和反射机制等手段作为跳板,构造出一个能够产生安全问题的利用链,如:任意文件读取或写入、远程代码执行等漏洞。利用链又称作Gadget chain,由于利用链的构造往往由多个类对象组成,环环相扣就像一个链条。如下所示是CVE-2015-4582的利用链:

Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke() 
LazyMap.get()             
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

一个触发点。

反序列化过程是一个正常的业务需求,将正常的字节流还原成对象属于正常的功能。但是当程序中的某处触发点在还原对象的过程中,并且能够成功地执行构造出来的利用链,则会成为反序列化漏洞的触发点。

反序列化的漏洞形成需要上述条件全部得到满足,程序中仅有一条利用链或者仅有一个反序列化的触发点都不会造成安全问题,不能认定为漏洞。

posted @   猫鳍  阅读(132)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
点击右上角即可分享
微信分享提示