avatar

cindahy

A text-focused Halo theme

  • 首页
  • 文章分类
  • 项目
  • 关于
Home Java反序列化基础1
文章

Java反序列化基础1

Posted 2025-08-11 Updated 2025-08- 12
By Administrator
28~36 min read

参考文章

java序列化与反序列化全讲解

Java反序列化基础篇-01-反序列化概念与利用

Java反序列化漏洞专题-基础篇

概述

序列化与反序列化

Java序列化:Java对象->字节序列

Java反序列化:字节序列->Java对象


为什么需要序列化与反序列化

为了传输数据,在两个java进程之中进行通信

序列化:将对象的状态保存到存储介质中或通过网络传输

反序列化:从存储介质或网络接收的数据重建对象


序列化与反序列化的应用场景

  • 想把内存中的对象保存到一个文件或者数据库中的时候

  • 想用套接字在网络上传送对象的时候

  • 想通过RMI传输对象的时候

样例代码解析

代码

package org.example;

import java.io.Serializable;

public class Person implements Serializable {

    private String name;
    private int age;

    public Person(){

    }
    // 构造函数
    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }

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



import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializationTest {
    public static void serialize(Object obj) throws IOException{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/main/ser.bin"));
        oos.writeObject(obj);
    }

    public static void main(String[] args) throws Exception{
        Person person = new Person("aa",22);
        System.out.println(person);
        serialize(person);
    }
}
package org.example;


import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class UnserializeTest {
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }

    public static void main(String[] args) throws Exception{
        Person person = (Person)unserialize("src/main/ser.bin");
        System.out.println(person);
    }
}

运行结果

SerializationTest

UnserializeTest

详解

Persion类

implements Serializable:这是一个标记接口,表示当前类可以被 ObjectOutputStream 序列化,以及被 ObjectInputStream 反序列化。

public class Person implements Serializable{}

如果去掉则会报错

SerializationTest类(序列化)

这里把序列化操作封装咋子serialize方法中,传入java对象之后,创建ObjectOutputStream对象,它包装了一个FileOutputStream,而FileOutputStream用于写入到文件

再通过 oos.writeObject(obj)将传入的对象写入到输出流中,进行序列化。

// 创建ObjectInputStream,包装FileInputStream
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
// 从输入流读取对象
Object obj = ois.readObject();
oos.writeObject(obj);
// 自动关闭流(try-with-resources语法)

UnserializeTest类(反序列化)

ObjectInputStream 是Java对象反序列化的核心类,它可以从字节流重建Java对象,将FileInputStream包装到ObjectInputStream,从输入流中读取对象后返回。

// 创建ObjectInputStream,包装FileInputStream
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
// 从输入流读取对象
Object obj = ois.readObject();
return obj;  // 返回反序列化的对象

Serializable 接口的特点

序列化类的属性没有实现 Serializable接口 那么再序列化就会报错

将原来的 implements Serializable接口删掉就会出现此报错

在反序列化过程中,它的父类如果没有实现序列化接口,那么将需要提供无参构造函数来重新创建对象。

Animal 是父类,它没有实现 Serilizable 接口

public class Animal {
    private String color;
 
    public Animal() {//没有无参构造将会报错
        System.out.println("调用 Animal 无参构造");
    }
 
    public Animal(String color) {
        this.color = color;
 
            System.out.println("调用 Animal 有 color 参数的构造");
    }
 
    @Override
    public String toString() {
        return "Animal{" +
                "color='" + color + '\'' +
                '}';
    }
}

BlackCat 是 Animal 的子类

public class BlackCat extends Animal implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
 
    public BlackCat() {
        super();
        System.out.println("调用黑猫的无参构造");
    }
 
    public BlackCat(String color, String name) {
        super(color);
        this.name = name;
        System.out.println("调用黑猫有 color 参数的构造");
    }
 
    @Override
    public String toString() {
        return "BlackCat{" +
                "name='" + name + '\'' +super.toString() +'\'' +
                '}';
    }
}

输出结果

由此执行结果可知,如果序列化的对象的父类Animal没有实现序列化接口,那么再反序列化时就会调用对应的无参构造方法,这样做的目的时重新初始化父类的属性,例如 Animal 因为没有实现序列化接口,因此对应的 color 属性就不会被序列化,因此反序列得到的 color 值就为 null。

一个实现 Serializable 接口的子类也是可以被序列化的

静态成员变量是不能被序列化

序列化是针对对象属性的,而静态成员变量是属于类的。

transient 标识的对象成员变量不参与序列化

//改为
private transient String name;

这里可以看到反序列化中的name变成了nulll。

Java反序列化的安全问题

根据开发需要的不同可以通过writeObject和readObject 方法自定义序列化过程

为什么会产生安全问题

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

可能的形式

入口类的readObject直接调用危险方法

