用一个 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 的核心思路是绕过异常抛出,通过以下方式实现:

  1. 利用 BeanContextSupport 类作为外层包装
  2. 通过异常处理机制使反序列化流程不被终止
  3. 最终仍能还原恶意的 AnnotationInvocationHandler 对象

0x04 关键基础知识

1. Try/catch 块的作用机制

  • 无异常抛出调用有异常抛出:如果被调用的方法出错,会导致调用方法出错且不会继续执行完调用方法的代码逻辑,但不会终止代码运行进程
  • 有异常抛出调用无异常抛出:如果被调用的方法出错,会继续执行完调用方法的代码逻辑,但如果调用方法也出错,会终止代码运行进程

2. 序列化数据结构

序列化数据由 TC_* 和各种字段描述符构成,主要结构包括:

  • STREAM_MAGIC (0xac ed)
  • STREAM_VERSION
  • TC_OBJECT (0x73)
  • TC_CLASSDESC (0x72)
  • TC_ENDBLOCKDATA (0x78)
  • TC_NULL (0x70)
  • TC_REFERENCE (0x71)

3. 序列化中的两个关键机制

引用机制

  • 每个写入字节流的对象都会被赋予引用 Handle
  • 引用 Handle 从 0x7E0000 开始顺序自增
  • 可通过 TC_REFERENCE 结构反向引用对象

成员抛弃机制

  • 如果字段未在字节流中出现,使用类定义的默认值
  • 如果字段出现在字节流中但不属于对象,则抛弃该值
  • 如果抛弃的值是对象,会为其分配 Handle

4. ObjectAnnotation 机制

如果一个可序列化的类重写了 writeObject 方法:

  1. 会设置 SC_WRITE_METHOD 标识 (0x03)
  2. 在 classdata 部分会多出 objectAnnotation 部分
  3. 写入的自定义数据会出现在 objectAnnotation 部分

0x05 漏洞利用关键技术

1. 利用 BeanContextSupport 绕过异常

BeanContextSupport.readChildren() 方法关键代码:

try {
    child = ois.readObject();
    bscc = (BeanContextSupport.BCSChild)ois.readObject();
} catch (IOException ioe) {
    continue;  // 关键:异常被捕获后继续执行
} catch (ClassNotFoundException cnfe) {
    continue;  // 关键:异常被捕获后继续执行
}

2. 手动构造序列化数据

构造步骤:

  1. 先序列化 BeanContextSupport
  2. 插入 AnnotationInvocationHandler 对象作为 objectAnnotation
  3. 修改 classDescFlags0x03 (SC_WRITE_METHOD | SC_SERIALIZABLE)
  4. 调整 Handle 引用值

3. 删除 TC_ENDBLOCKDATA

由于异常抛出会导致 TC_ENDBLOCKDATA 无法被正常处理,需要手动删除该标记,否则会导致后续结构解析错误。

0x06 漏洞利用流程

  1. 构造恶意的 AnnotationInvocationHandler 对象
  2. 将其包装在 BeanContextSupportobjectAnnotation
  3. 序列化时:
    • 先还原 BeanContextSupport 对象
    • 然后还原 AnnotationInvocationHandler 对象
  4. 利用动态代理触发 Proxy.equals(EvilTemplates.class)
  5. 实现 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 防御措施

  1. 升级 JDK 到最新版本
  2. 使用白名单机制限制反序列化的类
  3. 使用 ObjectInputFilter 过滤反序列化数据
  4. 避免直接反序列化不可信数据

0x09 总结

JDK8u20 反序列化漏洞的核心在于:

  1. 利用 BeanContextSupport 的异常处理机制绕过 InvalidObjectException
  2. 通过手动构造序列化数据插入恶意对象
  3. 精确控制序列化数据结构绕过完整性检查
  4. 最终仍能触发 AnnotationInvocationHandler 的恶意行为

理解该漏洞需要对 Java 序列化机制、异常处理流程和字节流结构有深入认识。

JDK8u20 原生反序列化漏洞深入分析 0x01 漏洞背景 JDK8u20 原生反序列化漏洞是对 JDK7u21 漏洞修复的绕过,是一个经典的 Java 反序列化漏洞。该漏洞利用了 Java 序列化机制中的多个底层特性,需要深入理解反序列化流程和序列化数据结构。 0x02 JDK7u21 修复分析 JDK7u21 漏洞修复的核心变化是在 AnnotationInvocationHandler.readObject() 方法中: 修复后,当传入的 type 不是 AnnotationType 类型时,会抛出异常终止反序列化流程。 0x03 JDK8u20 漏洞原理 JDK8u20 的核心思路是 绕过异常抛出 ,通过以下方式实现: 利用 BeanContextSupport 类作为外层包装 通过异常处理机制使反序列化流程不被终止 最终仍能还原恶意的 AnnotationInvocationHandler 对象 0x04 关键基础知识 1. Try/catch 块的作用机制 无异常抛出调用有异常抛出 :如果被调用的方法出错,会导致调用方法出错且不会继续执行完调用方法的代码逻辑,但不会终止代码运行进程 有异常抛出调用无异常抛出 :如果被调用的方法出错,会继续执行完调用方法的代码逻辑,但如果调用方法也出错,会终止代码运行进程 2. 序列化数据结构 序列化数据由 TC_* 和各种字段描述符构成,主要结构包括: STREAM_MAGIC (0xac ed) STREAM_VERSION TC_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() 方法关键代码: 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 漏洞利用示例代码 0x08 防御措施 升级 JDK 到最新版本 使用白名单机制限制反序列化的类 使用 ObjectInputFilter 过滤反序列化数据 避免直接反序列化不可信数据 0x09 总结 JDK8u20 反序列化漏洞的核心在于: 利用 BeanContextSupport 的异常处理机制绕过 InvalidObjectException 通过手动构造序列化数据插入恶意对象 精确控制序列化数据结构绕过完整性检查 最终仍能触发 AnnotationInvocationHandler 的恶意行为 理解该漏洞需要对 Java 序列化机制、异常处理流程和字节流结构有深入认识。