java之jdk17反射机制绕过深入剖析
字数 1956 2025-08-29 22:41:10

JDK17反射机制绕过深入剖析

问题背景

在JDK17中,Java的反射机制受到了更严格的模块化限制,导致传统的反射调用方式(如defineClass方法加载恶意字节码)会遇到访问限制问题。本文深入分析JDK17反射机制的限制及绕过方法。

反射调用限制分析

当通过反射调用defineClass方法时,会在setAccessible方法处报错。深入跟踪发现关键在于checkCanSetAccessible方法的限制逻辑:

  1. 调用者模块与声明模块相同:如果调用者的模块(callerModule)和声明该方法的模块(declaringModule)是同一个模块,返回true
  2. 调用者模块是java.base:如果调用者所在的模块是java.base,返回true
  3. 未命名模块:如果declaringModule是一个未命名模块(Unnamed Module),返回true
  4. 公共类与公共成员:如果声明的类是public,并且该类所在的包被导出给调用者模块(declaringModule.isExported(pn, callerModule)),且成员是public,则可以访问
  5. protected静态成员:如果成员是protectedstatic,并且调用者类是声明类的子类(isSubclassOf(caller, declaringClass)),则可以访问
  6. 开放包:如果声明类的包通过open关键字开放给调用者模块(declaringModule.isOpen(pn, callerModule)),则可以访问

Unsafe绕过技术

基本原理

在JDK9+的模块化机制中,sun.miscsun.reflect包下的类可以正常反射使用。关键利用点在于:

通过Unsafe类修改调用类的module属性为java.base,使checkCanSetAccessible方法返回true,从而允许反射调用。

实现步骤

  1. 获取Unsafe对象
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
  1. 获取java.base模块引用
Module baseModule = Object.class.getModule();
  1. 修改当前类的module属性
Class<?> currentClass = getClass();
unsafe.putObject(currentClass, unsafe.objectFieldOffset(Class.class.getDeclaredField("module")), baseModule);

技术原理

  • declaringModule通过declaringClass.getModule()获取,为module java.base
  • 将当前类的module属性设置为Object.class.getModule()的值(也是java.base
  • Unsafe的putObject方法直接操作JVM内存,绕过私有属性限制
  • 设为同一module后,setAccessible(true)不会报错

TemplatesImpl类的模块化限制

问题分析

JDK17的模块化机制导致无法直接使用TemplatesImpl类。尝试使用CB链时会直接报错,因为:

  1. 类访问限制:与之前情况不同,这里的问题是类本身就不能访问
  2. 反射获取实例:可以使用Unsafe反射获取TemplatesImpl实例并赋值
  3. 反序列化问题:在调用compare方法时仍会报错,因为模块化机制限制了getter方法的调用

代理类绕过尝试

通过JdkDynamicAopProxy代理类间接调用:

  1. 代理类调用getOutputProperties方法(public方法)
  2. 代理类可以自由调用
  3. 最终会调用到TemplatesImplgetOutputProperties方法

问题出现

  • 到达defineTransletClasses方法后
  • 调用defineClass加载恶意类时失败
  • 原因:恶意类继承AbstractTranslet,而AbstractTranslet受模块化限制

继承限制

  • 不继承AbstractTranslet:无法通过判断,不会实例化恶意类
  • 继承AbstractTranslet:受模块化限制导致加载失败

JdbcRowSetImpl类分析

JdbcRowSetImpl类的getDatabaseMetaData方法可用于JNDI注入:

  1. 是一个getter方法
  2. 问题:仅在JdbcRowSetImpl类中存在,无法用代理类绕过

适用场景总结

对于以下情况可以使用代理类绕过:

  1. 方法受模块化影响
  2. 接口类有对应方法
  3. sink点不涉及模块化限制

完整绕过示例代码

// 获取Unsafe实例
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);

// 获取java.base模块
Module baseModule = Object.class.getModule();

// 修改当前类的module属性
Class<?> currentClass = getClass();
unsafe.putObject(currentClass, unsafe.objectFieldOffset(Class.class.getDeclaredField("module")), baseModule);

