浅谈Java反序列化漏洞修复方案
字数 1231 2025-08-29 08:31:41
Java反序列化漏洞原理与修复方案详解
一、序列化与反序列化基础
1.1 序列化概念
序列化是将Java对象转换为字节流的过程,使得对象可以脱离Java运行环境,实现:
- 多平台通信
- 对象持久化存储
- 远程方法调用(RMI)
1.2 反序列化过程
Java程序使用ObjectInputStream的readObject()方法将字节流反序列化为Java对象。当输入的反序列化数据可被用户控制时,就可能存在安全漏洞。
二、反序列化漏洞原理
2.1 漏洞产生条件
当满足以下条件时,反序列化漏洞可能被利用:
- 应用程序接受外部输入的序列化数据
- 使用
ObjectInputStream.readObject()方法反序列化这些数据 - 类路径中包含可利用的"gadget chain"类
2.2 漏洞代码示例
// 读取输入流并转换对象
InputStream in = request.getInputStream();
ObjectInputStream ois = new ObjectInputStream(in);
// 恢复对象
ois.readObject();
ois.close();
2.3 序列化数据结构
Java序列化数据具有特定的格式:
- 魔术数字:2字节,固定为
0xACED - 版本号:2字节,通常为
0x0005 - 类描述信息:包括类名、成员变量类型和数量等
示例序列化数据:
00000000: aced 0005 7372 0024 636f 6d2e 7878 7878 ....sr.$com.xxxx
00000010: 7878 2e73 6563 2e77 6562 2e68 6f6d 652e xx.sec.web.home.
00000020: 5365 7269 616c 4f62 6a65 6374 4fda af97 SerialObjectO...
三、反序列化漏洞修复方案
3.1 通过Hook resolveClass校验反序列化的类
实现原理
重写ObjectInputStream的resolveClass方法,在反序列化时首先校验类名。
代码示例
public class AntObjectInputStream extends ObjectInputStream {
public AntObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException {
if (!desc.getName().equals(SerialObject.class.getName())) {
throw new InvalidClassException(
"Unauthorized deserialization attempt", desc.getName());
}
return super.resolveClass(desc);
}
}
优缺点
- 优点:可灵活设置白名单或黑名单
- 缺点:黑名单需要持续维护,无法覆盖未公开的利用方法
3.2 使用ValidatingObjectInputStream校验
实现方式
使用Apache Commons IO Serialization包中的ValidatingObjectInputStream类。
代码示例
private static Object deserialize(byte[] buffer)
throws IOException, ClassNotFoundException {
Object obj;
ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
ValidatingObjectInputStream ois = new ValidatingObjectInputStream(bais);
// 只允许反序列化SerialObject class
ois.accept(SerialObject.class);
obj = ois.readObject();
return obj;
}
3.3 使用contrast-rO0防御
实现方式
使用SafeObjectInputStream类实现白名单控制。
代码示例
SafeObjectInputStream in = new SafeObjectInputStream(inputStream, true);
in.addToWhitelist(SerialObject.class);
in.readObject();
3.4 使用ObjectInputFilter(Java 9+)
实现原理
Java 9引入的序列化数据过滤特性,可自定义过滤器。
代码示例
class BikeFilter implements ObjectInputFilter {
private long maxStreamBytes = 78;
private long maxDepth = 1;
private long maxReferences = 1;
@Override
public Status checkInput(FilterInfo filterInfo) {
if (filterInfo.references() < 0 ||
filterInfo.depth() < 0 ||
filterInfo.streamBytes() < 0 ||
filterInfo.references() > maxReferences ||
filterInfo.depth() > maxDepth ||
filterInfo.streamBytes() > maxStreamBytes) {
return Status.REJECTED;
}
Class<?> clazz = filterInfo.serialClass();
if (clazz != null) {
if (SerialObject.class == filterInfo.serialClass()) {
return Status.ALLOWED;
} else {
return Status.REJECTED;
}
}
return Status.UNDECIDED;
}
}
3.5 黑名单方案
适用场景
- 作为第三方库提供反序列化接口
- 无法预知调用方需要反序列化的类
注意事项
- 需要持续更新黑名单
- 无法防御未知的利用方式
- 建议作为辅助防御措施
四、最佳实践建议
- 优先使用白名单:白名单方案比黑名单更安全可靠
- 升级Java版本:Java 9+提供了更完善的序列化过滤机制
- 避免反序列化不可信数据:从根本上消除风险
- 使用替代方案:考虑JSON、XML等更安全的序列化格式
- 持续监控:关注新的反序列化漏洞利用方式