前言 就像以前看到过的,Java8以后的一些版本对反射操作做了限制,比如Java9就对反射操作进行了一定程度的WARNING。
这里学习一下具体的限制版本和绕过方式,顺带一说的是,IDEA切换Maven项目的Java版本时,也要记得把pom.xml里面的改一下。
环境搭建 搞两个版本的Java,分别是Java11和Java17。
Java11 首先,反射使用TemplatesImpl加载类依旧行得通,计算器正常弹出,只是会发生大量WARNING:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public static void main (String[] args) { try { Class<?> cls = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" ); Object templates = cls.getConstructor().newInstance(); Class<?> cls2 = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl" ); Object factory = cls2.getConstructor().newInstance(); setField(templates, "_name" , "Twings" ); setField(templates, "_bytecodes" , new byte [][]{getBytes()}); setField(templates, "_tfactory" , factory); Method m = cls.getDeclaredMethod("getOutputProperties" ); m.invoke(templates); }catch (Exception e) { log.info(e.getMessage()); } }private static byte [] getBytes() { return Base64.getDecoder().decode("yv66vgAAADQAIwoAAwAOBwAPBwAQBwARAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABtMb3JnL2V4YW1wbGUvVGVzdEphdmFzc2lzdDsBAApTb3VyY2VGaWxlAQASVGVzdEphdmFzc2lzdC5qYXZhDAAFAAYBABlvcmcvZXhhbXBsZS9UZXN0SmF2YXNzaXN0AQAQamF2YS9sYW5nL09iamVjdAEAFGphdmEvaW8vU2VyaWFsaXphYmxlAQAIPGNsaW5pdD4BABFqYXZhL2xhbmcvUnVudGltZQcAEwEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMABUAFgoAFAAXAQAIY2FsYy5leGUIABkBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAbABwKABQAHQEADVN0YWNrTWFwVGFibGUBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0BwAgCgAhAA4AIQACACEAAQAEAAAAAgABAAUABgABAAcAAAAvAAEAAQAAAAUqtwAisQAAAAIACAAAAAYAAQAAAAUACQAAAAwAAQAAAAUACgALAAAACAASAAYAAQAHAAAAJAADAAIAAAAPpwADAUy4ABgSGrYAHlexAAAAAQAfAAAAAwABAwABAAwAAAACAA0=" ); }
WARNING:
1 2 3 4 5 WARNING: An illegal reflective access operation has occurredWARNING: Illegal reflective access by org.example.Main (file:/D:/Java1.8/Unsafe/Unsafe/target/classes/) to constructor com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl()WARNING: Please consider reporting this to the maintainers of org.example.MainWARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operationsWARNING: All illegal access operations will be denied in a future release
该版本下Unsafe类去除了defineClass函数,只能通过defineAnonymousClass函数来加载类字节码:
1 2 3 4 5 6 Class<?> cls = Class.forName("sun.misc.Unsafe" );Field field = getField(cls, "theUnsafe" );Object unSafe = field.get(null );Method method = unSafe.getClass().getDeclaredMethod("defineAnonymousClass" , Class.class, byte [].class, Object[].class); Class<?> c = (Class<?>)method.invoke(unSafe, new Object []{Runtime.class, getBytes(), null }); c.newInstance();
以前在Java9行得通,但是在Java11.0.23版本下出现了问题:
1 Host class java /lang /Runtime and anonymous class org /example /TestJavassist are in different packages
看起来是待加载类和hostClass参数所属包不同导致的,应该可以通过修改待加载类的所属包来解决,但如果直接修改包名为java.lang来匹配就会遇到问题:
1 Prohibited package name: java.lang
解决方法一是修改类加载的hostClass或者在已知环境的条件下修改待加载类的包:
1 2 3 4 5 6 Class<?> cls = Class.forName("sun.misc.Unsafe" );Field field = getField(cls, "theUnsafe" );Object unSafe = field.get(null );Method method = unSafe.getClass().getDeclaredMethod("defineAnonymousClass" , Class.class, byte [].class, Object[].class); Class<?> c = (Class<?>)method.invoke(unSafe, new Object []{Main.class, getBytes(), null }); c.newInstance();
另一个方法好像也可以用于Java17的绕过,所以在下一部分再研究。
Java17 在该版本下,TemplatesImpl类也无法直接使用了,会爆出异常:
1 信息: class org.example .Main cannot access class com.sun .org .apache .xalan .internal .xsltc .trax .TemplatesImpl (in module java.xml) because module java.xml does not export com.sun .org .apache .xalan .internal .xsltc .trax to unnamed module @4 dd8dc3
可以看出,Java17加强了对反射的限制,禁止了跨模块的反射访问,报错发生在getDeclaredConstructor之后的newInstance步骤,也就是说无法通过反射获得对象实例进行函数调用。根据参考文章的说法,现在采用的是强封装,所有反射在访问java.*包下非public属性和函数时,将抛出InaccessibleObjectException异常。
而且Unsafe中的defineClass函数和defineAnonymousClass函数都被移除了,不能直接进行类加载,但是根据参考文章,此时官方还预留了sun.misc和sun.reflect两个包,这两个包仍然可以可以被反射调用,所以就有一种可能性,这两个包内存在某个类,通过反射调用它的某个函数可以绕过模块限制。
此时我们需要使用ClassLoader的defineClass函数进行类加载,所以我们就需要想办法让此时正在运行的代码(如Main函数)与ClassLoader属于同一个包,比如修改当前代码所属的包。
而Unsafe类正好有这个功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 Class<?> unSafe=Class.forName("sun.misc.Unsafe" ); Field unSafeField=unSafe.getDeclaredField("theUnsafe" ); unSafeField.setAccessible(true );Unsafe unSafeClass = (Unsafe) unSafeField.get(null );Module baseModule = Object.class.getModule(); Class<?> currentClass= Main.class;long addr = unSafeClass.objectFieldOffset(Class.class.getDeclaredField("module" )); unSafeClass.getAndSetObject(currentClass, addr, baseModule);Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass" , String.class, byte [].class, int .class, int .class); defineClass.setAccessible(true );byte [] bytes = getBytes(); Class<?> calc= (Class<?>) defineClass.invoke(ClassLoader.getSystemClassLoader(), "org.example.TestJavassist" , bytes, 0 , bytes.length); calc.newInstance();
需要注意的是,字节码需要重新生成,去掉TemplatesImpl使用时加上的superClass。
可以看到,虽然可以进行类加载,但是由于模块隔离,想要调用TemplatesImpl等类的函数就需要先获得其module并改写当前类,然而这种方法又需要反射其Class和Field,看起来好像没什么办法。
至于对反序列化的影响,改日再学习。
参考 浅析高版本JDK反射类加载问题
从一道题初接触RASP