JRE8u20反序列化漏洞分析
字数 2157 2025-08-18 11:37:23

JRE8u20反序列化漏洞深入分析与利用

0x00 漏洞概述

JRE8u20反序列化漏洞是在JDK7u21漏洞基础上改进而来,它绕过了Java SE对AnnotationInvocationHandler类的修复,允许攻击者通过精心构造的序列化数据实现任意代码执行。该漏洞利用了Java序列化机制中的多个特性,结合异常处理流程,最终实现了安全限制的绕过。

0x01 Java序列化机制深入解析

基本序列化格式

Java序列化后的字节流遵循特定格式,主要包含以下关键部分:

  1. STREAM_MAGIC (0xACED) - 序列化流魔数
  2. STREAM_VERSION (0x0005) - 序列化协议版本
  3. 实际数据内容

关键序列化结构

TC_STRING结构

表示字符串数据,格式如下:

TC_STRING newHandle length(2字节) value

示例:

TC_STRING 00 08 70 61 73 73 77 6f 72 64

TC_OBJECT结构

表示一个对象,格式:

TC_OBJECT classDesc newHandle classdata[]

其中classDesc是TC_CLASSDESC结构,描述类的元信息。

TC_CLASSDESC结构

描述类的结构信息:

TC_CLASSDESC className serialVersionUID newHandle classDescInfo
或
TC_PROXYCLASSDESC newHandle proxyClassDescInfo

classDescInfo包含:

  • classDescFlags (1字节)
  • fields (字段描述)
  • classAnnotation
  • superClassDesc

fields结构

描述类的成员字段:

(short)<count> fieldDesc[count]

fieldDesc分为:

  • primitiveDesc (基本类型)
  • objectDesc (对象类型)

基本类型编码:

  • B - byte
  • C - char
  • D - double
  • F - float
  • I - int
  • J - long
  • L - 对象
  • S - short
  • Z - boolean
  • [ - 数组

TC_REFERENCE结构

引用之前出现过的对象:

TC_REFERENCE Handle

序列化示例分析

以AuthClass为例:

class AuthClass implements Serializable {
    private static final long serialVersionUID = 100L;
    private String password;
    
    private void readObject(ObjectInputStream ois) throws Exception {
        ois.defaultReadObject();
        if(!this.password.equals("root")) {
            throw new Exception("Wrong Password.");
        }
    }
}

序列化后的关键部分:

TC_OBJECT
  TC_CLASSDESC
    className: "me.lightless.deserialize.vuln.AuthClass"
    serialVersionUID: 0x0000000000000064
    classDescFlags: 0x02 (SC_SERIALIZABLE)
    fields:
      - Object: L
        fieldName: "password"
        className1: "Ljava/lang/String;"
    classAnnotations: TC_ENDBLOCKDATA (0x78)
    superClassDesc: TC_NULL (0x70)
  classdata:
    password: TC_STRING "123456"

TC_BLOCKDATA结构

当类重写writeObject方法并写入额外数据时,会生成TC_BLOCKDATA结构。例如LinkedHashSet的序列化会包含HashMap的capacity、loadFactor和size等额外信息。

0x02 JDK7u21漏洞回顾

JDK7u21漏洞利用AnnotationInvocationHandler的反序列化问题,但后续版本增加了类型检查:

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
    var1.defaultReadObject();
    
    // 新增的类型检查
    if (!type.isAnnotation()) {
        throw new InvalidObjectException("Non-annotation type in annotation serial stream");
    }
    // ...
}

虽然检查在defaultReadObject()之后,但异常会中断反序列化流程,使得恶意对象虽然被反序列化但无法触发。

0x03 JRE8u20漏洞利用原理

关键发现

漏洞作者发现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 {
    int count = serializable;
    while (count-- > 0) {
        Object child = null;
        BeanContextSupport.BCSChild bscc = null;
        try {
            child = ois.readObject();
            bscc = (BeanContextSupport.BCSChild)ois.readObject();
        } catch (IOException ioe) {
            continue;
        } catch (ClassNotFoundException cnfe) {
            continue;
        }
        // ...
    }
}

关键点:

  1. 即使readObject抛出异常,流程仍会继续
  2. 可以通过构造特殊序列化数据让恶意对象被反序列化

利用技术

  1. 假成员技术:在Proxy对象中插入一个不存在的dummy成员

    • 反序列化时会尝试处理这个成员但最终会丢弃
    • 但会为这个假成员分配handle
    • 这使得我们可以控制关键对象的引用关系
  2. 异常捕获绕过:利用BeanContextSupport的异常捕获机制

    • 让AnnotationInvocationHandler在异常捕获流程中被反序列化
    • 即使检查失败也不会中断整个流程
  3. 精确偏移计算:精心计算TC_REFERENCE的handle值

    • 需要准确引用之前序列化的关键对象
    • 包括BeanContextSupport和AnnotationInvocationHandler

