最简单的jdk原生链8u20
字数 2251 2025-08-27 12:33:22

JDK 8u20反序列化漏洞利用分析

漏洞背景

JDK 8u20反序列化漏洞是对7u21漏洞的一种绕过利用方式,主要利用了Java序列化机制中的两个关键特性:

  1. 双层try/catch结构
  2. TC_REFERENCE机制

核心利用原理

1. 双层try/catch机制

基本原理

  • 当内层try块抛出异常时,如果外层catch能够捕获该异常类型,程序不会立即终止
  • 外层catch如果没有再次throw,程序可以继续执行后续代码

在漏洞中的利用

  • AnnotationInvocationHandler#readObject中先执行s.defaultReadObject(),然后才进行type判断
  • 利用双层try/catch可以让AnnotationInvocationHandler对象被还原,即使type判断后的代码无法执行也不影响利用

2. TC_REFERENCE机制

基本原理

  • Java序列化/反序列化会为每个对象分配一个Handler值(类似索引)
  • 当同一个对象被多次引用时,后续引用会直接使用第一次生成的Handler值

示例说明

Foo f = new Foo();
oos.writeObject(f);
oos.writeObject(f);  // 这里第二个f会存储第一个f的Handler

Handler值规则

  • 第一个Handler值为0x7E0000(十进制8257536)
  • 后续每个新Handler值递增1(第二个为8257537,以此类推)

利用链构造

关键组件

  1. BeanContextSupport

    • readObject()调用了readChildren(ois)
    • 内部的catch块满足双层try/catch条件
    • 可以包裹AnnotationInvocationHandler对象
  2. TemplatesImpl

    • 用于hash碰撞,与7u21利用方式相同
  3. Proxy对象

    • 代理类与BeanContextSupport包裹的是同一个对象

利用步骤

  1. BeanContextSupport包裹AnnotationInvocationHandler对象并放在第一位
  2. TemplatesImpl放在第二位,用于hash碰撞
  3. Proxy放在第三位,代理类与BeanContextSupport包裹的是同一个对象

执行流程

  1. 先反序列化BeanContextSupport对象,还原AnnotationInvocationHandler
  2. Proxy通过Handler调用已还原的AnnotationInvocationHandler对象
  3. 后续流程与7u21相同

问题分析与解决

报错问题分析

报错现象

  • 反序列化时出现readInt()报错
  • 根源在于readBlockHeader()返回-1导致in.read()为-1

深层原因

  1. defaultDataEnd标志:

    • 当类没有writeObject()方法时,defaultDataEnd = true
    • 但这不是根本原因,因为测试发现没有writeObject()的类也能正常反序列化
  2. 关键问题:

    • AnnotationInvocationHandlerreadObject报错导致退出
    • 未能执行defaultDataEnd = false的重新赋值
    • 导致readInt()报错

解决方案

  • 修改flags0x02改为0x03(添加SC_WRITE_METHOD标志)
  • 删除序列化数据中的null值,确保数据对齐

数据对齐问题

