dubbo反序列化问题-Hessian2安全加固和修复
字数 1569 2025-08-25 22:59:03
Dubbo Hessian2反序列化安全加固指南
0x01 背景与问题概述
Dubbo作为一款流行的分布式服务框架,默认使用Hessian2作为序列化协议。然而,Hessian2反序列化存在严重的安全隐患:
- Dubbo默认的Hessian2反序列化缺乏安全保护机制
- 攻击者可以构造恶意序列化数据触发远程代码执行
- 常见的反序列化gadget链(如Rome、XBean等)可以被利用
0x02 加固方案比较
针对Dubbo反序列化问题,主要有三种加固方案:
-
修改反序列化类型(不推荐)
- 改为原生Java、Fastjson等仍存在安全问题
- 业务修改风险高
-
RPC改为HTTP API(不推荐)
- 业务开发量大
- 架构改动成本高
-
加固Hessian2(推荐)
- 通过SPI机制扩展Hessian2
- 添加反序列化黑名单
- 对现有业务影响小
0x03 SPI机制详解
Java SPI机制
SPI(Service Provider Interface)是Java提供的服务发现机制,核心组件:
- 接口定义(如
Fruit) - 实现类(如
Apple、Mango) META-INF/services/接口全限定名文件- 内容为实现类的全限定名
加载方式:
ServiceLoader<Fruit> fruits = ServiceLoader.load(Fruit.class);
fruits.forEach(fruit -> System.out.println(fruit.name()));
Spring SPI机制
与Java SPI类似,但配置文件为META-INF/spring.factories,格式:
接口全限定名=实现类全限定名
加载方式:
List<Fruit> fruits = SpringFactoriesLoader.loadFactories(Fruit.class, null);
Dubbo SPI机制
Dubbo自定义了更强大的SPI机制:
-
配置文件位置:
META-INF/dubbo/META-INF/dubbo/internal/META-INF/services/
-
兼容处理:
- 自动处理
org.apache和com.alibaba包名转换
- 自动处理
-
核心类:
ExtensionLoader负责加载扩展实现
0x04 Hessian2安全加固实现
实现方案
通过Dubbo SPI机制扩展Hessian2,添加反序列化黑名单:
- 创建自定义序列化工厂
MyHessian2Serialization - 实现自定义输入处理
MyHessian2ObjectInput - 扩展
Hessian2Input添加黑名单检查
具体实现代码
1. MyHessian2Serialization
package com.threedr3am.learn.server.boot.serialize;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.serialize.ObjectInput;
import org.apache.dubbo.common.serialize.ObjectOutput;
import org.apache.dubbo.common.serialize.Serialization;
import org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectOutput;
public class MyHessian2Serialization implements Serialization {
@Override
public byte getContentTypeId() { return 22; }
@Override
public String getContentType() { return "x-application/hessian2"; }
@Override
public ObjectOutput serialize(URL url, OutputStream out) throws IOException {
return new Hessian2ObjectOutput(out);
}
@Override
public ObjectInput deserialize(URL url, InputStream is) throws IOException {
return new MyHessian2ObjectInput(is);
}
}
2. MyHessian2ObjectInput
package com.threedr3am.learn.server.boot.serialize;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import org.apache.dubbo.common.serialize.ObjectInput;
import org.apache.dubbo.common.serialize.hessian2.Hessian2SerializerFactory;
public class MyHessian2ObjectInput implements ObjectInput {
private final MyHessian2Input mH2i;
public MyHessian2ObjectInput(InputStream is) {
mH2i = new MyHessian2Input(is);
mH2i.setSerializerFactory(Hessian2SerializerFactory.SERIALIZER_FACTORY);
}
// 各种read方法实现...
@Override
public Object readObject() throws IOException {
return mH2i.readObject();
}
}
3. MyHessian2Input(核心安全检查)
package com.threedr3am.learn.server.boot.serialize;
import com.alibaba.com.caucho.hessian.io.Hessian2Input;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class MyHessian2Input extends Hessian2Input {
private static final Set<String> blackList = new HashSet<>();
static {
// 初始化黑名单
blackList.add("org.apache.xbean.naming.context.ContextUtil.ReadOnlyBinding");
blackList.add("org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor");
blackList.add("com.rometools.rome.feed.impl.EqualsBean");
blackList.add("com.caucho.naming.QName");
// 添加更多已知gadget类...
}
public MyHessian2Input(InputStream is) { super(is); }
// 重写所有readObject方法添加安全检查
@Override
public Object readObject() throws IOException {
checkClassDef();
return super.readObject();
}
// 类定义检查
void checkClassDef() {
if (_classDefs == null || _classDefs.isEmpty()) return;
for (Object c : _classDefs) {
Field[] fields = c.getClass().getDeclaredFields();
if (fields.length == 2) {
fields[0].setAccessible(true);
try {
String type = (String) fields[0].get(c);
if (blackList.contains(type))
_classDefs = new ArrayList(); // 清空恶意类定义
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
SPI配置文件
在resources/META-INF/dubbo/下创建org.apache.dubbo.common.serialize.Serialization文件,内容:
MyHessian2=com.threedr3am.learn.server.boot.serialize.MyHessian2Serialization
服务端配置
在application.properties中指定序列化方式:
dubbo.provider.serialization=MyHessian2
0x05 关键注意事项
- 双向部署:服务提供者和消费者两端都需要部署上述修改
- 黑名单维护:及时更新
MyHessian2Input中的黑名单 - 已知gadget类(部分):
org.apache.xbean.naming.context.ContextUtil.ReadOnlyBindingorg.springframework.aop.support.DefaultBeanFactoryPointcutAdvisorcom.rometools.rome.feed.impl.EqualsBeancom.caucho.naming.QName
0x06 扩展建议
- 持续监控:关注新的反序列化gadget发现,及时更新黑名单
- 白名单机制:对于高安全要求场景,可考虑实现白名单机制
- 版本升级:关注Dubbo官方安全更新,及时升级版本
通过以上方案,可以在不改变Dubbo核心架构的情况下,有效防御Hessian2反序列化攻击,保障分布式系统安全。