// 现在可以进行反射调用
Class<?> clazz = Class.forName("sun.reflect.misc.Trampoline");
Method method = clazz.getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);
method.setAccessible(true);

结论

JDK17的模块化机制增加了反射使用的限制,但通过Unsafe类直接操作内存修改模块属性,可以在特定条件下绕过这些限制。然而,对于某些类(如TemplatesImpl)的核心功能仍受到严格限制,需要寻找其他利用链或方法。

JDK17反射机制绕过深入剖析 问题背景 在JDK17中,Java的反射机制受到了更严格的模块化限制,导致传统的反射调用方式(如 defineClass 方法加载恶意字节码)会遇到访问限制问题。本文深入分析JDK17反射机制的限制及绕过方法。 反射调用限制分析 当通过反射调用 defineClass 方法时,会在 setAccessible 方法处报错。深入跟踪发现关键在于 checkCanSetAccessible 方法的限制逻辑: 调用者模块与声明模块相同 :如果调用者的模块( callerModule )和声明该方法的模块( declaringModule )是同一个模块,返回 true 调用者模块是java.base :如果调用者所在的模块是 java.base ,返回 true 未命名模块 :如果 declaringModule 是一个未命名模块(Unnamed Module),返回 true 公共类与公共成员 :如果声明的类是 public ,并且该类所在的包被导出给调用者模块( declaringModule.isExported(pn, callerModule) ),且成员是 public ,则可以访问 protected静态成员 :如果成员是 protected 且 static ,并且调用者类是声明类的子类( isSubclassOf(caller, declaringClass) ),则可以访问 开放包 :如果声明类的包通过 open 关键字开放给调用者模块( declaringModule.isOpen(pn, callerModule) ),则可以访问 Unsafe绕过技术 基本原理 在JDK9+的模块化机制中, sun.misc 和 sun.reflect 包下的类可以正常反射使用。关键利用点在于: 通过Unsafe类修改调用类的 module 属性为 java.base ,使 checkCanSetAccessible 方法返回 true ,从而允许反射调用。 实现步骤 获取Unsafe对象 : 获取java.base模块引用 : 修改当前类的module属性 : 技术原理 declaringModule 通过 declaringClass.getModule() 获取,为 module java.base 将当前类的 module 属性设置为 Object.class.getModule() 的值(也是 java.base ) Unsafe的 putObject 方法直接操作JVM内存,绕过私有属性限制 设为同一module后, setAccessible(true) 不会报错 TemplatesImpl类的模块化限制 问题分析 JDK17的模块化机制导致无法直接使用 TemplatesImpl 类。尝试使用CB链时会直接报错,因为: 类访问限制 :与之前情况不同,这里的问题是类本身就不能访问 反射获取实例 :可以使用Unsafe反射获取 TemplatesImpl 实例并赋值 反序列化问题 :在调用 compare 方法时仍会报错,因为模块化机制限制了 getter 方法的调用 代理类绕过尝试 通过 JdkDynamicAopProxy 代理类间接调用: 代理类调用 getOutputProperties 方法( public 方法) 代理类可以自由调用 最终会调用到 TemplatesImpl 的 getOutputProperties 方法 问题出现 : 到达 defineTransletClasses 方法后 调用 defineClass 加载恶意类时失败 原因:恶意类继承 AbstractTranslet ,而 AbstractTranslet 受模块化限制 继承限制 不继承 AbstractTranslet :无法通过判断,不会实例化恶意类 继承 AbstractTranslet :受模块化限制导致加载失败 JdbcRowSetImpl类分析 JdbcRowSetImpl 类的 getDatabaseMetaData 方法可用于JNDI注入: 是一个 getter 方法 问题:仅在 JdbcRowSetImpl 类中存在,无法用代理类绕过 适用场景总结 对于以下情况可以使用代理类绕过: 方法受模块化影响 接口类有对应方法 sink点不涉及模块化限制 完整绕过示例代码 结论 JDK17的模块化机制增加了反射使用的限制,但通过Unsafe类直接操作内存修改模块属性,可以在特定条件下绕过这些限制。然而,对于某些类(如 TemplatesImpl )的核心功能仍受到严格限制,需要寻找其他利用链或方法。