本文最后更新于 2026-09-08T17:04:38+08:00
前言
之前基础学的懵懵懂懂的,到后面学到反序列化,写各种代码,就不知道写什么,为什么这么写,比如 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()方法,传入两个参数:要修改的对象、修改后的值
检验
用get()方法来查看字段值,field.get(person);得到的是一个对象,要强转成 int 类型
1 2
| int age = (int) field.get(person); System.out.println(age);
|
先看一下修改前的,注释掉3、4步,会出现报错,因为没权限访问私有字段age

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

下面就是修改后的值

序列化
拿以前写过的来看,这个方法就接收一个构造好的对象
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()方法,来将传入的对象写入到流中
4. 关闭流
最后就是调用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文件

反序列化
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. 写入对象
4. 关闭对象输出流
前四步就是将传入的对象转化为字节,写入到内存缓冲区中
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. 关闭文件流
检验
注释掉之前的序列化操作,只保留serializeBase64Encode()

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

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; }
|