在之前的Persion类中添加readObject方法

 private void readObject(ObjectInputStream ois) throws IOException,ClassNotFoundException{
        ois.defaultReadObject();
        Runtime.getRuntime().exec("calc");
    }

如此在反序列化 的过程中就会自动执行readObject方法,如果里面有恶意代码就会导致恶意执行

同时以下情况也会触发java反序列化漏洞

入口类参数中包含可控类,该类有危险方法,readObject时调用

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

比如类型定义为Object,调用equals/hashcode/toString

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

产生漏洞的攻击路线

可利用的共同条件

继承Serializable

入口类source:(重写readObject 调用常见的函数 参数类型宽泛 最好jdk自带)例如Map类

调用链 gadget chain 相同名称,相同类型

执行类sink (rce ssrf文件等等)最重要 比如exec这种函数

这里以HashMap作为示例,跟踪到实现

说明HashMap继承了Serializable接口,满足了第一条件

再在它的方法中找到了重写的readObject

在readObject方法中找到

再到

这里的hashCode在Object类中,满足我们调用常见函数,函数类型宽泛的条件。、

综上所述,HashMap是一个很好的入口类

URLDNS实战

https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java

利用链

如果要尝试ssrf漏洞的话就可以想到URL类。

可以看到URL继承了Serializable接口,

openConnection方法跟踪下去会发现太复杂,不好利用,且此函数方法少见,而调用的时候需要用同名函数替换。

所以要找一个常见的函数比如说hashcode,一步步跟踪

getHostAddress:根据域名来获取地址。下面就会有域名解析类的工作

所以说,如果调用URL类的hashcode函数就会一个dns请求,就可以验证是否存在漏洞。

复现

在SerializationTest.java 文件下添加如下代码

这里的url我用bp生成的来接收dns

public static void main(String[] args) throws Exception{
        Person person = new Person("aa",22);
        HashMap<URL,Integer> hashmap=new HashMap<URL,Integer>();
        hashmap.put(new URL("http://7v3spzdbjo1uiz4h1b0013qufllb90.oastify.com"),1);
        HashMap a=new HashMap<>();
        //System.out.println(person);
        serialize(hashmap);
    }

按道理来说这次是序列化,应该不会发送dns请求的,但是我们还是接收到了

那为什么序列化的时候也会发送dns请求呢

我们首先翻看一下hashmap的put方法

这里发现就直接调用hash,进而调用了hashcode。

我们翻看一下URL对象的hashcode方法

这里hashcode被初始化为-1

所以刚开始hashcode初始化为-1的时候,hashmap.put就发送了dns请求,并且运行完之后hashcode的值变化,反序列化时反而不能触发dns。

如果要解决这个问题的话,我们就需要在序列化时

 public static void main(String[] args) throws Exception{
        Person person = new Person("aa",22);
        HashMap<URL,Integer> hashmap=new HashMap<URL,Integer>();
     //这里不要发送请求   
     hashmap.put(new URL("http://7v3spzdbjo1uiz4h1b0013qufllb90.oastify.com"),1);
     //这里要把hashcode改回-1   
     //通过反射改变一个已有对象的属性
     HashMap a=new HashMap<>();
        //System.out.println(person);
        serialize(hashmap);
    }

这里需要用到java的反射,所有poc还是放到下一篇吧。

java反序列化
java反序列化
License:  CC BY 4.0
Share

Further Reading

Oct 9, 2025

Java反序列化-RMI的几种攻击方式

RMI的基本攻击方式 RMI Client打RMI Registry RMI Client打RMI Server RMI Client 打RMI Registry 与注册中心的交互主要是这句话 Naming.bind("rmi://127.0.0.1:1099/sayHello", new Remo

Sep 28, 2025

Java反序列化-RMI流程分析

概述 官方文档:https://docs.oracle.com/javase/tutorial/rmi/overview.html RMI应用程序通常由两个独立的程序组成,一个服务器和一个客户端。服务端通过绑定这个远程对象类,它可以封装网络操作。客户端层面上只需要传递一个名字,还有地址。RMI提供了

Sep 21, 2025

Shiro反序列化漏洞-Shiro550

环境搭建 tomcat8.5.81 JDK1.7下载地址 https://www.oracle.com/java/technologies/javase/javase7-archive-downloads.html 下载shrio对应的war包 https://github.com/jas502n/

OLDER

Java反序列化基础2——java反射+URLDNS链

NEWER

java-反序列化基础3——JDK动态代理

Recently Updated

  • 常见安全产品整理(防火墙,WAF,EDR)
  • ELK从入门到实践
  • bp+mumu模拟器app抓包
  • xray漏扫工具
  • Java反序列化-RMI的几种攻击方式

Trending Tags

安全运营 文件上传 php反序列化 xss csrf ssrf xxe sql php 白帽子讲web安全

Contents

©2025 cindahy. Some rights reserved.

Using the Halo theme Chirpy