高版本JDK反射绕过
静态常量修改
修改static final属性值,关键在于通过反射将字段的final修饰符去掉
在JDK<=11,可以按照如下流程修改:
通过反射获取
java.lang.reflect.Field内部的modifiersField将修改目标字段的
modifiers修改为非final设置目标字段为新的值
public class FinalTest {
private static final String secret = "Y0U_C4nNot_M0d1fy_M3";
}
Field modifierField = Class.forName("java.lang.reflect.Field").getDeclaredField("modifiers");
modifierField.setAccessible(true);
Field secret = FinalTest.class.getDeclaredField("secret");
secret.setAccessible(true);
modifierField.setInt(secret, secret.getModifiers() & ~Modifier.FINAL);
secret.set(null, "G0T_Y0U");
System.out.println(secret.get(null)); // G0T_Y0U但自从 Java 12 开始,直接获取 Field 的 modifiers 字段会得到以下错误
Exception java.lang.NoSuchFieldException: modifiers
at java.base/java.lang.Class.getDeclaredField
👉 https://bugs.openjdk.org/browse/JDK-8210522
为防止安全敏感的字段被修改,JDK12开始反射过滤机制增强
对比 JDK11 和 JDK14 的jdk.internal.reflect.Reflection

可以看到fieldFilterMap增加了Field.class的所有成员,即Field下的任何字段都不能直接通过公共反射方法获取。
跟一下反射调用的流程 getDeclaredField

进入privateGetDeclaredFields,看到这里开始过滤反射字段


这里实际上已经通过getDeclaredFields0获取到了所有字段了
getDeclaredFields0是java.lang.Class下的方法,Reflection中的methodFilterMap并未过滤此方法,因此我们直接反射调用getDeclaredFields0就能获取到Fields的所有成员
上面的绕过在JDK17以下可以成功,JDK17对java* 下的非公共字段进行反射调用获取的话就会直接报错,且看下文分析
反射加载字节码
Java不像其他脚本语言,如js、php、python等有eval函数,可以把字符串当作代码来执行。
因此在Java中最直接的任意代码执行的方式就是加载字节码了。其他代码执行的方式,如EL表达式、js引擎,限于语法的差异,并不能完美地兼容和契合Java自身的语法和类型。
加载字节码的几种方法:
URLClassLoader#loadClass:需要出网或文件落地,不好使
TransletClassLoader#defineClass:一般通过反序列化漏洞打进来
ClassLoader#defineClass:需要通过反射调用
通过JS配合ClassLoader#defineClass来做到任意代码执行
JDK版本更迭史:
JDK6、7
引入JS引擎、采用Rhino实现,不支持
Java.type等获取Java类型的操作
JDK8
JS引擎采用Nashorn实现
JDK9
引入模块机制
部分非标准库的类被移除
JDK11
Unsafe.defineClass方法被移除默认禁止跨包之间反射调用非公共方法(非强制,只是警告)
JDK12
Reflection类下的fieldFilterMap增加过滤。反射被大大限制
JDK15
JS引擎被移除JDK
JDK17
强封装
不得反射调用未开放模块的非公共方法(强制)
JDK11
上面通过js加载字节码会报错
java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(byte[],int,int) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to module jdk.scripting.nashorn.scripts
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible
但直接运行下面代码却可以
java.lang.reflect.Method继承自java.lang.reflect.Executable,Executable和java.lang.reflect.Field均继承自java.lang.reflect.AccessibleObject
AccessibleObject#checkCanSetAccessible用于检测成员是否可以被设置为可访问


可以看到是否可访问取决于如下条件:
目标类为Public
待访问的成员为Public
待访问的成员为Protected、Static,且调用方是目标类的子类
目标类的模块对调用方的模块开放
modifiers Bypass
根据上面的可访问条件,ClassLoader为PUBLIC,我们只需将defineClass方法的修饰符修改为PUBLIC即可
Java版本:
Js版本:
override field Bypass
回看setAccessible的调用逻辑
checkCanSetAccessible去判断目标的修饰符、目标类是否对调用类开放...最后返回flag再传给setAccessible0
setAccessible0直接将flag赋值给this.override,override是其父类AccessibleObject的属性
因此也可以直接修改java.lang.reflect.AccessibleObject的override属性
JDK>=12之后,反射也过滤java.lang.reflect.AccessibleObject类的所有成员,得通过getDeclaredFields0去获取override属性
JDK 12/13/14
JDK>=12报错提示:没有modifiers字段
Caused by: java.lang.NoSuchFieldException: modifiers at java.base/java.lang.Class.getDeclaredField
fieldFilterMap Bypass
看的BeiChen师傅写的 orz
https://github.com/BeichenDream/Kcon2021Code/blob/master/bypassJdk/JdkSecurityBypass.java
直接把fieldFilterMap置空了。
通过unsafe.defineAnonymousClass创建匿名内部类,由此匿名类来获取类成员偏移量(为什么要创建匿名类是因为fieldFilterMap过滤了Reflection的所有成员,无法直接使用getDeclaredField获取Field),最后再通过unsafe.putObject修改原来Reflection类的静态成员fieldFilterMap,此外还要删除reflectionData反射缓存。
Java版本:
Js版本:
defineAnonymousClass Bypass
虽然JDK11把Unsafe#defineClass移除了,但Unsafe#defineAnonymousClass还在
JDK17
JDK17中,在checkCanSetAccessible最后一关判断模块是否开放不能通过,导致抛出异常Unable to make xxx accessible: module xxx does not opens xxx to xxx
declaringModule即目标AccessibleObject所在类declaringClass所在的模块
比如java.lang.Class所在java.base
pn是declaringClass的包名
跟进Module#isOpen
如何判断目标模块是否开放或导出?
unnamed modules对外开放调用者所在模块就是目标模块,并且
pn是目标模块下的包目标模块设置设置对外开放或者它是
automatic模块目标模块是否导出
pn包(isStaticallyExportedOrOpen)目标模块是否反射可导出
pn包(isReflectivelyExportedOrOpen)
( JDK<17,这里isStaticallyExportedOrOpen返回true,模块默认开放所有包)
实际上这里有些条件在checkCanSetAccessible开始已经判定过了
callerModule和declaringModule一致callerModule和Object所在模块一致declaringModule是unnamed modules
因此我们通过Unsafe修改当前调用者的模块为Object所在模块
(用Unsafe修改,刚好fieldFilterMap没有过滤Class的module成员)
还是以最开始的修改final字段为例
SumUp
总的来说,高版本JDK对于反射的限制有两点:
JDK >= 12,
fieldFilterMap新增黑名单类,反射获取字段受限JDK >= 9,反射调用方法/获取非公开字段,
checkCanSetAccessible判断模块是否对外开放,非public类或非public方法/字段无法调用/获取
相应的绕过:
JDK <= 11,对于公开类下的非公开成员的访问,修改其访问修饰符为public,或者修改
AccessibleObject的override属性为true12 <= JDK < 17,
Unsafe实例化一个Reflection,获取fieldFilterMap偏移量,再置为空JDK = 17,修改当前调用类的module为
Object的module
Reference
2023KCon 《Java表达式攻防下的黑魔法》
https://github.com/BeichenDream/Kcon2021Code/blob/master/bypassJdk/JdkSecurityBypass.java
Last updated
Was this helpful?