深度 - Java 反序列化 Payload 之 JRE8u20
字数 1713 2025-08-29 08:31:47
Java反序列化漏洞深入分析:JRE8u20 Payload详解
1. 背景知识
1.1 Jdk7u21 Payload回顾
Jdk7u21 Payload是一个经典的Java反序列化漏洞利用链,其核心原理如下:
- TemplatesImpl类:可序列化,内部
__bytecodes成员可存储class字节码 - 执行流程:
- 通过
TemplatesImpl.getOutputProperties()方法触发 __bytecodes存储的字节码会被转换为Class对象(通过ClassLoader.defineClass)- 实例化该Class,执行构造方法中的代码
- 通过
- 触发机制:利用
LinkedHashSet与AnnotationInvocationHandler触发getOutputProperties
1.2 Jdk7u21的修补
在7u21之后的版本中,AnnotationInvocationHandler.readObject方法增加了检查:
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
// 省略后续代码
}
关键点:
- 检查
this.type是否为注解类型,非注解类型则抛出异常 - 异常抛出前已经通过
defaultReadObject()还原了对象 - 这使得
this.type必须为Templates.class的7u21 Payload失效
2. 绕过思路
2.1 核心发现
AnnotationInvocationHandler.readObject的执行顺序:
- 先通过
var1.defaultReadObject()还原对象 - 然后检查
this.type并可能抛出异常
这意味着:
- 对象已经被成功还原
- 异常抛出后对象仍然存在
- 可以通过引用机制在其他地方使用这个对象
2.2 关键技术点
2.2.1 Java序列化引用机制
Java序列化会为每个对象分配一个handle,重复对象会使用TC_REFERENCE引用已序列化的对象。
示例:
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(new File("/tmp/ser")));
Date d = new Date();
out.writeObject(d); // 写入实际对象
out.writeObject(d); // 写入引用
out.close();
序列化结构:
TC_OBJECT - 第一个Date对象 (分配handle 0x00 7e 00 01)
TC_REFERENCE - 引用handle 0x00 7e 00 01
2.2.2 异常处理技巧
通过包装类捕获异常,使"非法"对象仍能被创建:
public class WrapperClass implements Serializable {
private void readObject(ObjectInputStream input) throws Exception {
input.defaultReadObject();
try {
input.readObject(); // 读取可能抛出异常的对象
} catch (Exception e) {
// 捕获异常但继续执行
}
}
}
序列化结构:
TC_OBJECT - WrapperClass
TC_OBJECT - 非法对象 (分配handle)
TC_REFERENCE - 引用非法对象
2.2.3 插入假成员
可以在TC_CLASSDESC中添加类中不存在的成员:
- 反序列化时会为假成员分配handle
- 实际对象不会包含这些成员
- 可用于创建需要的引用关系
3. JRE8u20 Payload分析
3.1 核心组件
-
BeanContextSupport类:
- 类似WrapperClass,提供try/catch保护
- 用于安全地创建AnnotationInvocationHandler对象
-
假成员技术:
- 在HashSet的TC_CLASSDESC中添加假属性
- 属性值为BeanContextSupport对象
3.2 执行流程
- 反序列化开始时创建HashSet
- 读取假成员时创建BeanContextSupport对象
- BeanContextSupport内部读取AnnotationInvocationHandler
- 虽然会抛出异常但被捕获
- AnnotationInvocationHandler对象已被创建并获得handle
- 后续payload通过TC_REFERENCE引用该对象
- 继续Jdk7u21的触发流程
3.3 代码实现关键点
// 创建AnnotationInvocationHandler
TCObject handler = new TCObject(ser) {
@Override
public void doWrite(DataOutputStream out, HandleContainer handles) throws Exception {
// 特殊处理,去掉TC_ENDBLOCKDATA
// 因为异常会导致序列化过程不能正常结束
}
};
// 创建BeanContextSupport包装器
TCObject makeBeanContextSupport(TCObject handler, Serialization ser) {
// 设置serializable成员
// 添加handler作为子对象
// 设置循环引用
}
// 主payload构造
TCObject linkedHashset = new TCObject(ser);
// 添加假成员
hashsetDesc.addField(new TCClassDesc.Field("fake", BeanContextSupport.class));
hashsetData.addData(makeBeanContextSupport(handler, ser));
// 添加其他必要数据
4. 防御与检测
4.1 防御措施
- 升级JDK到最新版本
- 使用安全管理器限制反序列化
- 替换默认的ObjectInputStream实现
- 使用白名单控制可反序列化的类
4.2 检测方法
- 监控异常日志中的"Non-annotation type in annotation serial stream"
- 检查序列化数据中的可疑结构:
- 异常的TC_CLASSDESC
- 不匹配的类描述与实际数据
- 可疑的引用关系
5. 总结
JRE8u20 Payload通过以下技术创新绕过限制:
- 利用序列化过程中对象先创建后验证的特性
- 通过包装类捕获异常保持反序列化流程
- 精心构造的引用关系维持对象可用性
- 假成员技术创建必要的对象引用
理解这些技术对于防御复杂的反序列化攻击至关重要,也为安全研究人员提供了分析高级payload的方法论。