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 | public interface Greeting { |
1 |
|
1 |
|
Client
1 | public class client { |
Result
1 | Hessian Call: Hi {a=a} |
Servlet Based
Server
1 | import com.caucho.hessian.server.HessianServlet; |
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 | public class ser { |
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 | public class client { |
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 | public static void main(String[] args)throws Exception{ |
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 | HashMap.put() |