Fastjson<=1.2.24漏洞复现

Fastjson<=1.2.24漏洞复现

Posted by SEVENTEEN on April 6, 2022

前言

   这次来分析一下Fastjson低版本漏洞的成因以及利用。

影响范围

   Fastjson <= 1.2.24

环境搭建

   如果是用Maven部署完基础Spring环境后,还需要添加一个类用来修改json解析器,Spring内置默认是Jackson。

import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;

@Configuration
public class FastjsonConfig {
    @Bean
    public HttpMessageConverters fastJsonHttpMessageConverters() {

        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();

        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);

        fastConverter.setFastJsonConfig(fastJsonConfig);
        HttpMessageConverter<?> converter = fastConverter;
        return new HttpMessageConverters(converter);
    }
}

   为了方便调试,这里直接调用Fastjson库来使用。

代码分析

   写好payload后直接在JSON.parse(payload)打下断点。

   跟进parse方法,在重载后的parse方法中先对text(即打过去的payload)进行判空, 非空的话进入else实例化DefaultJSONParser方法。

   这里调用DefaultJSONParser方法来解析json数据,对第一个字符进行判断后, 确定是{给token赋值12。

   返回之后调用parse方法根据token去解析json。

   又调用了parseObject方法来解析类。

   重点差不多从这里开始,前面大部分还是json解析的常规操作, 这里结合token以及token后的字符做判断利用while循环做嵌套解析,之后进入if条件调用scanSymbol方法获取key, 所以即将开始解析@type字段。

   当第二次while循环走到上面的地方的时候,会开始给key赋值@type, 所以会进入到这个if条件会调用TypeUtils.loadClass方法调用@type里的类名。 按我的理解,这里之所以回去loadclass,是因为这个@type原先的功能就是用来指定json反序列化哪个类, 不然的话,如果有两个类中有同名变量的话,会无法正确反序列化。

   这里先从mappings里面寻找类,发现为null之后会再调用loadclass加载对应的类返回。

   返回之后来到这个地方获取deserializer类,来跟进一下这个方法。

   跟进之后还发现这里从Config中读取了denyList黑名单,禁止反射java.lang.Thread类。

   通过调用createJavaBeanDeserializer方法获取derializer。

   跟进一下JavaBeanInfo.build方法。

   首先获取@type值中的类名,获取所有的方法名,需要满足条件才能进入到if中。 接着,进行遍历获取set开头的方法(下面也有一个一样的代码段进行遍历get开头的方法)。

   需要满足的方法条件:

1. 方法名字符串长度大于4

2. 非静态方法

3. 返回值为void或者当前类

4. 参数个数为1

   之后,通过add方法将对应的方法名去掉set开头,存入fieldList中。

   回来调用deserializer方法。

   之后调用fieldDeser.setValue方法。

   在这里调用反射刚刚存入fieldList的方法名。

   接下来看一下为什么通过反射加载com.sun.rowset.JdbcRowSetImpl类。 可以看到这个setDataSourceName进行了赋值,同时对this.conn初始化赋值为空。

   之后autoCommit触发再次反射调用setAutoCommit方法,因为上面对this.conn初始化赋值为空, 所以进入else里调用了connect方法。

   前面我们对DataSourceName做了赋值,所以getDataSourceName判断不为空, 进入到elseif中调用lookup方法触发rmi注入。

There Is Nothing Below

   

Turn at the next intersection.