为什么删除null值

  1. 序列化数据中:
    • TC_NULL对应null值
    • TC_BLOCKDATA对应int值
  2. 需要让TC_BLOCKDATA提前
  3. 删除TC_NULL不会影响后续反序列化,因为:
    • readInt()标志着BeanContextSupport反序列化结束
    • 紧接着要反序列化下一个对象(TemplatesImpl

完整POC构造

关键点

  1. 使用BeanContextSupport包裹AnnotationInvocationHandler
  2. 设置TemplatesImpl用于hash碰撞
  3. 使用Proxy代理同一个对象
  4. 修改flags添加SC_WRITE_METHOD
  5. 删除序列化数据中的null值确保对齐

JDK 8u20修复

修复方式:

  • 限制反射调用的方法,只允许以下三种:
    1. equals
    2. hashCode
    3. toString
  • 这样就不能调用危险的sink点方法

调试技巧

  1. 序列化数据分析

    • 使用工具如zkar或SerializationDumper
    • 关注@ObjectAnnotation@ClassDescFlags
  2. 定位问题

    • 通过前后pos值的变化定位序列化数据中的位置
    • 关注defaultDataEnd标志的变化
  3. 异常跟踪

    • 注意异常抛出后是否会进入catch块
    • 确保外层catch能捕获内层抛出的异常类型

总结

JDK 8u20反序列化漏洞利用了两个关键机制:

  1. 通过双层try/catch绕过AnnotationInvocationHandler的type检查
  2. 利用TC_REFERENCE机制复用已反序列化的对象

成功利用需要:

  • 精心构造的序列化数据
  • 正确处理数据对齐问题
  • 理解Java序列化/反序列化的内部机制

通过深入分析序列化数据和调试,可以完全理解漏洞利用的每个细节,包括为什么需要删除某些数据以及如何确保利用链的完整执行。

JDK 8u20反序列化漏洞利用分析 漏洞背景 JDK 8u20反序列化漏洞是对7u21漏洞的一种绕过利用方式,主要利用了Java序列化机制中的两个关键特性: 双层try/catch结构 TC_ REFERENCE机制 核心利用原理 1. 双层try/catch机制 基本原理 : 当内层try块抛出异常时,如果外层catch能够捕获该异常类型,程序不会立即终止 外层catch如果没有再次throw,程序可以继续执行后续代码 在漏洞中的利用 : AnnotationInvocationHandler#readObject 中先执行 s.defaultReadObject() ,然后才进行type判断 利用双层try/catch可以让 AnnotationInvocationHandler 对象被还原,即使type判断后的代码无法执行也不影响利用 2. TC_ REFERENCE机制 基本原理 : Java序列化/反序列化会为每个对象分配一个Handler值(类似索引) 当同一个对象被多次引用时,后续引用会直接使用第一次生成的Handler值 示例说明 : Handler值规则 : 第一个Handler值为 0x7E0000 (十进制8257536) 后续每个新Handler值递增1(第二个为8257537,以此类推) 利用链构造 关键组件 BeanContextSupport : 其 readObject() 调用了 readChildren(ois) 内部的catch块满足双层try/catch条件 可以包裹 AnnotationInvocationHandler 对象 TemplatesImpl : 用于hash碰撞,与7u21利用方式相同 Proxy对象 : 代理类与 BeanContextSupport 包裹的是同一个对象 利用步骤 让 BeanContextSupport 包裹 AnnotationInvocationHandler 对象并放在第一位 TemplatesImpl 放在第二位,用于hash碰撞 Proxy 放在第三位,代理类与 BeanContextSupport 包裹的是同一个对象 执行流程 : 先反序列化 BeanContextSupport 对象,还原 AnnotationInvocationHandler Proxy通过Handler调用已还原的 AnnotationInvocationHandler 对象 后续流程与7u21相同 问题分析与解决 报错问题分析 报错现象 : 反序列化时出现 readInt() 报错 根源在于 readBlockHeader() 返回-1导致 in.read() 为-1 深层原因 : defaultDataEnd 标志: 当类没有 writeObject() 方法时, defaultDataEnd = true 但这不是根本原因,因为测试发现没有 writeObject() 的类也能正常反序列化 关键问题: AnnotationInvocationHandler 的 readObject 报错导致退出 未能执行 defaultDataEnd = false 的重新赋值 导致 readInt() 报错 解决方案 : 修改 flags 从 0x02 改为 0x03 (添加 SC_WRITE_METHOD 标志) 删除序列化数据中的null值,确保数据对齐 数据对齐问题 为什么删除null值 : 序列化数据中: TC_NULL 对应null值 TC_BLOCKDATA 对应int值 需要让 TC_BLOCKDATA 提前 删除 TC_NULL 不会影响后续反序列化,因为: readInt() 标志着 BeanContextSupport 反序列化结束 紧接着要反序列化下一个对象( TemplatesImpl ) 完整POC构造 关键点 : 使用 BeanContextSupport 包裹 AnnotationInvocationHandler 设置 TemplatesImpl 用于hash碰撞 使用Proxy代理同一个对象 修改flags添加 SC_WRITE_METHOD 删除序列化数据中的null值确保对齐 JDK 8u20修复 修复方式: 限制反射调用的方法,只允许以下三种: equals hashCode toString 这样就不能调用危险的sink点方法 调试技巧 序列化数据分析 : 使用工具如zkar或SerializationDumper 关注 @ObjectAnnotation 和 @ClassDescFlags 定位问题 : 通过前后 pos 值的变化定位序列化数据中的位置 关注 defaultDataEnd 标志的变化 异常跟踪 : 注意异常抛出后是否会进入catch块 确保外层catch能捕获内层抛出的异常类型 总结 JDK 8u20反序列化漏洞利用了两个关键机制: 通过双层try/catch绕过 AnnotationInvocationHandler 的type检查 利用TC_ REFERENCE机制复用已反序列化的对象 成功利用需要: 精心构造的序列化数据 正确处理数据对齐问题 理解Java序列化/反序列化的内部机制 通过深入分析序列化数据和调试,可以完全理解漏洞利用的每个细节,包括为什么需要删除某些数据以及如何确保利用链的完整执行。