用一个 case 去理解 jdk8u20 原生反序列化漏洞
字数 1990 2025-08-05 08:17:55
JDK8u20 原生反序列化漏洞深入分析
0x01 漏洞背景
JDK8u20 原生反序列化漏洞是对 JDK7u21 漏洞修复的绕过,是一个经典的 Java 反序列化漏洞。该漏洞利用了 Java 序列化机制中的多个底层特性,需要深入理解反序列化流程和序列化数据结构。
0x02 JDK7u21 修复分析
JDK7u21 漏洞修复的核心变化是在 AnnotationInvocationHandler.readObject() 方法中:
// 修复前
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
return; // 直接返回
}
// 修复后
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
修复后,当传入的 type 不是 AnnotationType 类型时,会抛出异常终止反序列化流程。
0x03 JDK8u20 漏洞原理
JDK8u20 的核心思路是绕过异常抛出,通过以下方式实现:
- 利用
BeanContextSupport类作为外层包装 - 通过异常处理机制使反序列化流程不被终止
- 最终仍能还原恶意的
AnnotationInvocationHandler对象
0x04 关键基础知识
1. Try/catch 块的作用机制
- 无异常抛出调用有异常抛出:如果被调用的方法出错,会导致调用方法出错且不会继续执行完调用方法的代码逻辑,但不会终止代码运行进程
- 有异常抛出调用无异常抛出:如果被调用的方法出错,会继续执行完调用方法的代码逻辑,但如果调用方法也出错,会终止代码运行进程
2. 序列化数据结构
序列化数据由 TC_* 和各种字段描述符构成,主要结构包括:
STREAM_MAGIC(0xac ed)STREAM_VERSIONTC_OBJECT(0x73)TC_CLASSDESC(0x72)TC_ENDBLOCKDATA(0x78)TC_NULL(0x70)TC_REFERENCE(0x71)
3. 序列化中的两个关键机制
引用机制:
- 每个写入字节流的对象都会被赋予引用 Handle
- 引用 Handle 从 0x7E0000 开始顺序自增
- 可通过
TC_REFERENCE结构反向引用对象
成员抛弃机制:
- 如果字段未在字节流中出现,使用类定义的默认值
- 如果字段出现在字节流中但不属于对象,则抛弃该值
- 如果抛弃的值是对象,会为其分配 Handle
4. ObjectAnnotation 机制
如果一个可序列化的类重写了 writeObject 方法:
- 会设置
SC_WRITE_METHOD标识 (0x03) - 在 classdata 部分会多出
objectAnnotation部分 - 写入的自定义数据会出现在
objectAnnotation部分
0x05 漏洞利用关键技术
1. 利用 BeanContextSupport 绕过异常
BeanContextSupport.readChildren() 方法关键代码:
try {
child = ois.readObject();
bscc = (BeanContextSupport.BCSChild)ois.readObject();
} catch (IOException ioe) {
continue; // 关键:异常被捕获后继续执行
} catch (ClassNotFoundException cnfe) {
continue; // 关键:异常被捕获后继续执行
}
2. 手动构造序列化数据
构造步骤:
- 先序列化
BeanContextSupport类 - 插入
AnnotationInvocationHandler对象作为objectAnnotation - 修改
classDescFlags为0x03(SC_WRITE_METHOD | SC_SERIALIZABLE) - 调整 Handle 引用值
3. 删除 TC_ENDBLOCKDATA
由于异常抛出会导致 TC_ENDBLOCKDATA 无法被正常处理,需要手动删除该标记,否则会导致后续结构解析错误。
0x06 漏洞利用流程
- 构造恶意的
AnnotationInvocationHandler对象 - 将其包装在
BeanContextSupport的objectAnnotation中 - 序列化时:
- 先还原
BeanContextSupport对象 - 然后还原
AnnotationInvocationHandler对象
- 先还原
- 利用动态代理触发
Proxy.equals(EvilTemplates.class) - 实现 RCE
0x07 漏洞利用示例代码
// 简化的漏洞利用框架
public class Exploit {
public static void generatePayload() throws Exception {
// 1. 创建恶意的AnnotationInvocationHandler
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = clazz.getDeclaredConstructors()[0];
constructor.setAccessible(true);
// 2. 创建代理对象
Map<String, Object> map = new HashMap<>();
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Templates.class, map);
// 3. 创建BeanContextSupport对象
BeanContextSupport bcs = new BeanContextSupport();
// 4. 构造序列化数据(实际中需要手动构造字节流)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
// 写入BeanContextSupport
oos.writeObject(bcs);
// 手动写入AnnotationInvocationHandler作为objectAnnotation
// 这里需要精确控制字节流结构
byte[] payload = baos.toByteArray();
// 5. 触发反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(payload);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
}
}
0x08 防御措施
- 升级 JDK 到最新版本
- 使用白名单机制限制反序列化的类
- 使用
ObjectInputFilter过滤反序列化数据 - 避免直接反序列化不可信数据
0x09 总结
JDK8u20 反序列化漏洞的核心在于:
- 利用
BeanContextSupport的异常处理机制绕过InvalidObjectException - 通过手动构造序列化数据插入恶意对象
- 精确控制序列化数据结构绕过完整性检查
- 最终仍能触发
AnnotationInvocationHandler的恶意行为
理解该漏洞需要对 Java 序列化机制、异常处理流程和字节流结构有深入认识。