HessianServlet、HessianServiceExporter场景下的Payload
字数 2555 2025-08-29 22:41:10
HessianServlet与HessianServiceExporter漏洞利用分析
1. 背景介绍
Hessian是一种轻量级的RPC框架,在Java开发中广泛使用。目前常见的Hessian服务暴露方式主要有两种:
- HessianServlet:
com.caucho.hessian.server.HessianServlet - HessianServiceExporter:
org.springframework.remoting.caucho.HessianServiceExporter
现有的漏洞利用工具生成的payload往往针对原生Hessian反序列化场景,无法直接用于这两种服务暴露方式,因为它们在解析Hessian序列化数据时会进行额外的协议头判断。
2. 常见错误分析
当使用现有工具生成的payload攻击这两种服务时,通常会遇到以下错误:
- 协议头不匹配错误:服务端期望特定的协议头格式
- 版本校验错误:Hessian2.0服务需要正确的版本标识
3. 组件访问特征
3.1 HessianServiceExporter
典型服务暴露代码:
@Bean(name = "/remoting/AccountService")
public HessianServiceExporter accountService() {
HessianServiceExporter exporter = new HessianServiceExporter();
exporter.setService(new AccountServiceImpl());
exporter.setServiceInterface(AccountService.class);
return exporter;
}
访问特征:
- 通过Spring MVC暴露的HTTP接口
- 通常以
/remoting/*等形式出现在应用路由中
3.2 HessianServlet
访问特征:
- 直接继承
HessianServlet的HTTP服务 - 通常配置在web.xml中
4. 协议头分析
4.1 HessianServiceExporter协议头要求
POST请求进入invoke方法后,会首先检查输入流的第一个字节:
-
第一位必须是以下三种之一:
- 'H' (0x48)
- 'C' (0x43)
- 'c' (0x63)
-
不同协议头的处理逻辑:
'H' (Hessian2.0)
- 检查第二位是否为0x02(版本号)
- 检查第四位是否为'C' (0x43)
- 有效payload格式:
\x48\x02\x00\x43
'C' (Hessian2.0简化)
- 只需第一位为'C'
- 有效payload格式:
\x43
'c' (Hessian1.0)
- 检查第二位和第三位
- 需要额外添加头部:
\x48\x00\x03abc - 有效payload格式:
\x63\x02\x00\x48\x00\x03abc
4.2 HessianServlet协议头要求
与HessianServiceExporter类似,但有细微差别:
-
第一位必须是以下两种之一:
- 'r' (0x72)
- 'H' (0x48)
- 'c' (0x63)
-
不同协议头的处理逻辑:
'H' (Hessian2.0)
- 检查第二位是否为0x02(版本号)
- 检查第四位是否为'C' (0x43)
- 有效payload格式:
\x48\x02\x00\x43
'c' (Hessian1.0)
- 检查第二位和第三位
- 需要额外添加头部:
\x48\x00\x03abc - 有效payload格式:
\x63\x02\x00\x48\x00\x03abc
5. 反序列化流程分析
5.1 HessianServiceExporter流程
- 请求进入
HessianServiceExporter.invoke() - 读取输入流第一个字节进行协议头判断
- 根据协议头创建对应的输入流:
- 'H'/'C':
Hessian2Input - 'c':
HessianInput
- 'H'/'C':
- 进入
readCall方法,检查第四位是否为'C' - 调用
skeleton.invoke(in, out)进行反序列化
5.2 HessianServlet流程
- 请求进入
HessianServlet.service() - 读取输入流前三个字节进行协议头判断
- 根据协议头创建对应的输入流
- 后续流程与HessianServiceExporter类似
6. 有效payload构造
6.1 针对HessianServiceExporter
-
Hessian2.0:
\x48\x02\x00\x43 + [原生Hessian2.0 payload] -
简化Hessian2.0:
\x43 + [原生Hessian2.0 payload] -
Hessian1.0:
\x63\x02\x00\x48\x00\x03abc + [原生Hessian1.0 payload]
6.2 针对HessianServlet
-
Hessian2.0:
\x48\x02\x00\x43 + [原生Hessian2.0 payload] -
Hessian1.0:
\x63\x02\x00\x48\x00\x03abc + [原生Hessian1.0 payload]
7. 原生Hessian与封装Hessian的区别
原生Hessian反序列化(如xxl-job):
HessianInput input = new HessianInput(stream);
input.readObject();
- 无协议头检查
- 直接接受原生Hessian序列化流
封装Hessian(HessianServlet/HessianServiceExporter):
- 有严格的协议头检查
- 需要添加特定前缀才能进入反序列化流程
8. 协议头总结表
| 协议头 | ASCII | HEX | 版本 | 输入流 | 输出流 | 适用场景 | 备注 |
|---|---|---|---|---|---|---|---|
| 'H' | 72 | \x48\x02\x00\x43 | Hessian 2.0 | Hessian2Input | Hessian2Output | 两者通用 | Hessian2.0稳定利用 |
| 'C' | 67 | \x43 | Hessian 2.0 | Hessian2Input | Hessian2Output | 仅HessianServiceExporter | 简化版 |
| 'c' | 99 | \x63\x02\x00\x48\x00\x03abc | Hessian 1.0 | HessianInput | Hessian(2)Output | 两者通用 | 根据版本选择输出流 |
9. 实际利用建议
- 优先使用'H'开头的Hessian2.0 payload,兼容性最好
- 对于HessianServiceExporter,可以尝试简化版的'C'开头payload
- Hessian1.0利用限制较多,不建议优先考虑
- 在实际测试中,可以通过修改现有工具的payload生成逻辑,添加相应的协议头前缀
10. 防御建议
- 升级Hessian到最新版本
- 对Hessian服务进行访问控制
- 考虑使用白名单机制限制可反序列化的类
- 监控异常的Hessian协议头请求