java反序列化基础篇-序列化,反序列化

admin 2026-04-25 05:06:18 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文系统讲解Java序列化与反序列化机制,涵盖Serializable接口实现、ObjectOutputStream/ObjectInputStream使用、文件与字节流两种序列化方式。重点分析反序列化漏洞成因:重写readObject方法时若包含危险操作(如命令执行),攻击者可通过恶意序列化数据触发漏洞。文档通过完整代码示例演示漏洞触发过程,并指出反射机制是关键因素。 综合评分: 72 文章分类: 漏洞分析,WEB安全,应用安全,安全开发,代码审计


cover_image

java反序列化基础篇-序列化,反序列化

原创

三呼呼 三呼呼

古月安全

2026年3月18日 17:31 四川

在小说阅读器读本章

去阅读

概念:Java序列化(Serialization)和反序列化(Deserialization)是Java平台中用于对象持久化(存储)和网络传输的重要机制。

序列化(Serialization)

序列化是指将Java对象转换为字节序列的过程,以便:

  • 将对象状态保存到文件或数据库中
  • 通过网络传输对象
  • 在JVM之间传递对象

反序列化(Deserialization)

反序列化是序列化的逆过程,将字节序列重新构造成Java对象。

概念解析

要实现对象的序列化和反序列化,必须实现Serializable 接口。

序列化使用ObjectOutputStream ,反序列化使用ObjectInputStream他们都是类 ,用于将Java对象与字节流之间的转化(序列化–反序列化)

序列化的完整过程需要‌同时使用ObjectOutputStream对象和它的writeObject方法

反序列化的完整过程需要‌同时使用ObjectIutputStream对象和它的readObject方法,这2个方法执行实际的序列化和反序列化操作:一般用法如下:括号中的outputStream就是实际通过哪种输出方式进行序列化,比如常见的文件输出流(FileOutputStream),输出到文件的方式:

ObjectOutputStream oos = new ObjectOutputStream(outputStream);

oos.writeObject(yourObject);

自定义序列化就是通过实现对writeObject和readObject方法的重写,从而实现自己的一些操作,反序列化漏洞出现的问题都是实现readObject过程中导致的。因为在实现自定义的方法内,执行了一些预料之外的方法。

序列化过程

首先需要一个可序列化的类–必须实现Serializable 接口:

import java.io.Serializable;public class User implements Serializable {    private int age;    private String name;    public String nickName;    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }}

然后进行序列化:分别使用两种方式,第一种用于存储通常用.ser格式,当然用其他的也可,因为本质是二进制格式内容,存储其他比如txt也是可以的,但是打开是乱码–延申阅读(可读的序列化对象一般是JSON/XML等序列化方式)

第二种是用于可传输的二进制方式,如下:

public class test {    public static void main(String[] args) throws IOException {        User user = new User();        //用于存储的序列化方式ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"));//文件输出流的方式,输出对象        oos.writeObject(user);        //序列化可传输的二进制方式--字节流形式ByteArrayOutputStream baos = new ByteArrayOutputStream();//字节数组输出流        ObjectOutputStream oosB = new ObjectOutputStream(baos);//对象输出流,参数--baos通过哪种方式输出对象,这里采用字节输出流的方式        oosB.writeObject(user);//通过上面的方式,序列化对象 user。        byte[] byteArray = baos.toByteArray();        for (byte b : byteArray) {            System.out.print(b);        }}

运行一下:可以看到一种存储的方式,将对象user存储在文件user.ser中,第二种通过二进制数组的方式存储在对象byteArray数组中,循环打印一下看内容。这也是经常在利用java反序列化的时候,会在http请求中,发送构造好的二进制字符串的常见方式。通常会采用其他编码方式绕过检测。

反序列化过程

反序列化就是将上面得到的两种格式,分别进行反序列化,还原成user对象的过程。使用的是readObject

.为了便于观察过程,我们先给user的属性进行赋值。再重新序列化一次,然后再反序列化看看结果:

public class test {    public static void main(String[] args) throws IOException, ClassNotFoundException {        User user = new User();        //对属性进行赋值操作        user.setAge(99);        user.nickName="嘿";        user.setName("Now");        //用于存储的序列化方式ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"));        oos.writeObject(user);        //序列化可传输的二进制方式ByteArrayOutputStream baos = new ByteArrayOutputStream();        ObjectOutputStream oosB = new ObjectOutputStream(baos);        oosB.writeObject(user);        byte[] byteArray = baos.toByteArray();        for (byte b : byteArray) {            System.out.print(b+" ");        }        //反序列化//通过读取存储的文件进行还原ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"));        Object o = ois.readObject();        System.out.println(o);    }}

反序列化解释,输出一下这个o对象,可以看到的是o对象是一个user类型,再看看

再打印一下他的属性,可以看到该对象的各个属性,就是我们赋值的。反序列化就是将二进制字节流还原对象的一个过程。

这里是通过文件流的方式还原,现在看看用于传输的字节流还原:反序列化的代码如下:为了便于理解,这里我直接将前面的输出字节数组里面的所有值,重新赋值到一个新的字节数组中,将字节数组给ByteArrayInputStream转换为可顺序读取的输入流,从而进行反序列化

byte[] bytes = {-84,-19,0,5,115,114,0,4,85,115,101,114,122,81,103,-76,-86,83,53,-27,2,0,3,73,0,3,97,103,101,76,0,4,110,97,109,101,116,0,18,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,59,76,0,8,110,105,99,107,78,97,109,101,113,0,126,0,1,120,112,0,0,0,99,116,0,3,78,111,119,116,0,3,-27,-104,-65};//定义字节数组,user序列化后的值ObjectInputStream oisByte = new ObjectInputStream(new ByteArrayInputStream(bytes));//Object oByte = oisByte.readObject();//反序列化System.out.println("字节流反序列化" + oByte.toString());

可以看到两种方式,都恢复了原始的user对象。在反序列化过程中是通过反射的方式进行对象还原的如下图关键代码

细节可自行了解。

反序列化进阶

前面我们说了出现漏洞的是对readObject方法的重写导致的,现在我们在自己的类里面重写一下这个方法:

首先看下重写规则:

  • 实现Serializable接口‌:需序列化的类必须实现该接口
  • 定义serialVersionUID‌:显式声明版本控制字段
  • 声明private方法‌:private void readObject(ObjectInputStream in)方法签名必须严格匹配

依次添加到User类里面,如下:

import java.io.ObjectInputStream;import java.io.Serializable;public class User implements Serializable {    private static final long serialVersionUID = 8813939971390125541L;    private int age;    private String name;    public String nickName;    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {        in.defaultReadObject();        System.out.println("User重写方法调用!");    }    public User() {    }    @Overridepublic String toString() {        return "User{" +                "age=" + age +                ", name='" + name + '\'' +                ", nickName='" + nickName + '\'' +                '}';    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }}

现在再次调用一下反序列化,看看发生生么,为了方便查看,另外单独写了一个server测试类,并且将反序列化操作封装到deserializeObject方法中:

import java.io.ByteArrayInputStream;import java.io.IOException;import java.io.ObjectInputStream;public class server {    public static void main(String[] args) throws IOException, ClassNotFoundException {        byte[] bytes = {-84,-19,0,5,115,114,0,4,85,115,101,114,122,81,103,-76,-86,83,53,-27,2,0,3,73,0,3,97,103,101,76,0,4,110,97,109,101,116,0,18,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,59,76,0,8,110,105,99,107,78,97,109,101,113,0,126,0,1,120,112,0,0,0,99,116,0,3,78,111,119,116,0,3,-27,-104,-65};        Object o = deserializeObject(bytes);    }    public static Object deserializeObject(byte[] data) throws IOException, ClassNotFoundException {        ByteArrayInputStream byteIn = new ByteArrayInputStream(data);        ObjectInputStream objIn = new ObjectInputStream(byteIn);        return objIn.readObject();    }}

运行一下:发现反序列化过程中,user类的重写的方法被调用了,当类重写readObject方法时,反序列化过程会在调用ObjectInputStream的readObject方法过程中调用重写的readObject。若该方法包含危险操作(如动态加载类、执行命令或反射调用敏感方法),攻击者通过构造恶意序列化数据即可触发漏洞

可以看到重写的方法被调用了,在调用系统的readObject方法过程中,会通过反射的方式,进行调用重写的readObject。如下关键代码:有兴趣可自行了解。

结束————————————-


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:古月安全 三呼呼 三呼呼《java反序列化基础篇-序列化,反序列化》

评论:0   参与:  0