代码审计系列之Hessian开发框架
字数 1619 2025-08-24 07:48:33

Hessian开发框架代码审计与渗透测试指南

1. Hessian框架简介

Hessian是一个轻量级的remoting onhttp工具,使用简单的方法提供了RMI的功能。相比WebService,Hessian更简单、快捷。它采用二进制RPC协议,特别适合发送二进制数据。

参考链接: http://hessian.caucho.com/doc/hessian-overview.xtp

2. Hessian框架配置分析

2.1 默认web.xml配置

<servlet-mapping>
    <servlet-name>HessianSpringInvokeService</servlet-name>
    <url-pattern>/*.hessian</url-pattern>
</servlet-mapping>

2.2 核心服务逻辑

HessianSpringInvokeService的核心服务方法分析:

protected void service(HttpServletRequest var1, HttpServletResponse var2) throws ServletException, IOException {
    String var3 = var1.getRequestURI();
    int var4 = var3.lastIndexOf("/");
    if(var4 > 0) {
        var3 = var3.substring(var4 + 1);
    }
    
    if(!var1.getMethod().equals("POST")) {
        var2.setStatus(500, "Hessian Requires POST");
        // 错误处理...
    } else {
        try {
            ServletInputStream var7 = var1.getInputStream();
            ServletOutputStream var8 = var2.getOutputStream();
            var2.setContentType("application/x-hessian");
            
            int var9 = var7.read();
            int var10;
            int var11;
            Object var12;
            Object var13;
            
            if(var9 == 72) { // 'H' - Hessian 2.0
                var10 = var7.read();
                var11 = var7.read();
                if(var10 != 2 || var11 != 0) {
                    throw new IOException("Version " + var10 + "." + var11 + " is not understood");
                }
                var12 = this.createHessian2Input(var7);
                var13 = new Hessian2Output(var8);
                ((AbstractHessianInput)var12).readCall();
            } else { // 'c' - Hessian 1.0
                if(var9 != 99) {
                    throw new IOException("expected 'H' (Hessian 2.0) or 'c' (Hessian 1.0) in hessian input at " + var9);
                }
                var10 = var7.read();
                var11 = var7.read();
                var12 = new HessianInput(var7);
                if(var10 >= 2) {
                    var13 = new Hessian2Output(var8);
                } else {
                    var13 = new HessianOutput(var8);
                }
            }
            
            SerializerFactory var14 = this.getSerializerFactory();
            ((AbstractHessianInput)var12).setSerializerFactory(var14);
            ((AbstractHessianOutput)var13).setSerializerFactory(var14);
            
            this.getSkeletonByServiceId(var3).invoke((AbstractHessianInput)var12, (AbstractHessianOutput)var13);
        } catch (Throwable var15) {
            throw new ServletException(var15);
        }
    }
}

3. Hessian协议分析

3.1 协议版本识别

  • H (ASCII 72): Hessian 2.0版本

    • 后面跟随两个字节表示版本号(2.0)
  • c (ASCII 99): Hessian 1.0版本

    • 后面跟随两个字节(无实际意义,占位符)

3.2 服务映射机制

getSkeletonByServiceId方法分析:

private HessianSkeleton getSkeletonByServiceId(String var1) {
    HessianSkeleton var2 = (HessianSkeleton)this.skeletons.get(var1);
    if(var2 != null) {
        return var2;
    } else {
        Object var3 = ApplusContext.getBean(var1);
        var2 = new HessianSkeleton(var3, var3.getClass());
        this.skeletons.put(var1, var2);
        return var2;
    }
}

3.3 Spring整合配置示例

applicationContext-all.xml配置示例:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="...">
       
    <!-- hessian服务通过spring暴露出去 -->
    <bean id="EncryptService.hessian" class="com.ufgov.admin.license.svc.EncryptServiceImpl">
    </bean>
</beans>

4. Hessian方法调用机制

4.1 方法调用流程

  1. 读取方法名:

    public String readMethod() throws IOException {
        int tag = this.read();
        if(tag != 109) { // 'm' - 方法标识
            throw this.error("expected hessian method ('m') at " + this.codeName(tag));
        } else {
            int d1 = this.read();
            int d2 = this.read();
            this._isLastChunk = true;
            this._chunkLength = d1 * 256 + d2;
            this._sbuf.setLength(0);
            int ch;
            while((ch = this.parseChar()) >= 0) {
                this._sbuf.append((char)ch);
            }
            this._method = this._sbuf.toString();
            return this._method;
        }
    }
    
  2. 方法映射存储:

    protected AbstractSkeleton(Class apiClass) {
        this._apiClass = apiClass;
        Method[] methodList = apiClass.getMethods();
        for(int i = 0; i < methodList.length; ++i) {
            Method method = methodList[i];
            if(this._methodMap.get(method.getName()) == null) {
                this._methodMap.put(method.getName(), methodList[i]);
            }
            Class[] param = method.getParameterTypes();
            String mangledName = method.getName() + "_" + param.length;
            this._methodMap.put(mangledName, methodList[i]);
            this._methodMap.put(mangleName(method, false), methodList[i]);
        }
    }
    
  3. 反射调用:

    Object var17 = method.invoke(service, values);
    

4.2 参数读取机制

public Object readObject() throws IOException {
    int tag = this.read();
    String type;
    int type1;
    switch(tag) {
        case 66: // 'B' - 二进制数据
        case 98: // 'b'
            // 处理二进制数据...
        case 68: // 'D' - Double
            return new Double(this.parseDouble());
        case 70: // 'F' - false
            return Boolean.valueOf(false);
        case 73: // 'I' - Integer
            return new Integer(this.parseInt());
        case 76: // 'L' - Long
            return new Long(this.parseLong());
        case 77: // 'M' - Map
            type = this.readType();
            return this._serializerFactory.readMap(this, type);
        case 78: // 'N' - null
            return null;
        case 82: // 'R' - 引用
            type1 = this.parseInt();
            return this._refs.get(type1);
        case 83: // 'S' - 字符串(结束块)
        case 115: // 's' - 字符串(非结束块)
            this._isLastChunk = tag == 83;
            this._chunkLength = (this.read() << 8) + this.read();
            this._sbuf.setLength(0);
            while((type1 = this.parseChar()) >= 0) {
                this._sbuf.append((char)type1);
            }
            return this._sbuf.toString();
        case 84: // 'T' - true
            return Boolean.valueOf(true);
        case 86: // 'V' - List
            type = this.readType();
            int url1 = this.readLength();
            return this._serializerFactory.readList(this, url1, type);
        case 88: // 'X' - XML(结束块)
        case 120: // 'x' - XML(非结束块)
            this._isLastChunk = tag == 88;
            this._chunkLength = (this.read() << 8) + this.read();
            return this.parseXML();
        case 100: // 'd' - Date
            return new Date(this.parseLong());
        case 114: // 'r' - 远程引用
            type = this.readType();
            String url = this.readString();
            return this.resolveRemote(type, url);
        default:
            throw this.error("unknown code for readObject at " + this.codeName(tag));
    }
}

5. Hessian数据包构造

5.1 基本结构

  1. 版本标识:

    • Hessian 2.0: H (72) + \x02 + \x00
    • Hessian 1.0: c (99) + 两个占位字节
  2. 方法调用:

    • 方法标识: m (109)
    • 方法名长度: 两个字节表示(大端序)
    • 方法名: 实际方法名
  3. 参数传递:

    • 字符串参数: S (83) + 长度(两个字节) + 字符串内容
    • 其他类型参数: 对应类型标识 + 数据
  4. 结束标识: z (122)

5.2 示例数据包

假设调用方法getmodelCodeInfo,参数为SQL注入payload:

c12m\x00\x10getmodelCodeInfoS\x0081' union select USER,NULL,NULL,NULL,NULL from dual -- sdz

十六进制表示:

63 31 32 6D 00 10 67 65 74 6D 6F 64 65 6C 43 6F 
64 65 49 6E 66 6F 53 00 38 31 27 20 75 6E 69 6F 
6E 20 73 65 6C 65 63 74 20 55 53 45 52 2C 4E 55 
4C 4C 2C 4E 55 4C 4C 2C 4E 55 4C 4C 20 66 72 6F 
6D 20 64 75 61 6C 20 2D 2D 20 73 64 7A

其中:

  • 63 31 32: Hessian 1.0标识 + 版本(无意义)
  • 6D: 方法标识'm'
  • 00 10: 方法名长度(16字节)
  • 67 65 74...49 6E 66 6F: "getmodelCodeInfo"(16字节)
  • 53: 字符串参数标识'S'
  • 00 38: 参数长度(56字节)
  • 31 27...7A: 实际参数内容

6. 渗透测试方法

6.1 测试要点

  1. 版本识别:通过发送'H'或'c'识别Hessian版本
  2. 方法枚举:通过修改方法名长度和方法名枚举可用方法
  3. 参数注入
    • 修改参数长度字段(两个字节)以适应payload长度
    • 多余字符可用空格或注释填充
    • 适用于字符串、数字、对象等各种参数类型
  4. 反序列化攻击:针对Hessian的反序列化漏洞(参考marshalsec项目)

6.2 测试技巧

  1. 构造请求包时,确保修改参数值前面的长度字段大于等于payload长度
  2. 对于多出的字符,可以使用:
    • 空格填充
    • SQL注释(--或/* */)
    • 任意无害字符
  3. 针对二进制协议,使用十六进制编辑器或专门工具构造和解析数据包