0x04 完整Payload构造

步骤1:构造LinkedHashSet基础结构

TC_OBJECT
  TC_CLASSDESC
    "java.util.LinkedHashSet"
    serialVersionUID: 2851667679971038690L
    flags: SC_SERIALIZABLE (0x02)
    fields: 0
    TC_ENDBLOCKDATA
  TC_CLASSDESC (父类HashSet)
    "java.util.HashSet"
    serialVersionUID: 5024744406713321676L
    flags: SC_SERIALIZABLE | SC_WRITE_METHOD (0x03)
    fields: 0
    TC_ENDBLOCKDATA
  TC_NULL
  classdata:
    TC_BLOCKDATA (HashMap额外数据)
    element1 (恶意Templates对象)
    element2 (Proxy对象)

步骤2:构造Proxy对象

TC_OBJECT
  TC_PROXYCLASSDESC
    1 (接口数量)
    "com.sun.org.apache.xalan.internal.xsltc.runtime.Templates"
    TC_ENDBLOCKDATA
  TC_CLASSDESC (父类Proxy)
    "java.lang.reflect.Proxy"
    serialVersionUID: -2222568056686623797L
    flags: SC_SERIALIZABLE (0x02)
    fields:
      - L "dummy" "Ljava/lang/Object;" (假成员)
      - L "h" "Ljava/lang/reflect/InvocationHandler;"
    TC_ENDBLOCKDATA
    TC_NULL
  classdata:
    TC_OBJECT (dummy成员 - BeanContextSupport)
      TC_CLASSDESC
        "java.beans.beancontext.BeanContextSupport"
        serialVersionUID: -4879613978649577204L
        flags: SC_SERIALIZABLE | SC_WRITE_METHOD
        fields:
          - I "serializable"
        TC_ENDBLOCKDATA
      TC_CLASSDESC (父类)
        "java.beans.beancontext.BeanContextChildSupport"
        serialVersionUID: 6328947014421475877L
        fields:
          - L "beanContextChildPeer" "Ljava/beans/beancontext/BeanContextChild;"
        TC_ENDBLOCKDATA
        TC_NULL
      classdata:
        serializable: 1
        beanContextChildPeer: TC_REFERENCE <handle_to_BeanContextSupport>
        TC_BLOCKDATA (deserialize数据)
          0x00000000
    h: TC_REFERENCE <handle_to_AnnotationInvocationHandler>

步骤3:构造AnnotationInvocationHandler

TC_OBJECT
  TC_CLASSDESC
    "sun.reflect.annotation.AnnotationInvocationHandler"
    serialVersionUID: 6182022883658399397L
    flags: SC_SERIALIZABLE | SC_WRITE_METHOD
    fields:
      - L "type" "Ljava/lang/Class;"
      - L "memberValues" "Ljava/util/Map;"
    TC_ENDBLOCKDATA
    TC_NULL
  classdata:
    type: Templates.class
    memberValues: Map (包含恶意Templates对象)

关键偏移量计算

  1. BeanContextChildPeer引用

    • 需要引用前面的BeanContextSupport对象
    • 根据序列化流中对象出现的顺序计算精确handle值
  2. Proxy.h引用

    • 需要引用AnnotationInvocationHandler对象
    • 同样需要精确计算其在序列化流中的位置

典型偏移值:

  • BeanContextSupport引用:0x007e0019
  • AnnotationInvocationHandler引用:0x007e001d

0x05 漏洞修复

在JRE8u20之后的版本中,修复方式为:

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
    GetField var2 = var1.readFields();
    // 直接读取字段而非整个对象
    Class var3 = (Class)var2.get("type", (Object)null);
    // 严格类型检查
    if (!var3.isAnnotation()) {
        throw new InvalidObjectException("Non-annotation type in annotation serial stream");
    }
    // ...
}

修复关键点:

  1. 使用readFields()而非defaultReadObject()
  2. 在读取字段时就进行类型检查
  3. 避免整个对象被反序列化后再检查

0x06 防御建议

  1. 输入验证:对反序列化的数据来源进行严格校验
  2. 白名单机制:只允许反序列化可信的类
  3. 替换方案:考虑使用JSON等更安全的序列化格式
  4. 及时更新:确保使用最新版本的Java运行时环境
  5. 安全配置:使用ObjectInputFilter限制可反序列化的类

0x07 总结

