Java反序列化漏洞之基础补充

前言

之前基础学的懵懵懂懂的,到后面学到反序列化,写各种代码,就不知道写什么,为什么这么写,比如 Java 反射,放到序列化漏洞利用里,每次就是照搬来用,没怎么想过每一步为什么这么用(之前也许想过,不过忘了),也挺久没看 Java 了,现在来重温一下基础

反射

到现在已经调过了各种链子,感觉反射大部分都是给一些私有字段进行赋特殊值,从而达到触发利用链中某一节点,最终可以让整个链子通了

之前学调链子的时候,为了方便省事,也写过一个反射的工具类,用于修改私有字段

1
2
3
4
5
6
7
8
9
import java.lang.reflect.Field;

public class SetField {
public static void setField(Object obj,String fieldName,Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj,value);
}
}

简单看一下,就一个setField()方法,接收三个参数:obj(一个实例化对象)、fieldName(要修改的属性名)、value(修改后的值)

方法里的代码一个个看,先写一个Person

1
2
3
4
public class Person {
public String name = "Suzen";
private int age = 20;
}

1. 获取 Class 对象

第一步就是要获取到传入的对象的 Class 对象,获取的方法也有很多,我习惯用getClass()方法

1
2
3
4
5
6
public class ReflectionTest {
public static void main(String[] args) throws Exception{
Person person = new Person();
Class<?> personClass = person.getClass();
}
}

2. 获取类的属性

然后下一步就是继续获取要修改的属性字段了,比如age,这里用getDeclaredField()方法实现,可以获取到所有的字段,不论public还是private

1
Field field = personClass.getDeclaredField("age");

这里就可以进行一下简写,将上面这两步合并一起,可以减少变量,这里person对象的实例化就单独拿出来了

1
2
3
4
5
6
7
8
import java.lang.reflect.Field;

public class ReflectionTest {
public static void main(String[] args) throws Exception{
Person person = new Person();
Field field = person.getClass().getDeclaredField("age");
}
}

3. 绕过Java访问权限检查

私有字段是不能访问或修改的,所以一般都是用setAccessible(true)来进行绕过

1
field.setAccessible(true);

4. 修改字段值

现在就有权限进行修改了,使用set()方法,传入两个参数:要修改的对象、修改后的值

1
field.set(person, 18);

检验

get()方法来查看字段值,field.get(person);得到的是一个对象,要强转成 int 类型

1
2
int age = (int) field.get(person);
System.out.println(age);

先看一下修改前的,注释掉3、4步,会出现报错,因为没权限访问私有字段age

image-20260908153537536

只注释掉第4步,现在就有权限访问到了,可以获取修改前的值

image-20260908153632127

下面就是修改后的值

image-20260908153442943

序列化

拿以前写过的来看,这个方法就接收一个构造好的对象

1
2
3
4
5
6
public static void serialize(Object obj) throws Exception{
FileOutputStream fos = new FileOutputStream("ser.bin");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
oos.close();
}

1. 创建文件输出流

这一步是负责把字节写入文件,把序列化后的数据保存到一个文件中,FileOutputStream是文件输出流类,用于向文件写入字节数据,这里就是写入到ser.bin文件中

1
FileOutputStream fos = new FileOutputStream("ser.bin");

2. 创建对象输出流

这一步是把对象变成字节,ObjectOutputStream是对象输出流类,负责把 Java 对象转换为字节流

1
ObjectOutputStream oos = new ObjectOutputStream(fos);

3. 写入对象

调用writeObject()方法,来将传入的对象写入到流中

1
oos.writeObject(obj);

4. 关闭流

最后就是调用close()方法关闭流

1
oos.close();

检验

Person类实现Serializable接口,从而可以实现序列化

1
2
3
4
5
6
import java.io.Serializable;

public class Person implements Serializable {
public String name = "Suzen";
private int age = 20;
}

main()方法中调用serialize()方法,将person对象序列化,可以看到生成了一个ser.bin文件

image-20260908161010017

反序列化

1
2
3
4
5
6
public static void unserialize(String Filename) throws Exception{
FileInputStream fis = new FileInputStream(Filename);
ObjectInputStream ois = new ObjectInputStream(fis);
ois.readObject();
ois.close();
}

反序列化就是和序列化是相对应的,看着代码也很好理解,就不过多赘述

Base64编码

很多时候会需要把序列化后的数据base64编码,所以就也看一看,下面这个方法也是只要传入一个对象就行,并且一起实现了序列化的步骤

1
2
3
4
5
6
7
8
9
10
11
public static void serializeBase64Encode(Object obj) throws Exception{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();
String base64Str = java.util.Base64.getEncoder().encodeToString(baos.toByteArray());
System.out.println(base64Str);
FileOutputStream fos = new FileOutputStream("ser.bin");
fos.write(base64Str.getBytes());
fos.close();
}

1. 创建字节数组输出流

数据写入内存中的字节数组,临时存储

1
ByteArrayOutputStream baos = new ByteArrayOutputStream();

2. 创建对象输出流

和序列化一样,把对象转成字节流

1
ObjectOutputStream oos = new ObjectOutputStream(baos);

3. 写入对象

1
oos.writeObject(obj);

4. 关闭对象输出流

前四步就是将传入的对象转化为字节,写入到内存缓冲区中

1
oos.close();

5. Base64编码

下面就是进行base64编码的部分了,从内到外看

1
String base64Str = Base64.getEncoder().encodeToString(baos.toByteArray());
  • baos.toByteArray()用于获取内存中的序列化字节数组
  • Base64.getEncoder()是为了获取 Base64 编码器
  • 调用encodeToString()来将获取到的字节数据转为 Base64 字符串

6. 创建文件输出流

和反序列化差不多了,将 Base64 编码的结果写入到文件中

1
FileOutputStream fos = new FileOutputStream("ser.bin");

7. 写入Base64字符串

先调用getBytes()方法,把 Base64 字符串转化为字节数组,然后在将其写入到文件中

1
fos.write(base64Str.getBytes());

8. 关闭文件流

1
fos.close();

检验

注释掉之前的序列化操作,只保留serializeBase64Encode()

image-20260908165229065

看一下ser.bin文件,确实是 base64 编码后的字符串

image-20260908165258590

Base64解码

也是和 Base64 编码相对应的

1
2
3
4
5
6
7
8
9
10
11
public static Object unserializeBase64Decode(String Filename) throws Exception{
FileInputStream fis = new FileInputStream(Filename);
byte[] data = new byte[fis.available()];
fis.read(data);
fis.close();
byte[] decodedBytes = Base64.getDecoder().decode(new String(data));
ByteArrayInputStream bais = new ByteArrayInputStream(decodedBytes);
ObjectInputStream ois = new ObjectInputStream(bais);
Object obj = ois.readObject();
return obj;
}

Java反序列化漏洞之基础补充
https://yschen20.github.io/2026/09/08/Java反序列化漏洞之基础补充/
作者
Suzen
发布于
2026年9月8日
更新于
2026年9月8日
许可协议