7. 安全建议

  1. 使用Hessian最新版本,严格限制序列化和反序列化操作
  2. 对Hessian服务进行身份验证和授权控制
  3. 对输入参数进行严格过滤和验证
  4. 避免暴露敏感方法或服务
  5. 监控和记录Hessian服务的异常请求

8. 参考资源

  1. Hessian官方文档: http://hessian.caucho.com/doc/hessian-overview.xtp
  2. marshalsec项目: https://github.com/mbechler/marshalsec
  3. Hessian协议规范: http://hessian.caucho.com/doc/hessian-serialization.html
Hessian开发框架代码审计与渗透测试指南 1. Hessian框架简介 Hessian是一个轻量级的remoting onhttp工具,使用简单的方法提供了RMI的功能。相比WebService,Hessian更简单、快捷。它采用二进制RPC协议,特别适合发送二进制数据。 参考链接: http://hessian.caucho.com/doc/hessian-overview.xtp 2. Hessian框架配置分析 2.1 默认web.xml配置 2.2 核心服务逻辑 HessianSpringInvokeService的核心服务方法分析: 3. Hessian协议分析 3.1 协议版本识别 H (ASCII 72): Hessian 2.0版本 后面跟随两个字节表示版本号(2.0) c (ASCII 99): Hessian 1.0版本 后面跟随两个字节(无实际意义,占位符) 3.2 服务映射机制 getSkeletonByServiceId 方法分析: 3.3 Spring整合配置示例 applicationContext-all.xml配置示例: 4. Hessian方法调用机制 4.1 方法调用流程 读取方法名: 方法映射存储: 反射调用: 4.2 参数读取机制 5. Hessian数据包构造 5.1 基本结构 版本标识: Hessian 2.0: H (72) + \x02 + \x00 Hessian 1.0: c (99) + 两个占位字节 方法调用: 方法标识: m (109) 方法名长度: 两个字节表示(大端序) 方法名: 实际方法名 参数传递: 字符串参数: S (83) + 长度(两个字节) + 字符串内容 其他类型参数: 对应类型标识 + 数据 结束标识: z (122) 5.2 示例数据包 假设调用方法 getmodelCodeInfo ,参数为SQL注入payload: 十六进制表示: 其中: 63 31 32 : Hessian 1.0标识 + 版本(无意义) 6D : 方法标识'm' 00 10 : 方法名长度(16字节) 67 65 74...49 6E 66 6F : "getmodelCodeInfo"(16字节) 53 : 字符串参数标识'S' 00 38 : 参数长度(56字节) 31 27...7A : 实际参数内容 6. 渗透测试方法 6.1 测试要点 版本识别 :通过发送'H'或'c'识别Hessian版本 方法枚举 :通过修改方法名长度和方法名枚举可用方法 参数注入 : 修改参数长度字段(两个字节)以适应payload长度 多余字符可用空格或注释填充 适用于字符串、数字、对象等各种参数类型 反序列化攻击 :针对Hessian的反序列化漏洞(参考marshalsec项目) 6.2 测试技巧 构造请求包时,确保修改参数值前面的长度字段大于等于payload长度 对于多出的字符,可以使用: 空格填充 SQL注释(--或/* * /) 任意无害字符 针对二进制协议,使用十六进制编辑器或专门工具构造和解析数据包 7. 安全建议 使用Hessian最新版本,严格限制序列化和反序列化操作 对Hessian服务进行身份验证和授权控制 对输入参数进行严格过滤和验证 避免暴露敏感方法或服务 监控和记录Hessian服务的异常请求 8. 参考资源 Hessian官方文档: http://hessian.caucho.com/doc/hessian-overview.xtp marshalsec项目: https://github.com/mbechler/marshalsec Hessian协议规范: http://hessian.caucho.com/doc/hessian-serialization.html