浅析Java序列化和反序列化
字数 2035 2025-08-27 12:33:54
Java序列化与反序列化深度解析
序列化基础概念
序列化(Serialization) 是将数据结构或对象状态转换为字节流的过程,以便存储或传输。反序列化(Deserialization) 则是将字节流恢复为原始对象的过程。
Java序列化广泛应用于:
- EJB (Enterprise JavaBeans)
- RMI (Remote Method Invocation)
- Hessian等分布式技术
Java序列化机制详解
基本序列化示例
public class SerializationDemo implements Serializable {
private String stringField;
private int intField;
public SerializationDemo(String s, int i) {
this.stringField = s;
this.intField = i;
}
public static void main(String[] args) throws IOException {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bout);
out.writeObject(new SerializationDemo("gyyyy", 97777));
}
}
序列化二进制格式解析
Java序列化结果是一个二进制串,包含以下关键部分:
- 魔术头和版本号:
0xaced0005 - 类描述符:
0x73标识对象类型,0x72标识类描述符 - 类名:UTF8格式的类名
- serialVersionUID:8字节的唯一标识
- 序列化属性标志位:标识对象特性
- 字段信息:包括字段类型、名称和值
- 父类描述:处理继承关系
序列化执行流程
- 初始化
ObjectOutputStream,写入魔术头和版本号 - 调用
writeObject()开始序列化 ObjectStreamClass.lookup()封装类描述信息writeOrdinaryObject()写入对象数据writeClassDesc()写入类描述数据writeSerialData()写入对象的序列化数据
反序列化机制详解
基本反序列化示例
public static void main(String[] args) throws ClassNotFoundException {
byte[] data; // 从文件或网络获取
ByteArrayInputStream bin = new ByteArrayInputStream(data);
ObjectInputStream in = new ObjectInputStream(bin);
SerializationDemo demo = (SerializationDemo)in.readObject();
}
反序列化执行流程
- 初始化
ObjectInputStream,校验魔术头和版本号 - 调用
readObject()开始反序列化 - 读取对象类型标识
readClassDesc()读取类描述数据resolveClass()根据类名获取Class对象- 创建对象实例
readSerialData()读取并填充对象字段数据
serialVersionUID详解
serialVersionUID(SUID)是序列化版本唯一标识,决定反序列化是否成功。计算规则:
- 类名(UTF8格式)
- 类访问权限标识
- 实现的接口名(排序后)
- 非私有静态或瞬态字段信息(按字段名排序)
- 类初始化器信息
- 非私有构造方法信息(按方法签名排序)
- 非私有方法信息(按方法名和签名排序)
计算过程使用SHA1算法生成摘要,取前8字节作为SUID。
特殊方法
writeReplace()和readResolve()
- writeReplace():在序列化前调用,返回实际被序列化的对象
- readResolve():在反序列化后调用,返回实际反序列化结果对象
Externalizable接口
与Serializable的区别:
- 需要实现
readExternal()和writeExternal() - 必须有无参构造方法
- 序列化属性标志位为
0x0c - 字段个数固定为0
反序列化漏洞原理
当服务端反序列化恶意构造的对象时,如果该对象的readObject()方法存在不安全逻辑,可能导致安全问题。
经典漏洞示例:Apache Commons Collections
利用链构造:
Transformer[] trans = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{cmd})
};
Transformer chain = new ChainedTransformer(trans);
触发方式:
- 使用
TransformedMap装饰器触发transform() - 使用
AnnotationInvocationHandler作为反序列化载体
POP链构造法则
-
命令执行载体选择:
- 使用
TemplatesImpl触发newTransformer()或getOutputProperties() - 使用
bsh.Interpreter等
- 使用
-
反序列化载体选择:
HashMap:触发hashCode()和equals()AnnotationInvocationHandler:触发setValue()BadAttributeValueExpException:触发toString()PriorityQueue:触发compare()
常见POP链分析
- CommonsCollections1-6:使用
ChainedTransformer作为命令执行载体 - C3P0:通过JNDI注入远程加载恶意对象
- Jdk7u21:利用
LinkedHashSet和AnnotationInvocationHandler - ROME:通过
ObjectBean.hashCode()触发 - Spring1/2:利用
SerializableTypeWrapper
防御方案
- 黑名单过滤:过滤危险类
- 白名单校验:只允许特定类反序列化
- 自定义
ObjectInputStream:重写resolveClass()方法进行校验
public class SecureObjectInputStream extends ObjectInputStream {
@Override
protected Class<?> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException {
// 校验逻辑
}
}
总结
Java反序列化漏洞的核心在于:
- 理解序列化/反序列化机制
- 识别可利用的POP链
- 掌握常见组件的利用方式
- 实施有效的防御措施
反序列化漏洞不仅限于RCE,还可能引发信息泄露、权限提升等多种安全问题。