JRE8u20反序列化漏洞展示了Java序列化机制的深层次安全问题,通过精心构造的序列化数据、假成员技术和异常处理流程绕过,实现了安全限制的突破。理解该漏洞需要对Java序列化协议有深入认识,并掌握精确的字节流构造技术。防御此类漏洞需要从输入验证、安全配置和运行时保护等多方面入手。

JRE8u20反序列化漏洞深入分析与利用 0x00 漏洞概述 JRE8u20反序列化漏洞是在JDK7u21漏洞基础上改进而来,它绕过了Java SE对AnnotationInvocationHandler类的修复,允许攻击者通过精心构造的序列化数据实现任意代码执行。该漏洞利用了Java序列化机制中的多个特性,结合异常处理流程,最终实现了安全限制的绕过。 0x01 Java序列化机制深入解析 基本序列化格式 Java序列化后的字节流遵循特定格式,主要包含以下关键部分: STREAM_ MAGIC (0xACED) - 序列化流魔数 STREAM_ VERSION (0x0005) - 序列化协议版本 实际数据内容 关键序列化结构 TC_ STRING结构 表示字符串数据,格式如下: 示例: TC_ OBJECT结构 表示一个对象,格式: 其中classDesc是TC_ CLASSDESC结构,描述类的元信息。 TC_ CLASSDESC结构 描述类的结构信息: classDescInfo包含: classDescFlags (1字节) fields (字段描述) classAnnotation superClassDesc fields结构 描述类的成员字段: fieldDesc分为: primitiveDesc (基本类型) objectDesc (对象类型) 基本类型编码: B - byte C - char D - double F - float I - int J - long L - 对象 S - short Z - boolean [ - 数组 TC_ REFERENCE结构 引用之前出现过的对象: 序列化示例分析 以AuthClass为例: 序列化后的关键部分: TC_ BLOCKDATA结构 当类重写writeObject方法并写入额外数据时,会生成TC_ BLOCKDATA结构。例如LinkedHashSet的序列化会包含HashMap的capacity、loadFactor和size等额外信息。 0x02 JDK7u21漏洞回顾 JDK7u21漏洞利用AnnotationInvocationHandler的反序列化问题,但后续版本增加了类型检查: 虽然检查在defaultReadObject()之后,但异常会中断反序列化流程,使得恶意对象虽然被反序列化但无法触发。 0x03 JRE8u20漏洞利用原理 关键发现 漏洞作者发现 java.beans.beancontext.BeanContextSupport 类在反序列化时有特殊的异常处理: 关键点: 即使readObject抛出异常,流程仍会继续 可以通过构造特殊序列化数据让恶意对象被反序列化 利用技术 假成员技术 :在Proxy对象中插入一个不存在的dummy成员 反序列化时会尝试处理这个成员但最终会丢弃 但会为这个假成员分配handle 这使得我们可以控制关键对象的引用关系 异常捕获绕过 :利用BeanContextSupport的异常捕获机制 让AnnotationInvocationHandler在异常捕获流程中被反序列化 即使检查失败也不会中断整个流程 精确偏移计算 :精心计算TC_ REFERENCE的handle值 需要准确引用之前序列化的关键对象 包括BeanContextSupport和AnnotationInvocationHandler 0x04 完整Payload构造 步骤1:构造LinkedHashSet基础结构 步骤2:构造Proxy对象 步骤3:构造AnnotationInvocationHandler 关键偏移量计算 BeanContextChildPeer引用 : 需要引用前面的BeanContextSupport对象 根据序列化流中对象出现的顺序计算精确handle值 Proxy.h引用 : 需要引用AnnotationInvocationHandler对象 同样需要精确计算其在序列化流中的位置 典型偏移值: BeanContextSupport引用:0x007e0019 AnnotationInvocationHandler引用:0x007e001d 0x05 漏洞修复 在JRE8u20之后的版本中,修复方式为: 修复关键点: 使用readFields()而非defaultReadObject() 在读取字段时就进行类型检查 避免整个对象被反序列化后再检查 0x06 防御建议 输入验证 :对反序列化的数据来源进行严格校验 白名单机制 :只允许反序列化可信的类 替换方案 :考虑使用JSON等更安全的序列化格式 及时更新 :确保使用最新版本的Java运行时环境 安全配置 :使用ObjectInputFilter限制可反序列化的类 0x07 总结 JRE8u20反序列化漏洞展示了Java序列化机制的深层次安全问题,通过精心构造的序列化数据、假成员技术和异常处理流程绕过,实现了安全限制的突破。理解该漏洞需要对Java序列化协议有深入认识,并掌握精确的字节流构造技术。防御此类漏洞需要从输入验证、安全配置和运行时保护等多方面入手。